[
  {
    "path": ".dockerignore",
    "content": ".idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/usage.statistics.xml\n.idea/**/dictionaries\n.idea/**/shelf\n.idea/**/aws.xml\n.idea/**/contentModel.xml\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n.idea/**/gradle.xml\n.idea/**/libraries\ncmake-build-*/\n.idea/**/mongoSettings.xml\n*.iws\nout/\n.idea_modules/\natlassian-ide-plugin.xml\n.idea/replstate.xml\n.idea/sonarlint/\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n.idea/httpRequests\n.idea/caches/build_file_checksums.ser\n.DS_Store\n.AppleDouble\n.LSOverride\nIcon\n._*\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n__pycache__/\n*.py[cod]\n*$py.class\n*.so\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n*.manifest\n*.spec\npip-log.txt\npip-delete-this-directory.txt\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n*.mo\n*.pot\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\ninstance/\n.webassets-cache\n.scrapy\ndocs/_build/\n.pybuilder/\ntarget/\n.ipynb_checkpoints\nprofile_default/\nipython_config.py\n.pdm.toml\n.pdm-python\n.pdm-build/\n__pypackages__/\ncelerybeat-schedule\ncelerybeat.pid\n*.sage.py\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n.spyderproject\n.spyproject\n.ropeproject\n/site\n.mypy_cache/\n.dmypy.json\ndmypy.json\n.pyre/\n.pytype/\ncython_debug/\n/plugins/update_qr.py\n"
  },
  {
    "path": ".github/workflows/docker-build-on-commit.yml",
    "content": "name: Docker Multi-arch Build on Commit\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - 'Dockerfile'\n      - 'docker-compose.yml'\n      - '**.py'\n      - 'requirements.txt'\n      - '**.conf'\n      - '**.toml'\n      - '**.yml'\n      - '.github/workflows/docker-build-on-commit.yml'\n      - '.dockerignore'\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: 检出代码\n        uses: actions/checkout@v3\n\n      - name: 设置 QEMU 模拟器\n        uses: docker/setup-qemu-action@v2\n\n      - name: 设置 Docker Buildx\n        uses: docker/setup-buildx-action@v2\n        with:\n          driver-opts: |\n            image=moby/buildkit:master\n          install: true\n\n      - name: 创建多架构构建器\n        run: |\n          docker buildx create --name multiarch --use\n          docker buildx inspect --bootstrap\n\n      - name: 登录到 Docker Hub\n        uses: docker/login-action@v3\n        with:\n          username: ${{ vars.DOCKERHUB_USERNAME }}\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: 获取短 SHA\n        id: vars\n        run: echo \"GIT_SHA=$(git rev-parse --short HEAD)\" >> $GITHUB_ENV\n\n      - name: 构建并推送多架构镜像\n        uses: docker/build-push-action@v4\n        with:\n          context: .\n          builder: multiarch\n          push: true\n          platforms: linux/amd64,linux/arm64\n          tags: |\n            ${{ vars.DOCKERHUB_USERNAME }}/xybotv2:latest\n            ${{ vars.DOCKERHUB_USERNAME }}/xybotv2:${{ env.GIT_SHA }}\n          cache-from: type=registry,ref=${{ vars.DOCKERHUB_USERNAME }}/xybotv2:latest\n          cache-to: type=inline,mode=max"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# UV\n#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#uv.lock\n\n# poetry\n#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.\n#   This is especially recommended for binary packages to ensure reproducibility, and is more\n#   commonly ignored for libraries.\n#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control\n#poetry.lock\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n#   in version control.\n#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control\n.pdm.toml\n.pdm-python\n.pdm-build/\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project config\n.spyderproject\n.spyproject\n\n# Rope project config\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# PyCharm\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n#.idea/\n\n# PyPI configuration file\n.pypirc\n\n# XYBot\nresource/robot_stat.json\nWechatAPI/Client/login_stat.json\n/WechatAPIDocs/_build/doctrees/environment.pickle\n/WechatAPIDocs/_build/doctrees/index.doctree\n/WechatAPIDocs/_build/html/_modules/WechatAPI/Client/base.html\n/WechatAPIDocs/_build/html/_modules/WechatAPI/Client/chatroom.html\n/WechatAPIDocs/_build/html/_modules/WechatAPI/Client/friend.html\n/WechatAPIDocs/_build/html/_modules/WechatAPI/Client/hongbao.html\n/WechatAPIDocs/_build/html/_modules/WechatAPI/Client/login.html\n/WechatAPIDocs/_build/html/_modules/WechatAPI/Client/message.html\n/WechatAPIDocs/_build/html/_modules/WechatAPI/Client/protect.html\n/WechatAPIDocs/_build/html/_modules/WechatAPI/Client/tool.html\n/WechatAPIDocs/_build/html/_modules/WechatAPI/Client/user.html\n/WechatAPIDocs/_build/html/_modules/index.html\n/WechatAPIDocs/_build/html/_sources/index.rst.txt\n/WechatAPIDocs/_build/html/_static/scripts/furo.js\n/WechatAPIDocs/_build/html/_static/scripts/furo.js.LICENSE.txt\n/WechatAPIDocs/_build/html/_static/scripts/furo.js.map\n/WechatAPIDocs/_build/html/_static/scripts/furo-extensions.js\n/WechatAPIDocs/_build/html/_static/styles/furo.css\n/WechatAPIDocs/_build/html/_static/styles/furo.css.map\n/WechatAPIDocs/_build/html/_static/styles/furo-extensions.css\n/WechatAPIDocs/_build/html/_static/styles/furo-extensions.css.map\n/WechatAPIDocs/_build/html/_static/basic.css\n/WechatAPIDocs/_build/html/_static/debug.css\n/WechatAPIDocs/_build/html/_static/doctools.js\n/WechatAPIDocs/_build/html/_static/documentation_options.js\n/WechatAPIDocs/_build/html/_static/file.png\n/WechatAPIDocs/_build/html/_static/language_data.js\n/WechatAPIDocs/_build/html/_static/minus.png\n/WechatAPIDocs/_build/html/_static/plus.png\n/WechatAPIDocs/_build/html/_static/pygments.css\n/WechatAPIDocs/_build/html/_static/searchtools.js\n/WechatAPIDocs/_build/html/_static/skeleton.css\n/WechatAPIDocs/_build/html/_static/sphinx_highlight.js\n/WechatAPIDocs/_build/html/_static/translations.js\n/WechatAPIDocs/_build/html/.buildinfo\n/WechatAPIDocs/_build/html/genindex.html\n/WechatAPIDocs/_build/html/index.html\n/WechatAPIDocs/_build/html/objects.inv\n/WechatAPIDocs/_build/html/py-modindex.html\n/WechatAPIDocs/_build/html/search.html\n/WechatAPIDocs/_build/html/searchindex.js\n!/plugins/update_qr.py\n/WechatAPI/core/XYWechatPad\n/resource/images/avatar/cardicon_default.png\n/database/keyval.db\n/database/message.db\n/database/xybot.db\n/.idea/inspectionProfiles/profiles_settings.xml\n/.idea/inspectionProfiles/Project_Default.xml\n/.idea/.gitignore\n/.idea/misc.xml\n/.idea/modules.xml\n/.idea/sqldialects.xml\n/.idea/vcs.xml\n/.idea/XYBotV2.iml\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM python:3.11-slim\n\n# 设置工作目录\nWORKDIR /app\n\n# 设置环境变量\nENV TZ=Asia/Shanghai\nENV IMAGEIO_FFMPEG_EXE=/usr/bin/ffmpeg\n\n# 安装系统依赖\nRUN apt-get update && apt-get install -y \\\n    ffmpeg \\\n    redis-server \\\n    && rm -rf /var/lib/apt/lists/*\n\n# 复制 Redis 配置\nCOPY redis.conf /etc/redis/redis.conf\n\n# 复制依赖文件\nCOPY requirements.txt .\n\n# 安装 Python 依赖\nRUN pip install --no-cache-dir -r requirements.txt\n# 安装gunicorn和eventlet\nRUN pip install --no-cache-dir gunicorn eventlet\n\n# 复制应用代码\nCOPY . .\n\n# 创建启动脚本\nRUN echo '#!/bin/bash\\n\\\nredis-server /etc/redis/redis.conf --daemonize yes\\n\\\npython app.py' > /app/start.sh \\\n    && chmod +x /app/start.sh\n\n# 设置启动命令\nCMD [\"/app/start.sh\"]\n\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "# 个人原因，停止维护。\n\n# 🤖 XYBot V2\n\nXYBot V2 是一个功能丰富的微信机器人框架,支持多种互动功能和游戏玩法。\n\n# 免责声明\n\n- 这个项目免费开源，不存在收费。\n- 本工具仅供学习和技术研究使用，不得用于任何商业或非法行为。\n- 本工具的作者不对本工具的安全性、完整性、可靠性、有效性、正确性或适用性做任何明示或暗示的保证，也不对本工具的使用或滥用造成的任何直接或间接的损失、责任、索赔、要求或诉讼承担任何责任。\n- 本工具的作者保留随时修改、更新、删除或终止本工具的权利，无需事先通知或承担任何义务。\n- 本工具的使用者应遵守相关法律法规，尊重微信的版权和隐私，不得侵犯微信或其他第三方的合法权益，不得从事任何违法或不道德的行为。\n- 本工具的使用者在下载、安装、运行或使用本工具时，即表示已阅读并同意本免责声明。如有异议，请立即停止使用本工具，并删除所有相关文件。\n\n# 📄 文档\n\n## https://henryxiaoyang.github.io/XYBotV2\n\n# ✨ 主要功能\n\n## 🛠️ 基础功能\n\n- 🤖 AI聊天 - 支持文字、图片、语音等多模态交互\n- 📰 每日新闻 - 自动推送每日新闻\n- 🎵 点歌系统 - 支持在线点歌\n- 🌤️ 天气查询 - 查询全国各地天气\n- 🎮 游戏功能 - 五子棋、战争雷霆玩家查询等\n\n## 💎 积分系统\n\n- 📝 每日签到 - 支持连续签到奖励\n- 🎲 抽奖系统 - 多种抽奖玩法\n- 🧧 红包系统 - 群内发积分红包\n- 💰 积分交易 - 用户间积分转账\n- 📊 积分排行 - 查看积分排名\n\n## 👮 管理功能\n\n- ⚙️ 插件管理 - 动态加载/卸载插件\n- 👥 白名单管理 - 控制机器人使用权限\n- 📊 积分管理 - 管理员可调整用户积分\n- 🔄 签到重置 - 重置所有用户签到状态\n\n# 🔌 插件系统\n\nXYBot V2 采用插件化设计,所有功能都以插件形式实现。主要插件包括:\n\n- 👨‍💼 AdminPoint - 积分管理\n- 🔄 AdminSignInReset - 签到重置\n- 🛡️ AdminWhitelist - 白名单管理\n- 🤖 Ai - AI聊天\n- 📊 BotStatus - 机器人状态\n- 📱 GetContact - 获取通讯录\n- 🌤️ GetWeather - 天气查询\n- 🎮 Gomoku - 五子棋游戏\n- 🌅 GoodMorning - 早安问候\n- 📈 Leaderboard - 积分排行\n- 🎲 LuckyDraw - 幸运抽奖\n- 📋 Menu - 菜单系统\n- 🎵 Music - 点歌系统\n- 📰 News - 新闻推送\n- 💱 PointTrade - 积分交易\n- 💰 QueryPoint - 积分查询\n- 🎯 RandomMember - 随机群成员\n- 🖼️ RandomPicture - 随机图片\n- 🧧 RedPacket - 红包系统\n- ✍️ SignIn - 每日签到\n- ✈️ Warthunder - 战争雷霆查询\n\n# 🚀 部署说明\n\n## 💻 Python部署\n\n### 🪟 Windows部署\n\n#### 1. 环境准备\n\n- 安装 Python 3.11: https://www.python.org/downloads/release/python-3119/\n- 安装 ffmpeg: 从[ffmpeg官网](https://www.ffmpeg.org/download.html)下载并添加到环境变量\n- 安装 Redis: 从[Redis](https://github.com/tporadowski/redis/releases/tag/v5.0.14.1)下载并启动服务\n\n#### 2. 安装项目\n\n```bash\ngit clone https://github.com/HenryXiaoYang/XYBotV2.git\ncd XYBotV2\npython -m venv venv\n.\\venv\\Scripts\\activate\npip install -r requirements.txt\n```\n\n#### 3. 启动机器人\n\n```bash\nstart redis-server\npython app.py\n```\n\n### 🐧 Linux部署\n\n#### 1. 环境准备\n\n```bash\nsudo apt update\nsudo apt install python3.11 python3.11-venv redis-server ffmpeg\nsudo systemctl start redis\nsudo systemctl enable redis\n```\n\n#### 2. 安装项目\n\n```bash\ngit clone https://github.com/HenryXiaoYang/XYBotV2.git\ncd XYBotV2\npython3.11 -m venv venv\nsource venv/bin/activate\npip install -r requirements.txt\n```\n\n#### 3. 启动机器人\n\n```bash\npython app.py\n```\n\n### 🌌 无WebUI简单启动\n\n如果你不需要WebUI界面，可以直接使用bot.py：\n\n```bash\npython bot.py\n```\n\n## ⚙️ 配置说明\n\n- 主配置: main_config.toml\n- 插件配置: plugins/all_in_one_config.toml\n\n这几个插件需要配置API密钥:\n- 🤖 Ai\n- 🌤️ GetWeather\n\n## ❓ 常见问题\n\n1. 与网络相关的报错\n   - 检查网络连接\n   - 关闭代理软件\n   - 重启XYBot和Redis\n\n2. `正在运行`相关的报错\n   - 将占用9000端口的进程结束\n\n3. 无法访问Web界面\n   - 确保9999端口已开放\n   - 配置防火墙允许9999端口\n\n# 💻 代码提交\n\n提交代码时请使用 `feat: something` 作为说明，支持的标识如下:\n\n- `feat` 新功能(feature)\n- `fix` 修复bug\n- `docs` 文档(documentation)\n- `style` 格式(不影响代码运行的变动)\n- `ref` 重构(即不是新增功能，也不是修改bug的代码变动)\n- `perf` 性能优化(performance)\n- `test` 增加测试\n- `chore` 构建过程或辅助工具的变动\n- `revert` 撤销\n"
  },
  {
    "path": "WebUI/__init__.py",
    "content": "import logging\nimport os\nfrom datetime import datetime\nfrom pathlib import Path\nfrom typing import Dict, Tuple, Union\n\nfrom flask import Flask, redirect, url_for\nfrom flask_login import LoginManager\nfrom flask_session import Session\nfrom flask_socketio import SocketIO\nfrom loguru import logger\n\n\nclass InterceptHandler(logging.Handler):\n    def emit(self, record: logging.LogRecord) -> None:\n        logger_opt = logger.opt(depth=6, exception=record.exc_info)\n        logger_opt.log('WEBUI', record.getMessage())\n\n\ndef _configure_logging(app: Flask) -> None:\n    # 清除Flask默认日志处理器并配置使用loguru\n    app.logger.handlers = []\n    app.logger.propagate = False\n    app.logger.addHandler(InterceptHandler())\n\n    # 同样为werkzeug日志配置loguru\n    werkzeug_logger = logging.getLogger('werkzeug')\n    werkzeug_logger.handlers = []\n    werkzeug_logger.propagate = False\n    werkzeug_logger.addHandler(InterceptHandler())\n\ndef _setup_instance_directories(app: Flask) -> None:\n    try:\n        os.makedirs(app.instance_path, exist_ok=True)\n        os.makedirs(app.config['SESSION_FILE_DIR'], exist_ok=True)\n        logger.log('WEBUI', f\"已创建实例目录: {app.instance_path}\")\n    except OSError as e:\n        logger.log('WEBUI', f\"创建实例目录失败: {e}\")\n\n\ndef create_app() -> Tuple[Flask, SocketIO]:\n    \"\"\"创建并配置Flask应用实例及SocketIO实例\n        \n    Returns:\n        tuple: 包含配置好的Flask应用实例和SocketIO实例\n    \"\"\"\n    # 创建Flask应用，设置静态文件和模板路径\n    app = Flask(__name__,\n                instance_relative_config=True,\n                static_folder='static',\n                template_folder='templates')\n    # 配置日志系统\n    _configure_logging(app)\n\n    logger.log('WEBUI', \"正在初始化XYBotV2 WebUI应用...\")\n\n    # 加载配置\n    app.config.from_pyfile(Path(__file__).resolve().parent / 'config.py')\n    logger.log('WEBUI', \"已加载WEBUI配置\")\n\n    # 确保实例文件夹存在\n    _setup_instance_directories(app)\n\n\n    # 初始化Flask-Session\n    Session(app)\n\n    # 初始化Flask-Login\n    login_manager = LoginManager()\n    login_manager.init_app(app)\n    login_manager.login_view = 'auth.login'\n\n    # 注册模板过滤器\n    from .utils.template_filters import register_template_filters\n    register_template_filters(app)\n\n    # 注册蓝图\n    from .routes import register_blueprints\n    register_blueprints(app)\n    logger.log('WEBUI', \"已注册路由蓝图，模板过滤器\")\n\n    # 初始化WebSocket服务\n    from .services.websocket_service import socketio, init_websocket\n\n    # 配置socketio\n    socketio_config = {\n        'cors_allowed_origins': '*',  # 允许的跨域来源，生产环境应该更严格\n        'async_mode': 'eventlet',  # 使用eventlet作为异步模式\n        'logger': False,  # 禁用socketio日志\n        'engineio_logger': False  # 禁用engineio日志\n    }\n\n    # 初始化socketio\n    socketio.init_app(app, **socketio_config)\n    logger.log('WEBUI', \"已初始化会话管理，用户认证系统，SocketIO服务\")\n\n    # 启动WebSocket服务\n    init_websocket()\n    logger.log('WEBUI', \"已启动WebSocket日志监控\")\n\n    # 注册全局上下文处理器\n    @app.context_processor\n    def inject_global_vars() -> Dict[str, Union[str, datetime]]:\n        \"\"\"为所有模板注入全局变量\n        \n        Returns:\n            包含全局变量的字典\n        \"\"\"\n        return {\n            'app_name': 'XYBotV2 WebUI',\n            'version': '1.0.0',\n            'now': datetime.now()\n        }\n\n    # 定义用户加载函数\n    @login_manager.user_loader\n    def load_user(user_id):\n        pass\n\n    # 简单的首页路由（重定向到概览页）\n    @app.route('/')\n    def index():\n        \"\"\"应用首页路由处理，重定向到概览页\"\"\"\n        return redirect(url_for('overview.index'))\n\n    logger.log('WEBUI', \"XYBotV2 WebUI应用初始化完成\")\n    return app, socketio\n"
  },
  {
    "path": "WebUI/common/bot_bridge.py",
    "content": "import asyncio\nimport os\nimport sys\nfrom pathlib import Path\nfrom typing import List, Dict, Any, Optional\n\nfrom loguru import logger\n\nfrom WebUI.utils.singleton import Singleton\n# 引入键值数据库\nfrom database.keyvalDB import KeyvalDB\nfrom utils.plugin_manager import PluginManager\n\n# 确保可以导入根目录模块\nROOT_DIR = Path(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))\nsys.path.insert(0, str(ROOT_DIR))\n\n# 键值数据库键名常量\nKEY_MESSAGE_COUNT = \"bot:stats:message_count\"\nKEY_USER_COUNT = \"bot:stats:user_count\"\nKEY_LOG_POSITION = \"bot:logs:last_position\"\n\n\ndef get_or_create_eventloop():\n    \"\"\"\n    获取当前线程的事件循环，如果不存在则创建一个新的\n\n    返回:\n        asyncio.AbstractEventLoop: 事件循环对象\n    \"\"\"\n    try:\n        # 尝试获取当前线程的事件循环\n        loop = asyncio.get_event_loop()\n    except RuntimeError:\n        # 如果当前线程没有事件循环，则创建一个新的\n        loop = asyncio.new_event_loop()\n        asyncio.set_event_loop(loop)\n\n    return loop\n\n\nclass BotBridge(metaclass=Singleton):\n    def __init__(self):\n        # 个人资料数据\n        self.avatar_url = \"\"\n        self.nickname = \"\"\n        self.wxid = \"\"\n        self.alias = \"\"\n        self.is_running = False\n        self.start_time = float(0)\n\n        # 缓存数据\n        self._cache = {}\n        self._cache_time = 0\n        self._cache_ttl = 5  # 缓存有效期（秒）\n\n        # 存储正在运行的任务\n        self._tasks = []\n\n        # 初始化插件管理器\n        self.plugin_manager = PluginManager()\n\n        # 配置目录\n        self.config_dir = ROOT_DIR / 'plugins'\n\n        # 初始化数据库\n        self._db = KeyvalDB()\n\n        # 异步初始化数据库\n        loop = get_or_create_eventloop()\n        loop.run_until_complete(self._db.initialize())\n\n    async def get_message_count(self):\n        \"\"\"\n        获取接收消息数量\n        \"\"\"\n        try:\n            result = await self._db.get(KEY_MESSAGE_COUNT)\n            count = int(result) if result is not None else 0\n\n            return count\n        except Exception as e:\n            logger.log('WEBUI', f\"获取消息计数失败: {str(e)}\")\n            return 0\n\n    async def increment_message_count(self, amount=1):\n        \"\"\"\n        增加消息计数\n        \"\"\"\n        try:\n            current = await self.get_message_count()\n            new_count = current + amount\n            await self._db.set(KEY_MESSAGE_COUNT, str(new_count))\n\n            return True\n        except Exception as e:\n            logger.log('WEBUI', f\"增加消息计数失败: {str(e)}\")\n            return False\n\n    async def get_user_count(self):\n        \"\"\"\n        获取用户数量\n        \"\"\"\n        try:\n            result = await self._db.get(KEY_USER_COUNT)\n            count = int(result) if result is not None else 0\n\n            return count\n        except Exception as e:\n            logger.log('WEBUI', f\"获取用户计数失败: {str(e)}\")\n            return 0\n\n    async def increment_user_count(self, amount=1):\n        \"\"\"\n        增加用户计数\n        \"\"\"\n        try:\n            current = await self.get_user_count()\n            new_count = current + amount\n            await self._db.set(KEY_USER_COUNT, str(new_count))\n\n            return True\n        except Exception as e:\n            logger.log('WEBUI', f\"增加用户计数失败: {str(e)}\")\n            return False\n\n    async def get_start_time(self):\n        \"\"\"\n        获取机器人启动时间\n        \"\"\"\n        try:\n            return self.start_time\n        except Exception as e:\n            logger.log('WEBUI', f\"获取启动时间失败: {str(e)}\")\n            return 0\n\n    async def save_log_position(self, position):\n        \"\"\"\n        保存日志读取位置到数据库\n        \"\"\"\n        try:\n            await self._db.set(KEY_LOG_POSITION, str(position))\n            return True\n        except Exception as e:\n            logger.log('WEBUI', f\"保存日志位置失败: {str(e)}\")\n            return False\n\n    async def get_log_position(self):\n        \"\"\"\n        获取日志读取位置\n        \"\"\"\n        try:\n            pos = await self._db.get(KEY_LOG_POSITION)\n            return int(pos) if pos is not None else 0\n        except Exception as e:\n            logger.log('WEBUI', f\"获取日志位置失败: {str(e)}\")\n            return 0\n\n    def save_profile(self, avatar_url: str = \"\", nickname: str = \"\", wxid: str = \"\", alias: str = \"\"):\n        \"\"\"保存个人资料信息\"\"\"\n        self.avatar_url = avatar_url\n        self.nickname = nickname\n        self.wxid = wxid\n        self.alias = alias\n\n    def get_profile(self):\n        \"\"\"获取个人资料信息\"\"\"\n        if self.is_running:\n            return {\n                \"avatar\": self.avatar_url,  # 注意这里改为avatar以匹配前端期望\n                \"nickname\": self.nickname,\n                \"wxid\": self.wxid,\n                \"alias\": self.alias\n            }\n        else:\n            return {\n                \"avatar\": \"\",\n                \"nickname\": \"\",\n                \"wxid\": \"\",\n                \"alias\": \"\"\n            }\n\n    def _create_task(self, coro):\n        loop = get_or_create_eventloop()\n        task = loop.create_task(coro)\n        self._tasks.append(task)\n\n        # 添加完成回调，完成后从列表移除\n        def _done_callback(t):\n            if t in self._tasks:\n                self._tasks.remove(t)\n\n            # 检查是否有异常\n            if t.exception() is not None:\n                logger.log('WEBUI', f\"任务执行异常: {t.exception()}\")\n\n        task.add_done_callback(_done_callback)\n        return task\n\n    def get_all_plugins(self) -> List[Dict[str, Any]]:\n        \"\"\"\n        获取所有插件信息\n        \n        返回:\n            List[Dict[str, Any]]: 插件信息列表\n        \"\"\"\n        try:\n            # 使用同步方式执行异步操作，避免coroutine未等待问题\n            loop = get_or_create_eventloop()\n            if loop.is_running():\n                # 如果循环正在运行，可能是在事件循环内调用，创建任务\n                refresh_task = self._create_task(self.plugin_manager.refresh_plugins())\n                # 但注意这种情况下，操作可能尚未完成\n            else:\n                # 否则阻塞等待完成\n                loop.run_until_complete(self.plugin_manager.refresh_plugins())\n\n            plugins = self.plugin_manager.get_plugin_info()\n\n            # 格式化插件信息\n            formatted_plugins = []\n            for plugin in plugins:\n                directory = plugin.get(\"directory\", \"unknown\")\n                try:\n                    dir_path = ROOT_DIR / 'plugins' / directory\n                    rel_path = dir_path.relative_to(ROOT_DIR).as_posix()\n                except (ValueError, TypeError):\n                    rel_path = directory\n\n                formatted_plugin = {\n                    \"name\": plugin[\"name\"],\n                    \"description\": plugin[\"description\"],\n                    \"author\": plugin[\"author\"],\n                    \"version\": plugin[\"version\"],\n                    \"enabled\": plugin[\"enabled\"],\n                    \"directory\": rel_path\n                }\n                formatted_plugins.append(formatted_plugin)\n\n            return formatted_plugins\n        except Exception as e:\n            logger.log('WEBUI', f\"获取插件信息出错: {str(e)}\")\n            return []\n\n    def get_plugin_details(self, plugin_name: str) -> Optional[Dict[str, Any]]:\n        \"\"\"获取指定插件的详细信息\"\"\"\n        try:\n            plugin_info = self.plugin_manager.get_plugin_info(plugin_name)\n            if not plugin_info:\n                return None\n\n            # 获取相对路径\n            directory = plugin_info.get(\"directory\", \"unknown\")\n            try:\n                dir_path = ROOT_DIR / 'plugins' / directory\n                rel_path = dir_path.relative_to(ROOT_DIR).as_posix()\n            except (ValueError, TypeError):\n                rel_path = directory\n\n            return {\n                \"name\": plugin_info[\"name\"],\n                \"description\": plugin_info[\"description\"],\n                \"author\": plugin_info[\"author\"],\n                \"version\": plugin_info[\"version\"],\n                \"enabled\": plugin_info[\"enabled\"],\n                \"directory\": rel_path\n            }\n        except Exception as e:\n            logger.log('WEBUI', f\"获取插件详情出错: {str(e)}\")\n            return None\n\n    async def enable_plugin(self, plugin_name: str) -> bool:\n        \"\"\"启用插件\"\"\"\n        if not self.is_running:\n            raise Exception(\"机器人未运行\")\n        try:\n            return await self.plugin_manager.load_plugin(plugin_name)\n        except Exception as e:\n            logger.log('WEBUI', f\"启用插件出错: {str(e)}\")\n            return False\n\n    async def disable_plugin(self, plugin_name: str) -> bool:\n        \"\"\"禁用插件\"\"\"\n        if not self.is_running:\n            raise Exception(\"机器人未运行\")\n        try:\n            return await self.plugin_manager.unload_plugin(plugin_name)\n        except Exception as e:\n            logger.log('WEBUI', f\"禁用插件出错: {str(e)}\")\n            return False\n\n    async def reload_plugin(self, plugin_name: str) -> bool:\n        \"\"\"重新加载插件\"\"\"\n        try:\n            return await self.plugin_manager.reload_plugin(plugin_name)\n        except Exception as e:\n            logger.log('WEBUI', f\"重载插件出错: {str(e)}\")\n            return False\n\n\n# 创建全局实例\nbot_bridge = BotBridge()\n"
  },
  {
    "path": "WebUI/config.py",
    "content": "import os\nimport tomllib\nfrom pathlib import Path\n\n# 项目根目录\nBASE_DIR = Path(__file__).resolve().parent.parent\n\n# 尝试读取主配置文件\ntry:\n    with open(BASE_DIR / 'main_config.toml', 'rb') as f:\n        toml_config = tomllib.load(f)\n        WEBUI_CONFIG = toml_config.get('WebUI', {})\nexcept (FileNotFoundError, tomllib.TOMLDecodeError):\n    WEBUI_CONFIG = {}\n\n# Flask应用配置\nSECRET_KEY = WEBUI_CONFIG.get(\"flask-secret-key\") or os.environ.get('SECRET_KEY', 'HenryXiaoYang_XYBotV2')\nDEBUG = WEBUI_CONFIG.get(\"debug\", False)\n\n# 会话配置\nSESSION_TYPE = 'filesystem'\nSESSION_PERMANENT = True\nSESSION_USE_SIGNER = True\nSESSION_FILE_DIR = 'flask_session'\nPERMANENT_SESSION_LIFETIME = int(WEBUI_CONFIG.get('session-timeout', 30)) * 60  # 转换为秒\n\n# 认证相关配置\nADMIN_USERNAME = WEBUI_CONFIG.get('admin-username', 'admin')\nADMIN_PASSWORD = WEBUI_CONFIG.get('admin-password', 'admin123')\n\n# 日志配置\nLOG_LEVEL = 'DEBUG' if DEBUG else 'INFO'\nLOG_FILE = BASE_DIR / 'logs' / 'webui.log'"
  },
  {
    "path": "WebUI/forms/__init__.py",
    "content": ""
  },
  {
    "path": "WebUI/forms/auth_forms.py",
    "content": "from flask_wtf import FlaskForm\nfrom wtforms import StringField, PasswordField, BooleanField, SubmitField\nfrom wtforms.validators import DataRequired\n\n\nclass LoginForm(FlaskForm):\n    \"\"\"登录表单\"\"\"\n    username = StringField('用户名', validators=[DataRequired(message='请输入用户名')])\n    password = PasswordField('密码', validators=[DataRequired(message='请输入密码')])\n    remember_me = BooleanField('记住我')\n    submit = SubmitField('登录')\n"
  },
  {
    "path": "WebUI/routes/__init__.py",
    "content": "def register_blueprints(app):\n    \"\"\"\n    注册所有蓝图到Flask应用\n    \n    参数:\n        app: Flask应用实例\n    \"\"\"\n    # 导入所有蓝图\n    from .auth import auth_bp\n    from .overview import overview_bp\n    from .logs import logs_bp\n    from .config import config_bp\n    from .plugin import plugin_bp\n    from .tools import tools_bp\n    from .file import file_bp\n    from .bot import bot_bp\n    from .explorer import explorer_bp\n    from .about import about_bp\n\n    # 注册蓝图\n    app.register_blueprint(auth_bp)\n    app.register_blueprint(overview_bp)\n    app.register_blueprint(logs_bp)\n    app.register_blueprint(config_bp)\n    app.register_blueprint(plugin_bp)\n    app.register_blueprint(tools_bp)\n    app.register_blueprint(file_bp)\n    app.register_blueprint(bot_bp)\n    app.register_blueprint(explorer_bp)\n    app.register_blueprint(about_bp)\n"
  },
  {
    "path": "WebUI/routes/about.py",
    "content": "from flask import Blueprint, render_template\n\nfrom WebUI.utils.auth_utils import login_required\n\nabout_bp = Blueprint('about', __name__)\n\n\n@about_bp.route('/about')\n@login_required\ndef about():\n    \"\"\"关于页面路由\"\"\"\n    return render_template('about/index.html')\n"
  },
  {
    "path": "WebUI/routes/auth.py",
    "content": "from datetime import datetime, timedelta\n\nfrom flask import Blueprint, render_template, redirect, url_for, session, flash, current_app\n\nfrom WebUI.config import PERMANENT_SESSION_LIFETIME\nfrom WebUI.forms.auth_forms import LoginForm\nfrom WebUI.utils.auth_utils import verify_credentials, login_required\n\n# 创建认证蓝图\nauth_bp = Blueprint('auth', __name__, url_prefix='/auth')\n\n\n@auth_bp.route('/login', methods=['GET', 'POST'])\ndef login():\n    \"\"\"登录页面\"\"\"\n    # 已登录用户直接跳转到首页\n    if session.get('authenticated'):\n        return redirect(url_for('overview.index'))\n\n    form = LoginForm()\n\n    # 表单提交处理\n    if form.validate_on_submit():\n        username = form.username.data\n        password = form.password.data\n\n        if verify_credentials(username, password):\n            # 设置会话信息\n            session['authenticated'] = True\n            session['username'] = username\n            session['login_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')\n\n            # 设置会话超时时间\n            session.permanent = True\n\n            # 如果用户勾选\"记住我\"，延长会话时间\n            if form.remember_me.data:\n                # 延长会话时间为配置的两倍\n                current_app.permanent_session_lifetime = timedelta(seconds=PERMANENT_SESSION_LIFETIME * 2)\n\n            # 获取请求来源并重定向\n            next_url = session.pop('redirect_url', None)\n            if next_url:\n                return redirect(next_url)\n            return redirect(url_for('overview.index'))\n        else:\n            flash('用户名或密码错误', 'danger')\n\n    # GET请求渲染登录页面\n    return render_template('auth/login.html', form=form, now=datetime.now())\n\n\n@auth_bp.route('/logout')\n@login_required\ndef logout():\n    \"\"\"用户登出\"\"\"\n    # 清除会话信息\n    session.clear()\n    flash('您已成功退出登录', 'success')\n    return redirect(url_for('auth.login'))\n"
  },
  {
    "path": "WebUI/routes/bot.py",
    "content": "from flask import Blueprint, jsonify\n\nfrom WebUI.services.bot_service import bot_service\nfrom WebUI.utils.auth_utils import login_required\n\nbot_bp = Blueprint('bot', __name__, url_prefix='/bot')\n\n\n@bot_bp.route('/api/start', methods=['POST'])\n@login_required\ndef api_start_bot():\n    \"\"\"启动机器人API\"\"\"\n    success = bot_service.start_bot()\n    return jsonify({\n        'success': success,\n        'message': '机器人启动成功' if success else '机器人启动失败'\n    })\n\n\n@bot_bp.route('/api/stop', methods=['POST'])\n@login_required\ndef api_stop_bot():\n    \"\"\"停止机器人API\"\"\"\n    success = bot_service.stop_bot()\n    return jsonify({\n        'success': success,\n        'message': '机器人停止成功' if success else '机器人停止失败'\n    })\n\n\n@bot_bp.route('/api/status', methods=['GET'])\n@login_required\ndef api_get_bot_status():\n    \"\"\"获取机器人状态API\"\"\"\n    status = bot_service.get_status()\n    return jsonify(status)\n"
  },
  {
    "path": "WebUI/routes/config.py",
    "content": "import traceback\n\nfrom flask import Blueprint, request, jsonify, render_template, current_app\n\nfrom WebUI.services.config_service import config_service\nfrom WebUI.utils.auth_utils import login_required\n\n# 创建蓝图\nconfig_bp = Blueprint('config', __name__, url_prefix='/config')\n\n\n@config_bp.route('/', methods=['GET'])\n@login_required\ndef configs_page():\n    \"\"\"设置管理页面\"\"\"\n    current_app.logger.info(\"访问设置管理页面\")\n    return render_template('config/index.html')\n\n\n@config_bp.route('/api/config', methods=['GET'])\n@login_required\ndef get_config():\n    \"\"\"\n    获取配置\n    \n    返回:\n        JSON: 配置数据\n    \"\"\"\n    try:\n        current_app.logger.info(\"开始获取所有配置\")\n        config = config_service.get_config()\n        current_app.logger.info(f\"成功获取配置，包含 {len(config)} 个配置节\")\n\n        return jsonify({\n            \"code\": 0,\n            \"msg\": \"成功\",\n            \"data\": config\n        })\n    except Exception as e:\n        error_detail = traceback.format_exc()\n        current_app.logger.error(f\"获取配置失败: {str(e)}\")\n        current_app.logger.error(f\"异常详情: {error_detail}\")\n\n        return jsonify({\n            \"code\": 500,\n            \"msg\": f\"获取配置失败: {str(e)}\",\n            \"error_detail\": error_detail\n        })\n\n\n@config_bp.route('/api/schema', methods=['GET'])\n@login_required\ndef get_schema():\n    \"\"\"\n    获取表单架构\n    \n    返回:\n        JSON: 表单架构\n    \"\"\"\n    try:\n        current_app.logger.info(\"开始获取表单架构\")\n        schema = config_service.get_form_schema()\n        current_app.logger.info(f\"成功获取表单架构，包含 {len(schema)} 个配置节\")\n\n        return jsonify({\n            \"code\": 0,\n            \"msg\": \"成功\",\n            \"data\": schema\n        })\n    except Exception as e:\n        error_detail = traceback.format_exc()\n        current_app.logger.error(f\"获取表单架构失败: {str(e)}\")\n        current_app.logger.error(f\"异常详情: {error_detail}\")\n\n        return jsonify({\n            \"code\": 500,\n            \"msg\": f\"获取表单架构失败: {str(e)}\",\n            \"error_detail\": error_detail\n        })\n\n\n@config_bp.route('/api/schemas', methods=['GET'])\n@login_required\ndef get_schemas():\n    \"\"\"\n    获取表单架构（别名）\n    \n    返回:\n        JSON: 表单架构\n    \"\"\"\n    current_app.logger.info(\"转发请求到 /api/schema\")\n    # 复用get_schema函数的逻辑\n    return get_schema()\n\n\n@config_bp.route('/api/config', methods=['POST'])\n@login_required\ndef save_config():\n    \"\"\"\n    保存配置\n    \n    请求体:\n        JSON: 配置数据\n        \n    返回:\n        JSON: 操作结果\n    \"\"\"\n    try:\n        current_app.logger.info(\"开始保存所有配置\")\n\n        # 获取请求体中的JSON数据\n        config_data = request.json\n        current_app.logger.info(f\"接收到配置数据: {config_data}\")\n\n        if not config_data:\n            current_app.logger.warning(\"保存配置失败: 请求数据为空\")\n            return jsonify({\n                \"code\": 400,\n                \"msg\": \"请求数据为空\",\n                \"data\": None\n            })\n\n        # 验证配置\n        current_app.logger.info(\"验证配置数据\")\n        valid, errors = config_service.validate_config(config_data)\n        if not valid:\n            current_app.logger.warning(f\"配置验证失败: {errors}\")\n            return jsonify({\n                \"code\": 400,\n                \"msg\": \"配置验证失败\",\n                \"data\": errors\n            })\n\n        # 保存配置\n        current_app.logger.info(\"开始保存配置到文件\")\n        result = config_service.save_config(config_data)\n\n        if result:\n            current_app.logger.info(\"配置保存成功\")\n            return jsonify({\n                \"code\": 0,\n                \"msg\": \"配置保存成功\",\n                \"data\": None\n            })\n        else:\n            current_app.logger.warning(\"配置保存失败: 保存操作返回失败\")\n            return jsonify({\n                \"code\": 500,\n                \"msg\": \"配置保存失败\",\n                \"data\": None\n            })\n    except Exception as e:\n        error_detail = traceback.format_exc()\n        current_app.logger.error(f\"保存配置失败: {str(e)}\")\n        current_app.logger.error(f\"异常详情: {error_detail}\")\n\n        return jsonify({\n            \"code\": 500,\n            \"msg\": f\"保存配置失败: {str(e)}\",\n            \"error_detail\": error_detail\n        })\n\n\n@config_bp.route('/api/config/<config_name>', methods=['POST'])\n@login_required\ndef save_specific_config(config_name: str):\n    \"\"\"\n    保存特定配置\n    \n    参数:\n        config_name (str): 配置名称\n        \n    请求体:\n        JSON: 配置数据\n        \n    返回:\n        JSON: 操作结果\n    \"\"\"\n    try:\n        current_app.logger.info(f\"开始保存特定配置节: {config_name}\")\n\n        # 获取请求体中的JSON数据\n        config_data = request.json\n        current_app.logger.info(f\"接收到配置数据: {config_data}\")\n\n        if not config_data:\n            current_app.logger.warning(f\"保存配置节 {config_name} 失败: 请求数据为空\")\n            return jsonify({\n                \"code\": 400,\n                \"msg\": \"请求数据为空\",\n                \"data\": None\n            })\n\n        # 创建包含特定配置的完整配置数据结构\n        full_config = {config_name: config_data}\n\n        # 验证配置\n        current_app.logger.info(f\"验证配置节 {config_name} 数据\")\n        valid, errors = config_service.validate_config(full_config)\n        if not valid:\n            current_app.logger.warning(f\"配置节 {config_name} 验证失败: {errors}\")\n            return jsonify({\n                \"code\": 400,\n                \"msg\": \"配置验证失败\",\n                \"data\": errors\n            })\n\n        # 保存配置\n        current_app.logger.info(f\"开始保存配置节 {config_name} 到文件\")\n        result = config_service.save_config(full_config)\n\n        if result:\n            current_app.logger.info(f\"配置节 {config_name} 保存成功\")\n            return jsonify({\n                \"code\": 0,\n                \"msg\": \"配置保存成功\",\n                \"data\": None\n            })\n        else:\n            current_app.logger.warning(f\"配置节 {config_name} 保存失败: 保存操作返回失败\")\n            return jsonify({\n                \"code\": 500,\n                \"msg\": \"配置保存失败\",\n                \"data\": None\n            })\n    except Exception as e:\n        error_detail = traceback.format_exc()\n        current_app.logger.error(f\"保存配置节 {config_name} 失败: {str(e)}\")\n        current_app.logger.error(f\"异常详情: {error_detail}\")\n\n        return jsonify({\n            \"code\": 500,\n            \"msg\": f\"保存配置失败: {str(e)}\",\n            \"error_detail\": error_detail\n        })\n"
  },
  {
    "path": "WebUI/routes/explorer.py",
    "content": "from flask import Blueprint, render_template, request\n\nfrom WebUI.utils.auth_utils import login_required\n\nexplorer_bp = Blueprint('explorer', __name__, url_prefix='/explorer')\n\n\n@explorer_bp.route('/')\n@login_required\n# 暂时注释掉 @login_required\ndef index():\n    \"\"\"文件浏览器主页\"\"\"\n    path = request.args.get('path', '')\n    return render_template('explorer/index.html',\n                           page_title='文件浏览器',\n                           initial_path=path)\n\n\n@explorer_bp.route('/view/<path:file_path>')\n@login_required\n# 暂时注释掉 @login_required\ndef view_file(file_path):\n    \"\"\"查看文件内容\"\"\"\n    return render_template('explorer/view.html',\n                           file_path=file_path,\n                           page_title='文件查看')\n"
  },
  {
    "path": "WebUI/routes/file.py",
    "content": "from pathlib import Path\n\nfrom flask import Blueprint, render_template, jsonify, request\nfrom loguru import logger\nfrom werkzeug.exceptions import BadRequest\n\nfrom WebUI.services.file_service import file_service, ROOT_DIR\nfrom WebUI.utils.auth_utils import login_required\n\nfile_bp = Blueprint('file', __name__, url_prefix='/file')\n\n\ndef normalize_path(rel_path: str) -> Path:\n    \"\"\"安全路径标准化方法（增加日志目录例外）\"\"\"\n    try:\n        # 允许直接访问日志目录\n        if rel_path.strip().lower() == 'logs':\n            log_path = ROOT_DIR / 'logs'\n            if log_path.exists():\n                return log_path\n\n        # 空路径处理\n        if not rel_path.strip():\n            return ROOT_DIR\n\n        # 路径分解和清理\n        components = []\n        for part in rel_path.split('/'):\n            part = part.strip()\n            if not part or part == '.':\n                continue\n            if part == '..':\n                if components:\n                    components.pop()\n                continue\n            components.append(part)\n\n        safe_path = ROOT_DIR.joinpath(*components)\n        resolved_path = safe_path.resolve()\n\n        # 最终安全性检查\n        if not resolved_path.is_relative_to(ROOT_DIR.resolve()):\n            raise BadRequest(\"非法路径访问\")\n\n        return resolved_path\n    except Exception as e:\n        logger.log('WEBUI', f\"路径标准化错误: {str(e)}\")\n        raise BadRequest(\"路径处理错误\")\n\n\n@file_bp.route('/api/list')\n@login_required\ndef api_list_files():\n    \"\"\"获取目录文件列表API\"\"\"\n    try:\n        raw_path = request.args.get('path', '')\n\n        # 对于logs、plugins等特殊目录，提供额外处理\n        if raw_path.startswith('plugins/') or raw_path == 'plugins':\n            # 确保plugins目录存在\n            plugins_dir = ROOT_DIR / 'plugins'\n            if not plugins_dir.exists():\n                return jsonify({'error': '插件目录不存在'}), 404\n\n            # 如果是具体的插件目录\n            if raw_path != 'plugins':\n                plugin_path = ROOT_DIR / raw_path\n                if not plugin_path.exists():\n                    return jsonify({'error': '指定的插件不存在'}), 404\n\n                if not plugin_path.is_dir():\n                    return jsonify({'error': '不是目录'}), 400\n\n            target_path = ROOT_DIR / raw_path\n\n        elif raw_path == 'logs' or raw_path.startswith('logs/'):\n            # 日志目录特殊处理\n            logs_dir = ROOT_DIR / 'logs'\n            if not logs_dir.exists():\n                return jsonify({'error': '日志目录不存在'}), 404\n\n            target_path = ROOT_DIR / raw_path\n\n        else:\n            # 常规路径处理\n            target_path = normalize_path(raw_path)\n\n        if not target_path.exists():\n            return jsonify({\n                'path': raw_path,\n                'files': [],\n                'error': '路径不存在'\n            }), 200  # 返回200但提供错误消息\n\n        if not target_path.is_dir():\n            return jsonify({\n                'path': raw_path,\n                'files': [],\n                'error': '不是目录'\n            }), 200\n\n        files = file_service.list_directory(str(target_path.relative_to(ROOT_DIR)))\n\n        return jsonify({\n            'path': raw_path,\n            'files': files\n        })\n\n    except Exception as e:\n        logger.log('WEBUI', f\"文件列表API错误: {str(e)}\")\n        return jsonify({\n            'path': raw_path or '',\n            'files': [],\n            'error': str(e)\n        }), 200  # 返回200但提供错误消息\n\n\ndef is_safe_path(path: str) -> bool:\n    \"\"\"检查路径是否安全，不包含危险组件\"\"\"\n    return '../' not in path and not path.startswith('/')\n\n\n@file_bp.route('/api/content')\n@login_required\ndef api_file_content():\n    \"\"\"获取文件内容API\"\"\"\n    try:\n        # 获取请求参数\n        rel_path = request.args.get('path', '')\n        start_line = int(request.args.get('start', 0))\n        max_lines = int(request.args.get('max', 1000))\n\n        # 使用normalize_path而不是is_safe_path\n        try:\n            # 特殊处理日志文件\n            if rel_path.startswith('logs/'):\n                file_path = ROOT_DIR / rel_path\n            else:\n                # 使用normalize_path而不是is_safe_path\n                file_path = normalize_path(rel_path)\n\n            if not file_path.exists():\n                return jsonify({\n                    'content': [],\n                    'info': {\n                        'error': '文件不存在',\n                        'size': 0,\n                        'start_line': start_line,\n                        'total_lines': 0\n                    }\n                })\n\n            # 获取文件内容\n            content, info = file_service.get_file_content(\n                str(file_path.relative_to(ROOT_DIR)),\n                start_line,\n                max_lines\n            )\n            return jsonify({\n                'content': content,\n                'info': info\n            })\n        except Exception as e:\n            logger.log('WEBUI', f\"读取文件失败: {str(e)}\")\n            return jsonify({\n                'content': [],\n                'info': {\n                    'error': f'读取文件失败: {str(e)}',\n                    'path': rel_path,\n                    'size': 0,\n                    'start_line': start_line,\n                    'total_lines': 0\n                }\n            })\n    except Exception as e:\n        logger.log('WEBUI', f\"处理文件内容请求失败: {str(e)}\")\n        return jsonify({\n            'content': [],\n            'info': {\n                'error': f'处理请求失败: {str(e)}'\n            }\n        })\n\n\n@file_bp.route('/api/search')\n@login_required\ndef api_search_in_file():\n    \"\"\"在文件中搜索内容API\"\"\"\n    try:\n        path = request.args.get('path', '')\n        query = request.args.get('query', '')\n\n        if not path or not query:\n            return jsonify({'error': '路径和查询参数不能为空'}), 400\n\n        results = file_service.search_in_file(path, query)\n        return jsonify(results)\n    except BadRequest as e:\n        logger.log('WEBUI', f\"搜索文件参数错误: {str(e)}\")\n        return jsonify({'error': str(e)}), 400\n    except Exception as e:\n        logger.log('WEBUI', f\"搜索文件失败: {str(e)}\")\n        return jsonify({'error': str(e)}), 500\n\n\n@file_bp.route('/api/save', methods=['POST'])\n@login_required\ndef api_save_file():\n    \"\"\"保存文件内容API\"\"\"\n    try:\n        data = request.get_json()\n\n        if not data:\n            return jsonify({'error': '请求数据不能为空'}), 400\n\n        path = data.get('path', '')\n        content = data.get('content', '')\n\n        if not path:\n            return jsonify({'error': '文件路径不能为空'}), 400\n\n        # 检查路径安全性\n        if not is_safe_path(path):\n            logger.log('WEBUI', f\"尝试访问不安全路径: {path}\")\n            return jsonify({'error': '路径不安全'}), 403\n\n        result = file_service.save_file_content(path, content)\n\n        if result:\n            logger.log('WEBUI', f\"文件保存成功: {path}\")\n            return jsonify({'success': True, 'message': '文件保存成功'})\n        else:\n            logger.log('WEBUI', f\"文件保存失败: {path}\")\n            return jsonify({'success': False, 'error': '文件保存失败'}), 500\n    except BadRequest as e:\n        logger.log('WEBUI', f\"保存文件请求错误: {str(e)}\")\n        return jsonify({'error': str(e)}), 400\n    except Exception as e:\n        logger.log('WEBUI', f\"保存文件异常: {str(e)}\")\n        return jsonify({'error': str(e)}), 500\n\n\n@file_bp.route('/view/<path:file_path>')\n@login_required\ndef view_file(file_path):\n    \"\"\"通用文件查看页面\"\"\"\n    logger.log('WEBUI', f\"访问文件查看页面: {file_path}\")\n    context = {\n        'page_title': '文件查看',\n        'file_path': file_path\n    }\n    return render_template('file/view.html', **context)\n"
  },
  {
    "path": "WebUI/routes/logs.py",
    "content": "from flask import Blueprint, render_template\n\nfrom WebUI.utils.auth_utils import login_required\n\n# 创建日志管理蓝图\nlogs_bp = Blueprint('logs', __name__, url_prefix='/logs')\n\n\n@logs_bp.route('/')\n@login_required\ndef index():\n    \"\"\"日志管理首页\"\"\"\n    return render_template('logs/index.html',\n                           page_title='日志管理',\n                           directory='logs')\n"
  },
  {
    "path": "WebUI/routes/overview.py",
    "content": "from flask import Blueprint, render_template, jsonify\nimport asyncio\n\nfrom WebUI.common.bot_bridge import bot_bridge\nfrom WebUI.services.bot_service import bot_service\nfrom WebUI.services.data_service import data_service\nfrom WebUI.utils.auth_utils import login_required\n\n# 创建概览蓝图\noverview_bp = Blueprint('overview', __name__, url_prefix='/overview')\n\n\n@overview_bp.route('/')\n@login_required\ndef index():\n    \"\"\"概览页面首页\"\"\"\n    # 获取初始数据\n    bot_status = bot_service.get_status()\n    metrics = data_service.get_metrics()\n\n    context = {\n        'page_title': '概览',\n        'bot_status': bot_status,\n        'metrics': metrics,\n        'status': 'running' if bot_status.get('running', False) else 'stopped'\n    }\n\n    return render_template('overview/index.html', **context)\n\n\n@overview_bp.route('/api/status')\n@login_required\ndef api_status():\n    \"\"\"获取机器人状态API\"\"\"\n    try:\n        # 获取最新的数据\n        data_service._refresh_cache_data()  # 确保数据是最新的\n        \n        # 获取所有数据\n        context = bot_bridge.get_profile()\n        metrics = data_service.get_metrics()\n        status = bot_service.get_status()\n        \n        # 确保所有数据是可JSON序列化的\n        for k, v in list(metrics.items()):\n            if isinstance(v, (asyncio.Task, asyncio.Future)):\n                try:\n                    # 尝试获取Task的结果\n                    loop = asyncio.get_event_loop()\n                    if v._state == 'PENDING':\n                        metrics[k] = 0  # 如果任务仍在等待，使用默认值\n                    else:\n                        metrics[k] = str(v)  # 安全转换为字符串\n                except Exception:\n                    metrics[k] = 0  # 出错时使用默认值\n        \n        # 确保所有值都是基本数据类型\n        for k, v in list(context.items()):\n            if not isinstance(v, (str, int, float, bool, type(None))):\n                context[k] = str(v)\n        \n        # 更新上下文\n        context.update(metrics)\n        context.update(status)\n        \n        return jsonify(context)\n    except Exception as e:\n        # 返回错误信息\n        return jsonify({\n            'error': str(e),\n            'status': 'error'\n        })\n"
  },
  {
    "path": "WebUI/routes/plugin.py",
    "content": "from flask import Blueprint, request, jsonify, render_template\nfrom loguru import logger\n\nfrom WebUI.services.plugin_service import plugin_service\nfrom WebUI.utils.auth_utils import login_required\n\n# 创建蓝图\nplugin_bp = Blueprint('plugin', __name__, url_prefix='/plugin')\n\n\n@plugin_bp.route('/', methods=['GET'])\n@login_required\ndef plugin_page():\n    \"\"\"插件管理页面\"\"\"\n    return render_template('plugin/index.html')\n\n\n@plugin_bp.route('/api/list', methods=['GET'])\n@login_required\ndef get_plugins():\n    \"\"\"\n    获取所有插件列表\n    \n    返回:\n        JSON: 所有插件信息列表\n    \"\"\"\n    try:\n        plugins = plugin_service.get_all_plugins()\n        logger.log(\"WEBUI\", f\"成功获取到 {len(plugins)} 个插件\")\n        return jsonify({\n            \"code\": 0,\n            \"msg\": \"成功\",\n            \"data\": plugins\n        })\n    except Exception as e:\n        logger.log(\"WEBUI\", f\"获取插件列表失败: {str(e)}\")\n        return jsonify({\n            \"code\": 500,\n            \"msg\": f\"获取插件列表失败: {str(e)}\",\n            \"data\": []\n        })\n\n\n@plugin_bp.route('/api/detail/<plugin_name>', methods=['GET'])\n@login_required\ndef get_plugin_detail(plugin_name: str):\n    \"\"\"\n    获取插件详情\n    \n    参数:\n        plugin_name (str): 插件ID\n        \n    返回:\n        JSON: 插件详细信息\n    \"\"\"\n    plugin = plugin_service.get_plugin_details(plugin_name)\n    if not plugin:\n        return jsonify({\n            \"code\": 404,\n            \"msg\": \"插件不存在\",\n            \"data\": None\n        })\n\n    return jsonify({\n        \"code\": 0,\n        \"msg\": \"成功\",\n        \"data\": plugin\n    })\n\n\n@plugin_bp.route('/api/enable/<plugin_name>', methods=['POST'])\n@login_required\ndef enable_plugin(plugin_name: str):\n    \"\"\"\n    启用插件\n    \n    参数:\n        plugin_name (str): 插件ID\n        \n    返回:\n        JSON: 操作结果\n    \"\"\"\n    try:\n        # 使用run_async执行异步操作，而不是直接用asyncio.run\n        result = plugin_service.run_async(plugin_service.enable_plugin(plugin_name))\n        \n        if result:\n            return jsonify({\n                \"code\": 0,\n                \"msg\": \"插件启用成功\",\n                \"data\": None\n            })\n        else:\n            return jsonify({\n                \"code\": 500,\n                \"msg\": \"插件启用失败\",\n                \"data\": None\n            })\n    except Exception as e:\n        logger.log(\"WEBUI\", f\"启用插件失败: {str(e)}\")\n        return jsonify({\n            \"code\": 500,\n            \"msg\": f\"启用插件失败: {str(e)}\",\n            \"data\": None\n        })\n\n\n@plugin_bp.route('/api/disable/<plugin_name>', methods=['POST'])\n@login_required\ndef disable_plugin(plugin_name: str):\n    \"\"\"\n    禁用插件\n    \n    参数:\n        plugin_name (str): 插件ID\n        \n    返回:\n        JSON: 操作结果\n    \"\"\"\n    try:\n        # 使用run_async执行异步操作\n        result = plugin_service.run_async(plugin_service.disable_plugin(plugin_name))\n        \n        if result:\n            return jsonify({\n                \"code\": 0,\n                \"msg\": \"插件禁用成功\",\n                \"data\": None\n            })\n        else:\n            return jsonify({\n                \"code\": 500,\n                \"msg\": \"插件禁用失败\",\n                \"data\": None\n            })\n    except Exception as e:\n        logger.log(\"WEBUI\", f\"禁用插件失败: {str(e)}\")\n        return jsonify({\n            \"code\": 500,\n            \"msg\": f\"禁用插件失败: {str(e)}\",\n            \"data\": None\n        })\n\n\n@plugin_bp.route('/api/reload/<plugin_name>', methods=['POST'])\n@login_required\ndef reload_plugin(plugin_name: str):\n    \"\"\"\n    重新加载插件\n    \n    参数:\n        plugin_name (str): 插件ID\n        \n    返回:\n        JSON: 操作结果\n    \"\"\"\n    try:\n        # 使用run_async执行异步操作\n        result = plugin_service.run_async(plugin_service.reload_plugin(plugin_name))\n        \n        if result:\n            return jsonify({\n                \"code\": 0,\n                \"msg\": \"插件重新加载成功\",\n                \"data\": None\n            })\n        else:\n            return jsonify({\n                \"code\": 500,\n                \"msg\": \"插件重新加载失败\",\n                \"data\": None\n            })\n    except Exception as e:\n        logger.log(\"WEBUI\", f\"重载插件失败: {str(e)}\")\n        return jsonify({\n            \"code\": 500,\n            \"msg\": f\"重载插件失败: {str(e)}\",\n            \"data\": None\n        })\n\n\n@plugin_bp.route('/api/config/<plugin_name>/list', methods=['GET'])\n@login_required\ndef pluginl_list_files(plugin_name: str):\n    \"\"\"\n    获取插件配置\n    \n    参数:\n        plugin_name (str): 插件ID\n        \n    返回:\n        JSON: 插件配置\n    \"\"\"\n    root_key = request.args.get('root', 'logs')\n\n\n@plugin_bp.route('/api/config/<plugin_name>', methods=['POST'])\n@login_required\ndef save_plugin_config(plugin_name: str):\n    \"\"\"\n    保存插件配置\n    \n    参数:\n        plugin_name (str): 插件ID\n        \n    请求体:\n        JSON: 插件配置\n        \n    返回:\n        JSON: 操作结果\n    \"\"\"\n    # 获取请求体中的JSON数据\n    config_data = request.json\n\n    if not config_data:\n        return jsonify({\n            \"code\": 400,\n            \"msg\": \"请求数据为空\",\n            \"data\": None\n        })\n\n    # 保存配置\n    result = plugin_service.save_plugin_config(plugin_name, config_data)\n\n    if result:\n        # 重载插件以应用新配置\n        try:\n            logger.info(f\"正在重载插件 {plugin_name} 以应用新配置...\")\n            reload_result = plugin_service.run_async(plugin_service.reload_plugin(plugin_name))\n            \n            if reload_result:\n                logger.info(f\"插件 {plugin_name} 重载成功\")\n            else:\n                logger.warning(f\"插件 {plugin_name} 重载失败\")\n                \n            return jsonify({\n                \"code\": 0,\n                \"msg\": \"配置保存成功\",\n                \"data\": None\n            })\n        except Exception as e:\n            logger.error(f\"插件重载失败: {str(e)}\")\n            return jsonify({\n                \"code\": 0,\n                \"msg\": \"配置已保存，但插件重载失败\",\n                \"data\": None\n            })\n    else:\n        return jsonify({\n            \"code\": 500,\n            \"msg\": \"配置保存失败\",\n            \"data\": None\n        })\n"
  },
  {
    "path": "WebUI/routes/tools.py",
    "content": "from flask import Blueprint, render_template, jsonify, current_app\n\nfrom WebUI.services.tool_service import execute_tool, get_tools_list\nfrom WebUI.utils.auth_utils import login_required\n\n# 创建工具箱蓝图\ntools_bp = Blueprint('tools', __name__, url_prefix='/tools')\n\n\n@tools_bp.route('/')\n@login_required\ndef index():\n    \"\"\"工具箱页面首页\"\"\"\n    context = {\n        'page_title': '工具箱',\n    }\n    return render_template('tools/index.html', **context)\n\n\n@tools_bp.route('/api/list')\n@login_required\ndef list_tools():\n    \"\"\"获取工具列表\"\"\"\n    try:\n        tools = get_tools_list()\n        return jsonify({\n            'code': 0,\n            'msg': '获取成功',\n            'data': tools\n        })\n    except Exception as e:\n        current_app.logger.error(f\"获取工具列表失败: {str(e)}\")\n        return jsonify({\n            'code': 1,\n            'msg': f'获取工具列表失败: {str(e)}'\n        })\n\n\n@tools_bp.route('/api/execute/<tool_id>', methods=['POST'])\n@login_required\ndef execute_tool_api(tool_id):\n    \"\"\"执行工具\"\"\"\n    try:\n        # 执行工具\n        result = execute_tool(tool_id)\n\n        return jsonify({\n            'code': 0,\n            'msg': '执行成功',\n            'data': result\n        })\n    except Exception as e:\n        error_msg = f\"执行工具 {tool_id} 失败: {str(e)}\"\n        current_app.logger.error(error_msg)\n\n        return jsonify({\n            'code': 1,\n            'msg': error_msg\n        })\n"
  },
  {
    "path": "WebUI/services/bot_service.py",
    "content": "import asyncio\nimport os\nimport sys\nimport threading\nimport time\nimport traceback\nfrom pathlib import Path\nfrom typing import Optional, Dict, Any, Union\n\nfrom loguru import logger\n\nfrom WebUI.common.bot_bridge import bot_bridge\nfrom WebUI.utils.singleton import Singleton\n\n# 项目根目录路径\nROOT_DIR = Path(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))\n\n# 导入机器人主函数\nsys.path.append(str(ROOT_DIR))\nfrom bot import run_bot\n\n# 线程本地存储，用于存储每个线程的事件循环\nthread_local = threading.local()\n\n\ndef get_or_create_eventloop() -> asyncio.AbstractEventLoop:\n    try:\n        # 尝试获取当前线程的事件循环\n        loop = asyncio.get_event_loop()\n        # 检查事件循环是否已关闭，已关闭则创建新循环\n        if loop.is_closed():\n            logger.log('WEBUI', f\"检测到线程 {threading.current_thread().name} 的事件循环已关闭，创建新循环\")\n            loop = asyncio.new_event_loop()\n            asyncio.set_event_loop(loop)\n    except RuntimeError:\n        # 如果当前线程没有事件循环，则创建一个新的\n        loop = asyncio.new_event_loop()\n        asyncio.set_event_loop(loop)\n        logger.log('WEBUI', f\"为线程 {threading.current_thread().name} 创建了新的事件循环\")\n\n    return loop\n\n\nclass BotService(metaclass=Singleton):\n    def __init__(self):\n        \"\"\"初始化机器人控制服务\n        \n        设置内部状态变量，用于跟踪机器人运行状态。\n        \"\"\"\n        self._task: Optional[Union[asyncio.Task, asyncio.Future]] = None\n        self._start_time: float = 0\n        self._loop: Optional[asyncio.AbstractEventLoop] = None\n        self._bot_thread: Optional[threading.Thread] = None\n\n    def start_bot(self) -> bool:\n        \"\"\"启动机器人\n        \n        创建并运行机器人主协程，根据当前事件循环状态选择合适的启动方式。\n        如果事件循环未运行，会在新线程中启动事件循环。\n        \n        Returns:\n            bool: 启动是否成功\n        \"\"\"\n        # 如果机器人已在运行，直接返回\n        if self.is_running():\n            logger.log('WEBUI', \"机器人已经在运行中，无需重复启动\")\n            return True\n\n        try:\n            # 获取或创建事件循环\n            loop = get_or_create_eventloop()\n            self._loop = loop\n            logger.log('WEBUI', \"准备启动机器人...\")\n\n            # 在事件循环中创建并运行任务\n            if loop.is_running():\n                # 如果循环已经在运行，使用asyncio.run_coroutine_threadsafe\n                logger.log('WEBUI', \"在现有事件循环中启动机器人\")\n                future = asyncio.run_coroutine_threadsafe(run_bot(), loop)\n                self._task = future\n            else:\n                # 如果循环未运行，在新线程中运行事件循环\n                logger.log('WEBUI', \"创建新的事件循环来启动机器人\")\n                self._task = loop.create_task(run_bot())\n\n                # 在单独的线程中运行事件循环\n                def run_loop():\n                    try:\n                        logger.log('WEBUI', \"事件循环开始运行\")\n                        loop.run_forever()\n                    except Exception as e:\n                        logger.log('WEBUI', f\"事件循环异常: {str(e)}\")\n                        logger.log('WEBUI', traceback.format_exc())\n                    finally:\n                        logger.log('WEBUI', \"事件循环已关闭\")\n\n                self._bot_thread = threading.Thread(target=run_loop, daemon=True)\n                self._bot_thread.start()\n                logger.log('WEBUI', f\"已在后台线程启动事件循环 (线程ID: {self._bot_thread.ident})\")\n\n            self._start_time = time.time()\n            bot_bridge.start_time = self._start_time\n            bot_bridge.is_running = True\n            logger.log('WEBUI', \"机器人启动成功\")\n\n            return True\n        except Exception as e:\n            logger.log('WEBUI', f\"启动机器人失败: {str(e)}\")\n            logger.log('WEBUI', traceback.format_exc())\n            return False\n\n    def stop_bot(self) -> bool:\n        \"\"\"停止机器人\n        \n        取消正在运行的机器人任务，并根据需要停止事件循环。\n        清理相关资源并重置状态。\n        \n        Returns:\n            bool: 停止是否成功\n        \"\"\"\n        if not self.is_running():\n            logger.log('WEBUI', \"机器人未在运行，无需停止\")\n            return True\n\n        try:\n            logger.log('WEBUI', \"开始停止机器人...\")\n\n            # 取消异步任务\n            if self._task:\n                if isinstance(self._task, asyncio.Task) and not self._task.done():\n                    logger.log('WEBUI', \"取消机器人任务\")\n                    self._task.cancel()\n                elif hasattr(self._task, 'cancel'):\n                    logger.log('WEBUI', \"取消机器人Future\")\n                    self._task.cancel()\n\n            # 停止事件循环\n            if self._loop and not self._loop.is_closed() and self._loop.is_running():\n                logger.log('WEBUI', \"停止事件循环\")\n                self._loop.call_soon_threadsafe(self._loop.stop)\n\n            # 重置状态\n            self._task = None\n            self._start_time = 0\n            self._bot_thread = None\n            self._loop = None\n            bot_bridge.start_time = 0\n            bot_bridge.is_running = False\n            logger.log('WEBUI', \"机器人已成功停止\")\n\n            return True\n        except Exception as e:\n            logger.log('WEBUI', f\"停止机器人失败: {str(e)}\")\n            logger.log('WEBUI', traceback.format_exc())\n            return False\n\n    def is_running(self) -> bool:\n        \"\"\"检查机器人是否正在运行\n        \n        通过检查异步任务的状态确定机器人是否在运行。\n        如果任务已完成或出错，会重置状态。\n        \n        Returns:\n            bool: 机器人是否正在运行\n        \"\"\"\n        # 检查异步任务是否正在运行\n        if self._task:\n            if isinstance(self._task, asyncio.Task) and not self._task.done():\n                return True\n            elif hasattr(self._task, 'done') and not self._task.done():\n                return True\n\n        # 如果任务已完成或出错，重置状态\n        if self._task is not None:\n            logger.log('WEBUI', \"检测到机器人任务已完成或出错，重置状态\")\n            self._task = None\n            self._start_time = 0\n            self._loop = None\n            self._bot_thread = None\n            bot_bridge.start_time = 0\n            bot_bridge.is_running = False\n\n        return False\n\n    def get_status(self) -> Dict[str, Any]:\n        \"\"\"获取机器人状态信息\n        \n        收集当前运行状态、进程ID和启动时间等信息。\n        \n        Returns:\n            Dict[str, Any]: 包含状态信息的字典\n        \"\"\"\n        running = self.is_running()\n\n        status = {\n            'running': running,\n            'pid': os.getpid(),\n            'start_time': time.strftime(\"%Y-%m-%d %H:%M:%S\", time.localtime(self._start_time)) if running else 0,\n        }\n\n        return status\n\n\n# 创建机器人控制服务实例\nbot_service = BotService()\n"
  },
  {
    "path": "WebUI/services/config_service.py",
    "content": "import os\nimport re\nimport sys\nimport traceback\nfrom pathlib import Path\nfrom typing import Dict, Any, List, Tuple, Optional, Union\n\nimport tomlkit\nfrom loguru import logger\n\nfrom WebUI.utils.singleton import Singleton\n\n# 确保可以导入根目录模块\nROOT_DIR = Path(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))\nsys.path.insert(0, str(ROOT_DIR))\n\n\nclass ConfigService(metaclass=Singleton):\n    \"\"\"配置服务类，提供配置文件管理功能\n    \n    负责读取、保存和验证XYBotV2系统配置，使用TOML格式存储配置信息。\n    支持字段验证、默认值和表单模式生成，方便Web界面的配置编辑。\n    \"\"\"\n\n    def __init__(self):\n        \"\"\"初始化配置服务\n        \n        设置配置文件路径，定义默认配置结构、字段选项和验证规则。\n        \"\"\"\n        self.config_path = ROOT_DIR / \"main_config.toml\"\n\n        # 默认配置结构\n        self.default_config = {\n            \"WechatAPIServer\": {\n                \"port\": 9000,\n                \"mode\": \"release\",\n                \"redis-host\": \"127.0.0.1\",\n                \"redis-port\": 6379,\n                \"redis-password\": \"\",\n                \"redis-db\": 0\n            },\n            \"XYBot\": {\n                \"version\": \"v1.0.0\",\n                \"ignore-protection\": False,\n                \"XYBotDB-url\": \"sqlite:///database/xybot.db\",\n                \"msgDB-url\": \"sqlite+aiosqlite:///database/message.db\",\n                \"keyvalDB-url\": \"sqlite+aiosqlite:///database/keyval.db\",\n                \"admins\": [\"admin-wxid\"],\n                \"disabled-plugins\": [\"ExamplePlugin\"],\n                \"timezone\": \"Asia/Shanghai\",\n                \"ignore-mode\": \"None\"\n            },\n            \"WebUI\": {\n                \"admin-username\": \"admin\",\n                \"admin-password\": \"admin123\",\n                \"session-timeout\": 30\n            }\n        }\n\n        # 字段选项（用于表单下拉选择）\n        self.field_options = {\n            \"WechatAPIServer.mode\": [\"release\", \"debug\"],\n            \"XYBot.ignore-mode\": [\"None\", \"Whitelist\", \"Blacklist\"]\n        }\n\n        # 字段验证规则\n        self.field_validators = {\n            \"WechatAPIServer.port\": lambda v: isinstance(v, int) and 1 <= v <= 65535,\n            \"WechatAPIServer.redis-port\": lambda v: isinstance(v, int) and 1 <= v <= 65535,\n            \"WebUI.session-timeout\": lambda v: isinstance(v, int) and v > 0\n        }\n\n    def get_config(self) -> Dict[str, Any]:\n        \"\"\"获取完整配置\n        \n        从配置文件读取完整配置，如果文件不存在或读取失败则返回默认配置。\n        \n        Returns:\n            Dict[str, Any]: 配置数据字典\n        \"\"\"\n        if not self.config_path.exists():\n            logger.log('WEBUI', f\"配置文件不存在，将使用默认配置: {self.config_path}\")\n            return self.default_config\n\n        try:\n            with open(self.config_path, \"r\", encoding=\"utf-8\") as f:\n                return tomlkit.parse(f.read())\n        except Exception as e:\n            logger.log('WEBUI', f\"读取配置文件出错: {str(e)}\")\n            return self.default_config\n\n    def get_toml_doc(self) -> Optional[Union[tomlkit.TOMLDocument, dict]]:\n        \"\"\"获取TOML文档对象，保留所有格式和注释\n        \n        与get_config不同，此方法返回原始TOML文档对象，保留所有格式和注释。\n        \n        Returns:\n            Optional[Union[tomlkit.TOMLDocument, dict]]: TOML文档对象或None（如果读取失败）\n        \"\"\"\n        if not self.config_path.exists():\n            logger.log('WEBUI', f\"TOML文档不存在: {self.config_path}\")\n            return None\n\n        try:\n            with open(self.config_path, \"r\", encoding=\"utf-8\") as f:\n                return tomlkit.parse(f.read())\n        except Exception as e:\n            logger.log('WEBUI', f\"读取TOML文档出错: {str(e)}\")\n            return None\n\n    def extract_comments(self) -> Dict[str, str]:\n        \"\"\"从TOML文件中提取注释\n        \n        解析TOML文件，提取每个配置字段的注释，用于生成表单时提供帮助文本。\n        \n        Returns:\n            Dict[str, str]: 字段路径到注释的映射，格式为 {\"section.key\": \"注释内容\"}\n        \"\"\"\n        comments = {}\n\n        try:\n            # 如果文件不存在，直接返回空字典\n            if not self.config_path.exists():\n                logger.log('WEBUI', \"无法提取注释：配置文件不存在\")\n                return comments\n\n            # 读取原始文件内容\n            with open(self.config_path, \"r\", encoding=\"utf-8\") as f:\n                content = f.read()\n\n            # 按行解析并提取注释\n            lines = content.split('\\n')\n            current_section = \"\"\n            standalone_comment_lines = []  # 记录独立注释行的行号\n\n            for i, line in enumerate(lines):\n                line = line.strip()\n\n                # 跳过空行\n                if not line:\n                    continue\n\n                # 检查是否是节定义行\n                section_match = re.match(r'^\\[([^\\]]+)\\]', line)\n                if section_match:\n                    current_section = section_match.group(1)\n                    continue\n\n                # 记录独立的注释行（不紧随在键值对后面的注释）\n                if line.startswith('#') and (\n                        i == 0 or not lines[i - 1].strip() or lines[i - 1].strip().startswith('#')):\n                    standalone_comment_lines.append(i)\n                    continue\n\n                # 解析带注释的键值对（key = value # comment）\n                kv_comment_match = re.match(r'^([^=]+)=([^#]*)#(.+)$', line)\n                if kv_comment_match:\n                    key = kv_comment_match.group(1).strip()\n                    comment = kv_comment_match.group(3).strip()\n                    field_path = f\"{current_section}.{key}\"\n                    comments[field_path] = comment\n                    continue\n\n                # 检查键值对后面没有注释的情况（查找前一行是否为注释）\n                kv_match = re.match(r'^([^=]+)=(.*)$', line)\n                if kv_match:\n                    key = kv_match.group(1).strip()\n                    # 寻找前一行是否是该字段的注释（且不是独立注释行）\n                    idx = i - 1\n                    if (idx >= 0 and idx not in standalone_comment_lines and\n                            lines[idx].strip().startswith('#')):\n                        comment = lines[idx].strip()[1:].strip()\n                        field_path = f\"{current_section}.{key}\"\n                        comments[field_path] = comment\n\n            logger.log('WEBUI', f\"成功提取配置注释 {len(comments)} 条\")\n            return comments\n        except Exception as e:\n            logger.log('WEBUI', f\"提取注释出错: {str(e)}\")\n            return {}\n\n    def save_config(self, config: Dict[str, Any]) -> bool:\n        \"\"\"保存配置到文件\n        \n        将配置保存到TOML文件，会尝试保留现有的注释和格式。\n        \n        Args:\n            config: 要保存的配置数据\n            \n        Returns:\n            bool: 是否成功保存\n        \"\"\"\n        try:\n            # 打印接收到的配置数据，用于调试\n            logger.log('WEBUI', f\"接收到配置数据: {config}\")\n\n            # 修复已损坏的配置结构 - 处理嵌套结构变成的特殊情况\n            self._fix_nested_config_structure(config)\n\n            # 如果配置文件已存在，先读取它以保留注释和格式\n            doc = tomlkit.document()\n            if self.config_path.exists():\n                try:\n                    with open(self.config_path, \"r\", encoding=\"utf-8\") as f:\n                        doc = tomlkit.parse(f.read())\n                except Exception as e:\n                    logger.log('WEBUI', f\"读取现有配置文件失败，将创建新文件: {str(e)}\")\n                    # 如果读取失败，创建新文档\n                    doc = tomlkit.document()\n\n            # 更新配置\n            for section_name, section_data in config.items():\n                if section_name not in doc:\n                    doc[section_name] = tomlkit.table()\n                    logger.log('WEBUI', f\"创建新配置节: {section_name}\")\n\n                for key, value in section_data.items():\n                    # 特殊处理列表类型，确保保持为列表类型而不是字符串\n                    if isinstance(value, list):\n                        # 记录调试信息\n                        logger.log('WEBUI', f\"处理列表字段 {section_name}.{key}，原始值: {value}\")\n\n                        # 创建一个新的tomlkit数组对象\n                        toml_array = tomlkit.array()\n\n                        # 遍历列表中每个元素，添加到tomlkit数组\n                        for item in value:\n                            # 确保不会添加None\n                            if item is not None:\n                                # 去除可能的前后空格\n                                if isinstance(item, str):\n                                    item = item.strip()\n                                    if not item:  # 跳过空字符串\n                                        continue\n                                toml_array.append(item)\n                                logger.log('WEBUI', f\"  - 添加元素: {item} (类型: {type(item).__name__})\")\n\n                        # 设置数组值\n                        doc[section_name][key] = toml_array\n                        logger.log('WEBUI', f\"完成列表字段 {section_name}.{key} 保存: {toml_array}\")\n                    else:\n                        # 原始值，记录用于调试\n                        orig_value = doc[section_name].get(key, None) if section_name in doc else None\n                        # 设置新值\n                        doc[section_name][key] = value\n                        logger.log('WEBUI', f\"设置字段 {section_name}.{key}: {value} (原值: {orig_value})\")\n\n            # 保存配置\n            with open(self.config_path, \"w\", encoding=\"utf-8\") as f:\n                toml_content = tomlkit.dumps(doc)\n                logger.log('WEBUI', f\"生成的TOML内容: \\n{toml_content}\")\n                f.write(toml_content)\n\n            logger.log('WEBUI', \"配置已成功保存\")\n            return True\n        except Exception as e:\n            logger.log('WEBUI', f\"保存配置文件出错: {str(e)}\")\n            logger.log('WEBUI', f\"异常详情: {traceback.format_exc()}\")\n            return False\n\n    def _fix_nested_config_structure(self, config: Dict[str, Any]) -> None:\n        \"\"\"\n        修复配置结构中被错误保存的嵌套结构\n        \n        特别处理那些应该是单一字段但被分解为嵌套结构的情况\n        例如：disabled-plugins 被错误保存为 [XYBot.disabled] 下的 plugins\n        \n        Args:\n            config: 要修复的配置数据\n        \"\"\"\n        try:\n            # 特殊处理 disabled-plugins 的情况\n            if 'XYBot' in config:\n                # 情况1: disabled-plugins 被错误地保存为嵌套结构 [XYBot.disabled]\n                if 'disabled' in config['XYBot'] and isinstance(config['XYBot']['disabled'], dict):\n                    if 'plugins' in config['XYBot']['disabled']:\n                        # 将 XYBot.disabled.plugins 的值转移到 XYBot.disabled-plugins\n                        plugins_value = config['XYBot']['disabled']['plugins']\n                        logger.log('WEBUI',\n                                   f\"修复嵌套结构: 将 XYBot.disabled.plugins 值 {plugins_value} 转移到 XYBot.disabled-plugins\")\n                        config['XYBot']['disabled-plugins'] = plugins_value\n\n                        # 删除嵌套结构\n                        del config['XYBot']['disabled']\n\n                # 情况2: disabled-plugins 字段存在，但不是数组类型\n                if 'disabled-plugins' in config['XYBot'] and not isinstance(config['XYBot']['disabled-plugins'], list):\n                    # 尝试将字符串转换为数组\n                    plugin_value = config['XYBot']['disabled-plugins']\n                    logger.log('WEBUI', f\"修复 disabled-plugins 类型: 从 {type(plugin_value).__name__} 转换为数组\")\n\n                    if isinstance(plugin_value, str):\n                        # 如果是空字符串，转换为空数组\n                        if not plugin_value.strip():\n                            config['XYBot']['disabled-plugins'] = []\n                        # 如果是逗号分隔的字符串，拆分成数组\n                        elif ',' in plugin_value:\n                            config['XYBot']['disabled-plugins'] = [item.strip() for item in plugin_value.split(',') if\n                                                                   item.strip()]\n                        # 否则作为单个元素的数组\n                        else:\n                            config['XYBot']['disabled-plugins'] = [plugin_value.strip()]\n\n            # 清理可能的 undefined 字段\n            for section_name in list(config.keys()):\n                if section_name == 'undefined':\n                    logger.log('WEBUI', f\"删除无效配置节: {section_name}\")\n                    del config[section_name]\n                    continue\n\n                section_data = config[section_name]\n                if isinstance(section_data, dict):\n                    for key in list(section_data.keys()):\n                        if key == 'undefined':\n                            logger.log('WEBUI', f\"删除无效字段: {section_name}.{key}\")\n                            del section_data[key]\n\n            logger.log('WEBUI', \"配置结构修复完成\")\n        except Exception as e:\n            logger.log('WEBUI', f\"修复配置结构时出错: {str(e)}\")\n            logger.log('WEBUI', f\"异常详情: {traceback.format_exc()}\")\n\n    def get_form_schema(self) -> Dict[str, Any]:\n        \"\"\"获取表单架构，用于Web界面动态生成配置表单\n        \n        根据当前配置和字段元数据生成符合前端需求的表单架构。\n        \n        Returns:\n            Dict[str, Any]: 表单架构，格式为 {section_name: {title, description, properties, propertyOrder}}\n        \"\"\"\n        # 获取当前配置\n        config = self.get_config()\n\n        # 从文件中读取原始配置顺序\n        sections_order = []\n        fields_order = {}\n\n        try:\n            if self.config_path.exists():\n                with open(self.config_path, \"r\", encoding=\"utf-8\") as f:\n                    content = f.read()\n\n                # 按行解析配置文件，提取节和字段的顺序\n                lines = content.split('\\n')\n                current_section = None\n\n                for line in lines:\n                    line = line.strip()\n                    if not line or line.startswith('#'):\n                        continue\n\n                    # 检查是否是节定义行\n                    section_match = re.match(r'^\\[([^\\]]+)\\]', line)\n                    if section_match:\n                        current_section = section_match.group(1)\n                        if current_section not in sections_order:\n                            sections_order.append(current_section)\n                            fields_order[current_section] = []\n                        continue\n\n                    # 检查是否是键值对\n                    kv_match = re.match(r'^([^=]+)=', line)\n                    if kv_match and current_section:\n                        field_name = kv_match.group(1).strip()\n                        if field_name not in fields_order[current_section]:\n                            fields_order[current_section].append(field_name)\n\n                logger.log('WEBUI', f\"提取到配置节顺序: {sections_order}\")\n                for section, fields in fields_order.items():\n                    logger.log('WEBUI', f\"配置节 {section} 的字段顺序: {fields}\")\n        except Exception as e:\n            logger.log('WEBUI', f\"提取配置顺序出错: {str(e)}\")\n            # 如果出错，使用默认顺序\n            sections_order = list(config.keys())\n            fields_order = {section: list(section_data.keys()) for section, section_data in config.items()}\n\n        # 从TOML文件中提取注释\n        comments = self.extract_comments()\n\n        # 创建符合前端预期的schema格式\n        schemas = {}\n\n        # 要忽略的字段名\n        ignored_fields = ['undefined']\n\n        # 遍历配置节（按原始顺序）\n        for section_name in sections_order:\n            if section_name not in config:\n                continue\n\n            section_data = config[section_name]\n\n            # 跳过无效的配置节\n            if section_name in ignored_fields:\n                logger.log('WEBUI', f\"跳过无效配置节: {section_name}\")\n                continue\n\n            # 创建该配置节的schema\n            section_schema = {\n                \"title\": section_name,\n                \"description\": \"\",  # 可以为配置节添加描述信息\n                \"properties\": {},\n                \"propertyOrder\": fields_order.get(section_name, [])  # 添加字段顺序信息\n            }\n\n            # 遍历字段（按原始顺序）\n            for field_name in fields_order.get(section_name, []):\n                if field_name not in section_data:\n                    continue\n\n                # 跳过无效字段\n                if field_name in ignored_fields:\n                    logger.log('WEBUI', f\"跳过无效字段: {section_name}.{field_name}\")\n                    continue\n\n                field_value = section_data[field_name]\n                field_path = f\"{section_name}.{field_name}\"\n                field_type = self._get_field_type(field_value)\n\n                # 从TOML注释中获取字段描述\n                field_description = comments.get(field_path, \"\")\n\n                # 创建字段的schema\n                field_schema = {\n                    \"title\": field_name,\n                    \"type\": field_type,\n                    \"description\": field_description,\n                }\n\n                # 添加选项（如果有）\n                if self.field_options.get(field_path):\n                    field_schema[\"enum\"] = self.field_options.get(field_path, [])\n\n                # 如果是数组类型，添加items描述\n                if field_type == \"array\":\n                    # 确定数组元素类型\n                    items_type = \"string\"  # 默认为字符串类型\n                    if field_value and len(field_value) > 0:\n                        items_type = self._get_field_type(field_value[0])\n\n                    field_schema[\"items\"] = {\"type\": items_type}\n\n                # 将字段schema添加到配置节properties中\n                section_schema[\"properties\"][field_name] = field_schema\n\n            # 将配置节schema添加到总schema中\n            schemas[section_name] = section_schema\n\n        # 添加配置节顺序信息到返回值\n        schemas[\"_meta\"] = {\n            \"sectionsOrder\": sections_order\n        }\n\n        logger.log('WEBUI', f\"已生成配置表单架构，包含 {len(schemas) - 1} 个配置节\")\n        return schemas\n\n    def validate_config(self, config: Dict[str, Any]) -> Tuple[bool, List[str]]:\n        \"\"\"验证配置是否符合规则\n        \n        检查配置中的字段是否符合预定义的验证规则。\n        \n        Args:\n            config: 要验证的配置数据\n            \n        Returns:\n            Tuple[bool, List[str]]: (是否验证通过, 错误信息列表)\n        \"\"\"\n        errors = []\n\n        for section_name, section_data in config.items():\n            for field_name, field_value in section_data.items():\n                field_path = f\"{section_name}.{field_name}\"\n\n                # 如果有验证器，则进行验证\n                if field_path in self.field_validators:\n                    validator = self.field_validators[field_path]\n                    if not validator(field_value):\n                        errors.append(f\"字段 '{field_path}' 的值 '{field_value}' 无效\")\n\n        if errors:\n            logger.log('WEBUI', f\"配置验证失败，发现 {len(errors)} 个错误\")\n            for error in errors:\n                logger.log('WEBUI', f\"配置错误: {error}\")\n        else:\n            logger.log('WEBUI', \"配置验证通过\")\n\n        return len(errors) == 0, errors\n\n    def _dict_to_toml(self, data: Dict[str, Any]) -> str:\n        \"\"\"将字典转换为TOML字符串\n        \n        内部工具方法，将配置字典转换为格式化的TOML字符串。\n        \n        Args:\n            data: 要转换的字典数据\n            \n        Returns:\n            str: 格式化的TOML字符串\n        \"\"\"\n        doc = tomlkit.document()\n\n        for section_name, section_data in data.items():\n            table = tomlkit.table()\n\n            for key, value in section_data.items():\n                table.add(key, value)\n\n            doc.add(section_name, table)\n\n        return tomlkit.dumps(doc)\n\n    def _get_field_type(self, value: Any) -> str:\n        \"\"\"推断字段值的数据类型\n        \n        根据字段值推断适合的JSON Schema类型名称。\n        \n        Args:\n            value: 字段值\n            \n        Returns:\n            str: 字段类型名称（如\"boolean\", \"integer\", \"string\"等）\n        \"\"\"\n        if isinstance(value, bool):\n            return \"boolean\"\n        elif isinstance(value, int):\n            return \"integer\"\n        elif isinstance(value, float):\n            return \"number\"\n        elif isinstance(value, list):\n            return \"array\"\n        elif isinstance(value, dict):\n            return \"object\"\n        elif isinstance(value, str):\n            return \"string\"\n        else:\n            return \"string\"  # 默认为字符串类型\n\n    def get_version(self) -> str:\n        \"\"\"获取XYBot版本号\n        \n        从配置中提取XYBot的版本号。\n        \n        Returns:\n            str: 版本号，如果未找到则返回\"未知\"\n        \"\"\"\n        config = self.get_config()\n        try:\n            return config.get(\"XYBot\", {}).get(\"version\", \"未知\")\n        except Exception as e:\n            logger.log('WEBUI', f\"获取版本号失败: {str(e)}\")\n            return \"未知\"\n\n\n# 创建配置服务实例\nconfig_service = ConfigService()\n"
  },
  {
    "path": "WebUI/services/data_service.py",
    "content": "import asyncio\nimport logging\nimport os\nimport time\nfrom pathlib import Path\n\nfrom WebUI.common.bot_bridge import bot_bridge\nfrom WebUI.services.bot_service import bot_service\nfrom WebUI.utils.async_to_sync import async_to_sync\nfrom WebUI.utils.singleton import Singleton\n\n# 项目根目录路径\nROOT_DIR = Path(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))\n# 日志目录\nLOGS_DIR = ROOT_DIR / 'logs'\n# 日志文件路径\nBOT_LOG_PATH = LOGS_DIR / 'xybot.log'\n\n# 设置日志记录器\nlogger = logging.getLogger(__name__)\nhandler = logging.StreamHandler()\nformatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')\nhandler.setFormatter(formatter)\nlogger.addHandler(handler)\nlogger.setLevel(logging.INFO)\n\n\nclass DataService(metaclass=Singleton):\n    \"\"\"数据服务类，提供机器人状态和统计数据\"\"\"\n\n    def __init__(self):\n        \"\"\"初始化数据服务\"\"\"\n        self._cache = {}\n        self._last_update = 0\n        self._update_interval = 5  # 更新间隔（秒）\n        self._last_user_sync_time = 0  # 上次同步用户数量的时间\n        # 确保异步初始化被执行\n        try:\n            loop = asyncio.get_event_loop()\n        except RuntimeError:\n            loop = asyncio.new_event_loop()\n            asyncio.set_event_loop(loop)\n        loop.run_until_complete(self._init_async())\n        # 日志文件位置追踪\n        self._log_last_position = 0\n\n    async def _init_async(self):\n        \"\"\"异步初始化\"\"\"\n        self._log_last_position = await bot_bridge.get_log_position()\n\n    def get_bot_status(self):\n        \"\"\"\n        获取机器人当前运行状态\n        \n        返回:\n            dict: 包含状态信息的字典\n        \"\"\"\n        # 检查缓存是否需要更新\n        return {\n            'running': bot_service.is_running(),\n            'status': 'running' if bot_service.is_running() else 'stopped',\n            'uptime': self._get_uptime(),\n            'message_count': self._cache.get('messages', 0),\n            'user_count': self._cache.get('users', 0)\n        }\n\n    def get_metrics(self):\n        \"\"\"\n        获取机器人核心指标\n        \"\"\"\n        # 先刷新缓存数据再返回\n        self._refresh_cache_data()\n        \n        return {\n            'messages': self._cache.get('messages', 0),\n            'users': self._cache.get('users', 0),\n            'uptime': self._get_uptime_formatted()\n        }\n        \n    def _refresh_cache_data(self):\n        \"\"\"\n        刷新缓存数据\n        \"\"\"\n        current_time = time.time()\n        # 如果距离上次更新时间超过了更新间隔，则更新缓存\n        if current_time - self._last_update > self._update_interval:\n            try:\n                # 获取事件循环\n                try:\n                    loop = asyncio.get_event_loop()\n                except RuntimeError:\n                    loop = asyncio.new_event_loop()\n                    asyncio.set_event_loop(loop)\n                \n                # 直接同步执行异步操作\n                if loop.is_running():\n                    # 如果循环正在运行，使用Future同步等待结果\n                    messages = asyncio.run_coroutine_threadsafe(bot_bridge.get_message_count(), loop).result(5)\n                    users = asyncio.run_coroutine_threadsafe(bot_bridge.get_user_count(), loop).result(5)\n                else:\n                    # 否则直接执行到完成\n                    messages = loop.run_until_complete(bot_bridge.get_message_count())\n                    users = loop.run_until_complete(bot_bridge.get_user_count())\n                \n                # 同步XYBotDB的真实用户数量到KeyvalDB (每10分钟执行一次)\n                ten_minutes = 600  # 10分钟的秒数\n                if current_time - self._last_user_sync_time > ten_minutes:\n                    try:\n                        # 导入XYBotDB以获取真实用户数量\n                        from database.XYBotDB import XYBotDB\n                        db = XYBotDB()\n                        real_users_count = db.get_users_count()\n                        \n                        # 只有当实际用户数大于当前计数时才更新\n                        if real_users_count > users:\n                            if loop.is_running():\n                                asyncio.run_coroutine_threadsafe(\n                                    bot_bridge._db.set(\"bot:stats:user_count\", str(real_users_count)), \n                                    loop\n                                ).result(5)\n                                users = real_users_count\n                            else:\n                                loop.run_until_complete(\n                                    bot_bridge._db.set(\"bot:stats:user_count\", str(real_users_count))\n                                )\n                                users = real_users_count\n                            logger.info(f\"已从XYBotDB同步用户数量: {real_users_count}\")\n                        \n                        self._last_user_sync_time = current_time\n                    except Exception as e:\n                        logger.error(f\"同步用户数量失败: {str(e)}\")\n                \n                # 更新缓存 - 直接从bot_bridge获取start_time，不需要异步调用\n                self._cache['messages'] = messages\n                self._cache['users'] = users\n                self._cache['start_time'] = bot_bridge.start_time\n                self._last_update = current_time\n                logger.info(f\"缓存数据已刷新: 消息数={messages}, 用户数={users}, 启动时间={bot_bridge.start_time}\")\n            except Exception as e:\n                logger.error(f\"刷新缓存数据失败: {str(e)}\")\n                # 使用默认值\n                self._cache.setdefault('messages', 0)\n                self._cache.setdefault('users', 0)\n                self._cache.setdefault('start_time', 0)\n\n    def get_recent_logs(self, n=100):\n        \"\"\"\n        获取最近的日志\n        \"\"\"\n        logs = []\n\n        try:\n            if BOT_LOG_PATH.exists():\n                with open(BOT_LOG_PATH, 'r', encoding='utf-8') as f:\n                    lines = f.readlines()\n                    logs = lines[-n:] if len(lines) > n else lines\n\n                # 更新日志位置指针\n                self._log_last_position = os.path.getsize(BOT_LOG_PATH)\n                self._save_log_position()\n        except Exception as e:\n            error_msg = f\"读取日志文件出错: {str(e)}\"\n            logger.error(error_msg)\n            logs = [error_msg]\n\n        # 确保返回的是字符串列表，并移除每行末尾的换行符\n        return [line.strip() if isinstance(line, str) else str(line).strip() for line in logs]\n\n    def get_new_logs(self):\n        \"\"\"\n        获取新增的日志内容（增量更新）\n        \"\"\"\n        new_logs = []\n\n        try:\n            if BOT_LOG_PATH.exists():\n                current_size = os.path.getsize(BOT_LOG_PATH)\n\n                # 检查是否有新内容\n                if current_size > self._log_last_position:\n                    with open(BOT_LOG_PATH, 'r', encoding='utf-8') as f:\n                        # 移动到上次读取的位置\n                        f.seek(self._log_last_position)\n                        # 读取新增内容\n                        new_lines = f.readlines()\n                        new_logs = [line.strip() for line in new_lines]\n\n                    # 更新位置指针\n                    self._log_last_position = current_size\n                    self._save_log_position()\n        except Exception as e:\n            error_msg = f\"读取新增日志出错: {str(e)}\"\n            logger.error(error_msg)\n            new_logs = [error_msg]\n\n        # 确保返回的是字符串列表\n        return [line if isinstance(line, str) else str(line) for line in new_logs]\n\n    @async_to_sync\n    async def _save_log_position(self):\n        \"\"\"保存日志读取位置到数据库\"\"\"\n        try:\n            await bot_bridge.save_log_position(self._log_last_position)\n            return True\n        except Exception as e:\n            logger.error(f\"保存日志位置失败: {str(e)}\")\n            return False\n\n    @async_to_sync\n    async def _get_message_count(self):\n        \"\"\"\n        获取接收消息数量\n        \"\"\"\n        try:\n            return await bot_bridge.get_message_count()\n        except Exception as e:\n            logger.error(f\"获取消息计数失败: {str(e)}\")\n            return 0\n\n    @async_to_sync\n    async def _get_user_count(self):\n        \"\"\"\n        获取用户数量\n        \"\"\"\n        try:\n            return await bot_bridge.get_user_count()\n        except Exception as e:\n            logger.error(f\"获取用户计数失败: {str(e)}\")\n            return 0\n\n    @async_to_sync\n    async def _get_start_time(self):\n        \"\"\"\n        获取机器人启动时间\n        \n        返回:\n            float: 启动时间戳\n        \"\"\"\n        try:\n            return await bot_bridge.get_start_time()\n        except Exception as e:\n            logger.error(f\"获取启动时间失败: {str(e)}\")\n            return 0\n\n    def _get_uptime(self):\n        \"\"\"\n        获取机器人运行时长（秒）\n        \n        返回:\n            int: 运行时长（秒）\n        \"\"\"\n        try:\n            if not bot_service.is_running():\n                return 0\n\n            start_time = self._cache.get('start_time', 0)\n            if start_time == 0:\n                return 0\n\n            return int(time.time() - float(start_time))\n        except Exception as e:\n            logger.error(f\"计算运行时长失败: {str(e)}\")\n            return 0\n\n    def _get_uptime_formatted(self):\n        \"\"\"\n        获取格式化的运行时长\n        \n        返回:\n            str: 格式化的运行时长\n        \"\"\"\n        try:\n            uptime_seconds = self._get_uptime()\n\n            if uptime_seconds == 0:\n                return \"未运行\"\n\n            # 将秒转换为天、小时、分钟、秒\n            days, remainder = divmod(uptime_seconds, 86400)\n            hours, remainder = divmod(remainder, 3600)\n            minutes, seconds = divmod(remainder, 60)\n\n            if days > 0:\n                return f\"{days}天 {hours}小时\"\n            elif hours > 0:\n                return f\"{hours}小时 {minutes}分钟\"\n            elif minutes > 0:\n                return f\"{minutes}分钟 {seconds}秒\"\n            else:\n                return f\"{seconds}秒\"\n        except Exception as e:\n            logger.error(f\"格式化运行时长失败: {str(e)}\")\n            return \"未知\"\n\n    @async_to_sync\n    async def increment_message_count(self, amount=1):\n        \"\"\"\n        增加消息计数\n        \n        参数:\n            amount (int): 增加数量\n        \n        返回:\n            bool: 操作是否成功\n        \"\"\"\n        try:\n            return await bot_bridge.increment_message_count(amount)\n        except Exception as e:\n            logger.error(f\"增加消息计数失败: {str(e)}\")\n            return False\n\n    @async_to_sync\n    async def increment_user_count(self, amount=1):\n        \"\"\"\n        增加用户计数\n        \n        参数:\n            amount (int): 增加数量\n        \n        返回:\n            bool: 操作是否成功\n        \"\"\"\n        try:\n            return await bot_bridge.increment_user_count(amount)\n        except Exception as e:\n            logger.error(f\"增加用户计数失败: {str(e)}\")\n            return False\n\n\n# 创建数据服务实例\ndata_service = DataService()\n"
  },
  {
    "path": "WebUI/services/file_service.py",
    "content": "import os\nimport traceback\nfrom pathlib import Path\nfrom typing import List, Dict, Any, Tuple\n\nfrom loguru import logger\n\nfrom WebUI.utils.singleton import Singleton\n\n# 项目根目录路径\nROOT_DIR = Path(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))\n# 日志目录\nLOGS_DIR = ROOT_DIR / 'logs'\n\n\nclass SecurityError(Exception):\n    \"\"\"安全性验证错误，当路径访问超出允许范围时抛出\"\"\"\n    pass\n\n\nclass PathValidationError(Exception):\n    \"\"\"路径验证错误，当路径处理过程中出现问题时抛出\"\"\"\n    pass\n\n\nclass FileService(metaclass=Singleton):\n    \"\"\"文件服务类，提供文件系统操作功能\n    \n    负责处理文件系统相关操作，包括读取目录内容、获取文件内容、\n    在文件中搜索和保存文件。所有路径操作都经过安全性验证，\n    以防止目录遍历攻击。\n    \"\"\"\n\n    def __init__(self):\n        \"\"\"初始化文件服务\n        \n        确保必要的目录结构存在，如日志目录。\n        \"\"\"\n        # 确保日志目录存在\n        LOGS_DIR.mkdir(parents=True, exist_ok=True)\n\n    def _validate_path(self, rel_path: str) -> Path:\n        \"\"\"验证并返回安全的文件路径\n        \n        对输入的相对路径进行安全验证，确保访问不超出根目录范围，\n        防止目录遍历攻击和恶意访问。\n        \n        Args:\n            rel_path: 相对于根目录的文件路径\n            \n        Returns:\n            Path: 验证后的安全绝对路径\n            \n        Raises:\n            SecurityError: 当路径尝试访问根目录之外的位置时\n            PathValidationError: 当路径处理过程中出现其他错误时\n        \"\"\"\n        try:\n            # 规范化路径处理\n            clean_path = rel_path.strip('/')  # 去除首尾斜杠\n            if not clean_path:  # 处理根目录情况\n                return ROOT_DIR\n\n            # 分解路径组件并过滤危险字符\n            path_components = [p for p in clean_path.split('/') if p not in ('', '.', '..')]\n\n            # 重建安全路径\n            safe_path = ROOT_DIR.joinpath(*path_components)\n            resolved_path = safe_path.resolve()\n\n            # 二次验证路径安全性\n            if not resolved_path.is_relative_to(ROOT_DIR):\n                logger.log('WEBUI', f\"安全警告：路径越界访问尝试: {rel_path} -> {resolved_path}\")\n                raise SecurityError(\"路径越界访问尝试\")\n\n            return resolved_path\n        except SecurityError:\n            # 直接重新抛出安全错误\n            raise\n        except Exception as e:\n            logger.log('WEBUI', f\"路径验证失败: {rel_path}, 错误: {str(e)}\")\n            raise PathValidationError(f\"路径验证失败: {str(e)}\")\n\n    def list_directory(self, rel_path: str = '') -> List[Dict[str, Any]]:\n        \"\"\"列出指定目录中的内容\n        \n        获取指定目录中的文件和子目录列表，并返回其元数据信息。\n        结果按照目录优先、名称字母顺序排序。\n        \n        Args:\n            rel_path: 相对于根目录的目录路径，默认为根目录\n            \n        Returns:\n            List[Dict[str, Any]]: 目录内容列表，每项包含名称、路径、类型等信息\n        \"\"\"\n        try:\n            target_dir = self._validate_path(rel_path)\n\n            if not target_dir.exists():\n                logger.log('WEBUI', f\"目录不存在: {target_dir}\")\n                raise FileNotFoundError(f\"目录不存在: {target_dir}\")\n            if not target_dir.is_dir():\n                logger.log('WEBUI', f\"路径不是目录: {target_dir}\")\n                raise NotADirectoryError(f\"路径不是目录: {target_dir}\")\n\n            items = []\n            for path in target_dir.iterdir():\n                try:\n                    # 过滤隐藏文件（以.开头）\n                    if path.name.startswith('.'):\n                        continue\n\n                    stat = path.stat()\n                    items.append({\n                        'name': path.name,\n                        'path': str(path.relative_to(ROOT_DIR)),\n                        'is_dir': path.is_dir(),\n                        'size': stat.st_size,\n                        'modified': stat.st_mtime,\n                        'created': stat.st_ctime,\n                        'permissions': stat.st_mode\n                    })\n                except Exception as e:\n                    logger.log('WEBUI', f\"处理目录项时出错: {path.name}, 错误: {str(e)}\")\n                    continue  # 跳过处理失败的项\n\n            # 排序：目录在前，按名称排序\n            sorted_items = sorted(items, key=lambda x: (not x['is_dir'], x['name'].lower()))\n            return sorted_items\n\n        except (SecurityError, PathValidationError, FileNotFoundError, NotADirectoryError) as e:\n            # 这些是预期的异常，记录后返回空列表\n            logger.log('WEBUI', f\"目录列表错误: {str(e)}\")\n            return []\n        except Exception as e:\n            # 未预期的异常，记录详细信息\n            logger.log('WEBUI', f\"目录列表未知错误: {str(e)}\")\n            logger.log('WEBUI', traceback.format_exc())\n            return []\n\n    def get_file_content(self, rel_path: str = '',\n                         start_line: int = 0, max_lines: int = 1000) -> Tuple[List[str], Dict[str, Any]]:\n        \"\"\"获取文件内容\n        \n        读取指定文件的内容，支持指定起始行和最大行数，适用于分页读取大文件。\n        \n        Args:\n            rel_path: 相对于根目录的文件路径\n            start_line: 起始行号（从0开始）\n            max_lines: 最大行数\n            \n        Returns:\n            Tuple[List[str], Dict[str, Any]]: (文件内容行列表, 文件信息字典)\n        \"\"\"\n        try:\n            file_path = self._validate_path(rel_path)\n\n            if not file_path.exists() or not file_path.is_file():\n                logger.log('WEBUI', f\"文件不存在或不是常规文件: {rel_path}\")\n                return [], {'error': '文件不存在'}\n\n            file_info = {\n                'name': file_path.name,\n                'path': str(file_path.relative_to(ROOT_DIR)),\n                'size': file_path.stat().st_size,\n                'modified': file_path.stat().st_mtime,\n                'created': file_path.stat().st_ctime,\n                'total_lines': 0,\n                'start_line': start_line,\n                'end_line': 0\n            }\n\n            # 读取文件内容\n            with open(file_path, 'r', encoding='utf-8', errors='replace') as f:\n                # 如果起始行为0，直接读取指定行数\n                if start_line == 0:\n                    lines = []\n                    for i, line in enumerate(f):\n                        if i >= max_lines:\n                            break\n                        lines.append(line.rstrip('\\n'))\n\n                    file_info['total_lines'] = i + 1 if i < max_lines else i + 1 + 1  # +1表示还有更多行\n                    file_info['end_line'] = min(start_line + len(lines) - 1, file_info['total_lines'] - 1)\n\n                    logger.log('WEBUI', f\"读取文件 {rel_path} 内容, 从第 {start_line} 行起，共 {len(lines)} 行\")\n                    return lines, file_info\n\n                # 否则，先跳过前面的行\n                for i, _ in enumerate(f):\n                    if i >= start_line - 1:\n                        break\n\n                if i < start_line - 1:\n                    # 起始行超出文件行数\n                    file_info['total_lines'] = i + 1\n                    file_info['end_line'] = i\n                    logger.log('WEBUI', f\"请求的起始行 {start_line} 超出文件 {rel_path} 的行数 {i + 1}\")\n                    return [], file_info\n\n                # 然后读取指定行数\n                lines = []\n                for j in range(max_lines):\n                    line = f.readline()\n                    if not line:\n                        break\n                    lines.append(line.rstrip('\\n'))\n\n                # 继续读取剩余行数，计算总行数\n                remaining_count = 0\n                while f.readline():\n                    remaining_count += 1\n                    if remaining_count > 1000:  # 设置一个合理的限制，避免处理过大的文件\n                        break\n\n                file_info['total_lines'] = start_line + len(lines) + remaining_count\n                file_info['end_line'] = min(start_line + len(lines) - 1, file_info['total_lines'] - 1)\n\n                logger.log('WEBUI',\n                           f\"读取文件 {rel_path} 内容, 从第 {start_line} 行起，共 {len(lines)} 行，总行数约 {file_info['total_lines']}\")\n                return lines, file_info\n\n        except (SecurityError, PathValidationError) as e:\n            # 安全相关错误\n            logger.log('WEBUI', f\"读取文件内容安全错误: {str(e)}\")\n            return [], {'error': f'安全错误: {str(e)}'}\n        except UnicodeDecodeError as e:\n            # 编码错误，可能是二进制文件\n            logger.log('WEBUI', f\"文件编码错误: {rel_path}, {str(e)}\")\n            return [], {'error': f'文件编码错误, 可能是二进制文件'}\n        except Exception as e:\n            # 其他未预期的错误\n            logger.log('WEBUI', f\"读取文件内容出错: {rel_path}, {str(e)}\")\n            logger.log('WEBUI', traceback.format_exc())\n            return [], {'error': f'读取文件出错: {str(e)}'}\n\n    def search_in_file(self, rel_path: str = '',\n                       query: str = '', max_results: int = 100) -> List[Dict[str, Any]]:\n        \"\"\"在文件中搜索指定内容\n        \n        在指定文件中搜索字符串，返回匹配的行信息，包括行号、内容和匹配位置。\n        \n        Args:\n            rel_path: 相对于根目录的文件路径\n            query: 要搜索的字符串\n            max_results: 最大结果数\n            \n        Returns:\n            List[Dict[str, Any]]: 搜索结果列表，每项包含行号、内容和匹配位置\n        \"\"\"\n        if not query:\n            logger.log('WEBUI', \"搜索查询为空，返回空结果\")\n            return []\n\n        try:\n            file_path = self._validate_path(rel_path)\n\n            if not file_path.exists() or not file_path.is_file():\n                logger.log('WEBUI', f\"搜索的文件不存在或不是常规文件: {rel_path}\")\n                return []\n\n            results = []\n\n            with open(file_path, 'r', encoding='utf-8', errors='replace') as f:\n                for i, line in enumerate(f):\n                    if query.lower() in line.lower():\n                        results.append({\n                            'line_number': i + 1,  # 从1开始的行号\n                            'content': line.rstrip('\\n'),\n                            'match_position': line.lower().find(query.lower())\n                        })\n\n                        if len(results) >= max_results:\n                            logger.log('WEBUI', f\"搜索结果达到上限 {max_results}，停止搜索\")\n                            break\n\n            logger.log('WEBUI', f\"在文件 {rel_path} 中搜索 '{query}'，找到 {len(results)} 条匹配\")\n            return results\n        except (SecurityError, PathValidationError) as e:\n            logger.log('WEBUI', f\"搜索文件安全错误: {str(e)}\")\n            return []\n        except UnicodeDecodeError as e:\n            logger.log('WEBUI', f\"搜索文件编码错误: {rel_path}, {str(e)}\")\n            return []\n        except Exception as e:\n            logger.log('WEBUI', f\"搜索文件内容出错: {rel_path}, {str(e)}\")\n            logger.log('WEBUI', traceback.format_exc())\n            return []\n\n    def save_file_content(self, rel_path: str, content: str) -> bool:\n        \"\"\"保存文件内容\n        \n        将内容写入指定文件，如果文件不存在则创建。\n        \n        Args:\n            rel_path: 相对于根目录的文件路径\n            content: 要保存的文件内容\n            \n        Returns:\n            bool: 是否成功保存\n        \"\"\"\n        try:\n            # 验证路径\n            file_path = self._validate_path(rel_path)\n\n            # 确保目标是文件而不是目录\n            if file_path.is_dir():\n                logger.log('WEBUI', f\"保存失败: 目标是目录，不能保存内容: {rel_path}\")\n                raise ValueError(\"目标是目录，不能保存内容\")\n\n            # 确保父目录存在\n            file_path.parent.mkdir(parents=True, exist_ok=True)\n\n            # 写入文件内容\n            with open(file_path, 'w', encoding='utf-8') as f:\n                f.write(content)\n\n            logger.log('WEBUI', f\"文件内容保存成功: {rel_path}, 大小: {len(content)} 字符\")\n            return True\n        except (SecurityError, PathValidationError) as e:\n            logger.log('WEBUI', f\"保存文件安全错误: {str(e)}\")\n            return False\n        except Exception as e:\n            logger.log('WEBUI', f\"保存文件异常: {str(e)}\")\n            logger.log('WEBUI', traceback.format_exc())\n            return False\n\n\n# 创建文件服务实例\nfile_service = FileService()\n"
  },
  {
    "path": "WebUI/services/plugin_service.py",
    "content": "import asyncio\nimport os\nimport sys\nfrom pathlib import Path\nfrom typing import List, Dict, Any, Optional\n\nfrom loguru import logger\n\nfrom WebUI.common.bot_bridge import bot_bridge\nfrom WebUI.utils.singleton import Singleton\n\n# 确保可以导入根目录模块\nROOT_DIR = Path(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))\nsys.path.insert(0, str(ROOT_DIR))\n\n\ndef get_event_loop():\n    \"\"\"\n    获取一个可用的事件循环，如果当前循环已关闭则创建新循环\n    \n    Returns:\n        asyncio.AbstractEventLoop: 事件循环\n    \"\"\"\n    try:\n        loop = asyncio.get_event_loop()\n        if loop.is_closed():\n            logger.log('WEBUI', f\"插件服务: 检测到事件循环已关闭，创建新循环\")\n            loop = asyncio.new_event_loop()\n            asyncio.set_event_loop(loop)\n    except RuntimeError:\n        loop = asyncio.new_event_loop()\n        asyncio.set_event_loop(loop)\n\n    return loop\n\n\nclass PluginService(metaclass=Singleton):\n    \"\"\"插件服务类，提供插件管理功能\"\"\"\n\n    def __init__(self):\n        \"\"\"初始化插件服务\"\"\"\n        # 配置目录\n        self.config_dir = ROOT_DIR / 'plugins'\n\n    def get_all_plugins(self) -> List[Dict[str, Any]]:\n        \"\"\"获取所有插件列表\"\"\"\n        return bot_bridge.get_all_plugins()\n\n    def get_plugin_details(self, plugin_name: str) -> Optional[Dict[str, Any]]:\n        \"\"\"获取指定插件的详细信息\"\"\"\n        return bot_bridge.get_plugin_details(plugin_name)\n\n    async def enable_plugin(self, plugin_name: str) -> bool:\n        \"\"\"启用插件\"\"\"\n        return await bot_bridge.enable_plugin(plugin_name)\n\n    async def disable_plugin(self, plugin_name: str) -> bool:\n        \"\"\"禁用插件\"\"\"\n        return await bot_bridge.disable_plugin(plugin_name)\n\n    async def reload_plugin(self, plugin_name: str) -> bool:\n        \"\"\"重新加载插件\"\"\"\n        return await bot_bridge.reload_plugin(plugin_name)\n\n    def run_async(self, coro):\n        \"\"\"\n        安全地执行异步协程\n        \n        Args:\n            coro: 协程对象\n            \n        Returns:\n            协程执行结果\n        \"\"\"\n        loop = get_event_loop()\n\n        if loop.is_running():\n            # 如果循环正在运行，使用run_coroutine_threadsafe\n            future = asyncio.run_coroutine_threadsafe(coro, loop)\n            return future.result(timeout=30)  # 设置30秒超时\n        else:\n            # 否则直接运行\n            return loop.run_until_complete(coro)\n\n    def save_plugin_config(self, plugin_name: str, config_data: Dict[str, Any]) -> bool:\n        \"\"\"\n        保存插件配置\n        \n        Args:\n            plugin_name: 插件名称\n            config_data: 配置数据\n            \n        Returns:\n            bool: 保存是否成功\n        \"\"\"\n        try:\n            # 实现保存插件配置的逻辑\n            # 这里是一个占位实现，您需要根据实际情况完善\n            logger.log('WEBUI', f\"保存插件 {plugin_name} 配置\")\n            return True\n        except Exception as e:\n            logger.log('WEBUI', f\"保存插件配置失败: {str(e)}\")\n            return False\n\n\nplugin_service = PluginService()\n"
  },
  {
    "path": "WebUI/services/tool_service.py",
    "content": "import json\nimport os\nimport pathlib\nimport traceback\nfrom typing import Dict, List, Any, Optional, Callable\n\nfrom loguru import logger\n\n# 工具注册表\n_TOOLS_REGISTRY: Dict[str, Dict[str, Any]] = {}\n\n\ndef register_tool(tool_id: str, title: str, description: str,\n                  icon: str, handler_func: Callable, params: Optional[List[Dict[str, Any]]] = None) -> bool:\n    \"\"\"\n    注册工具到工具注册表\n    \n    Args:\n        tool_id: 工具ID，唯一标识\n        title: 工具标题\n        description: 工具描述\n        icon: 工具图标(Font Awesome图标名)\n        handler_func: 处理函数\n        params: 工具参数定义，默认为None\n        \n    Returns:\n        bool: 注册是否成功\n        \n    Raises:\n        ValueError: 当处理函数不可调用时\n    \"\"\"\n    if tool_id in _TOOLS_REGISTRY:\n        logger.log('WEBUI', f\"工具 {tool_id} 已存在，将被覆盖\")\n\n    # 检查处理函数是否有效\n    if not callable(handler_func):\n        error_msg = f\"工具 {tool_id} 的处理函数必须是可调用的\"\n        logger.log('WEBUI', f\"注册工具失败: {error_msg}\")\n        raise ValueError(error_msg)\n\n    # 注册工具\n    _TOOLS_REGISTRY[tool_id] = {\n        'id': tool_id,\n        'title': title,\n        'description': description,\n        'icon': icon,\n        'handler': handler_func,\n        'params': params or []\n    }\n\n    logger.log('WEBUI', f\"工具 {tool_id} 已注册成功\")\n    return True\n\n\ndef get_tools_list() -> List[Dict[str, Any]]:\n    \"\"\"\n    获取所有注册的工具列表（不包含处理函数）\n    \n    Returns:\n        List[Dict[str, Any]]: 工具列表，每个工具包含id、title、description、icon和params\n    \"\"\"\n    # 确保工具已加载\n    load_built_in_tools()\n\n    # 返回工具列表（不包含处理函数）\n    tools = []\n    for tool_id, tool in _TOOLS_REGISTRY.items():\n        tools.append({\n            'id': tool['id'],\n            'title': tool['title'],\n            'description': tool['description'],\n            'icon': tool['icon'],\n            'params': tool['params']\n        })\n\n    logger.log('WEBUI', f\"获取工具列表，共 {len(tools)} 个工具\")\n    return tools\n\n\ndef execute_tool(tool_id: str) -> Dict[str, Any]:\n    \"\"\"\n    执行指定ID的工具\n    \n    Args:\n        tool_id: 工具ID\n    \n    Returns:\n        Dict[str, Any]: 执行结果\n        \n    Raises:\n        ValueError: 当工具不存在时\n    \"\"\"\n    # 确保工具已加载\n    load_built_in_tools()\n\n    # 检查工具是否存在\n    if tool_id not in _TOOLS_REGISTRY:\n        error_msg = f\"工具 {tool_id} 不存在\"\n        logger.log('WEBUI', f\"执行工具失败: {error_msg}\")\n        raise ValueError(error_msg)\n\n    tool = _TOOLS_REGISTRY[tool_id]\n    handler = tool['handler']\n\n    try:\n        logger.log('WEBUI', f\"开始执行工具: {tool_id}\")\n        # 执行工具处理函数\n        result = handler()\n\n        # 默认返回格式\n        if result is None:\n            result = {'success': True}\n        elif not isinstance(result, dict):\n            result = {'success': True, 'data': result}\n\n        # 确保包含成功标志\n        if 'success' not in result:\n            result['success'] = True\n\n        logger.log('WEBUI', f\"工具 {tool_id} 执行成功\")\n        return result\n    except Exception as e:\n        error_msg = f\"执行工具 {tool_id} 出错: {str(e)}\"\n        logger.log('WEBUI', error_msg)\n        logger.log('WEBUI', traceback.format_exc())\n\n        return {\n            'success': False,\n            'error': str(e),\n            'stack': traceback.format_exc()\n        }\n\n\ndef load_built_in_tools() -> None:\n    \"\"\"\n    加载内置工具\n    \n    该函数会注册所有系统内置的工具。如需添加新工具，请在此处注册。\n    \"\"\"\n    # 如果已经加载，则跳过\n    if _TOOLS_REGISTRY:\n        return\n\n    logger.log('WEBUI', \"开始加载内置工具\")\n\n    # 注册内置工具\n    register_tool(\n        tool_id='reset_account',\n        title='登录新账号',\n        description='删除当前保存的账号文件，以登录新账号',\n        icon='user-plus',\n        handler_func=reset_account_handler\n    )\n\n    # 可以在这里注册更多内置工具\n\n    logger.log('WEBUI', f\"内置工具加载完成，共 {len(_TOOLS_REGISTRY)} 个工具\")\n\n\ndef reset_account_handler() -> Dict[str, Any]:\n    \"\"\"\n    重置账号文件处理函数\n    \n    删除现有账号信息，创建一个新的空账号文件，用于重新登录\n        \n    Returns:\n        Dict[str, Any]: 执行结果\n    \"\"\"\n    try:\n        # 账号文件路径\n        account_path = pathlib.Path(\"resource/robot_stat.json\")\n\n        # 检查文件是否存在\n        if not os.path.exists(account_path):\n            error_msg = '账号文件不存在'\n            logger.log('WEBUI', f\"重置账号失败: {error_msg}\")\n            return {\n                'success': False,\n                'error': error_msg\n            }\n\n        # 创建新的账号数据\n        new_data = {\n            \"wxid\": \"\",\n            \"device_name\": \"\",\n            \"device_id\": \"\"\n        }\n\n        # 写入文件\n        with open(account_path, 'w', encoding='utf-8') as f:\n            json.dump(new_data, f, ensure_ascii=False, indent=4)\n\n        logger.log('WEBUI', \"账号文件已成功重置\")\n        return {\n            'success': True,\n            'message': '账号文件已重置'\n        }\n    except Exception as e:\n        error_msg = f\"重置账号文件失败: {str(e)}\"\n        logger.log('WEBUI', error_msg)\n        logger.log('WEBUI', traceback.format_exc())\n        return {\n            'success': False,\n            'error': error_msg\n        }\n"
  },
  {
    "path": "WebUI/services/websocket_service.py",
    "content": "import os\nimport threading\nimport time\nfrom pathlib import Path\n\nfrom flask_socketio import SocketIO, emit\nfrom loguru import logger\n\nfrom WebUI.services.data_service import BOT_LOG_PATH\n\n# 创建SocketIO实例 - 但不在这里初始化，而是通过工厂函数传入\nsocketio = SocketIO()\n\n\nclass LogWatcher:\n    \"\"\"日志监控器，用于实时推送新的日志\"\"\"\n\n    def __init__(self, socketio_instance):\n        \"\"\"初始化日志监控器\n        \n        Args:\n            socketio_instance: SocketIO实例，用于推送WebSocket消息\n        \"\"\"\n        self.socketio = socketio_instance\n        self.running = False\n        self.watch_thread = None\n        self.last_position = 0\n        self.last_emit_time = 0\n        self.buffer = []\n        self.throttle_interval = 1.0  # 限制发送频率为1秒\n        self._init_watcher()\n\n    def _init_watcher(self):\n        \"\"\"初始化日志监控器，记录当前日志文件大小作为起始位置\"\"\"\n        if isinstance(BOT_LOG_PATH, str):\n            log_path = Path(BOT_LOG_PATH)\n        else:\n            log_path = BOT_LOG_PATH\n\n        if log_path.exists():\n            self.last_position = os.path.getsize(log_path)\n        else:\n            self.last_position = 0\n            logger.warning(f\"日志文件不存在: {log_path}\")\n\n    def start(self):\n        \"\"\"启动日志监控线程\"\"\"\n        if self.running:\n            return\n\n        self.running = True\n        self.watch_thread = threading.Thread(target=self._watch_log_file, daemon=True)\n        self.watch_thread.start()\n\n    def stop(self):\n        \"\"\"停止日志监控线程\"\"\"\n        self.running = False\n        if self.watch_thread and self.watch_thread.is_alive():\n            self.watch_thread.join(timeout=1.0)\n        logger.info(\"WebSocket日志监控服务已关闭\")\n\n    def _should_ignore_log(self, log_line):\n        \"\"\"判断是否应该忽略某条日志\n        \n        Args:\n            log_line: 日志行文本\n            \n        Returns:\n            bool: 如果应该忽略则返回True\n        \"\"\"\n        # 忽略WebSocket自身的调试日志，避免循环\n        if \"WebUI.services.websocket_service\" in log_line and \"已推送\" in log_line:\n            return True\n\n        # 忽略SocketIO的emitting日志\n        if log_line.startswith(\"emitting event\"):\n            return True\n\n        # 忽略空日志\n        if not log_line.strip():\n            return True\n\n        return False\n\n    def _emit_logs(self):\n        \"\"\"发送缓冲区中的日志\"\"\"\n        if not self.buffer:\n            return\n\n        # 过滤掉应该忽略的日志\n        filtered_logs = [log for log in self.buffer if not self._should_ignore_log(log)]\n\n        # 如果过滤后还有日志需要发送\n        if filtered_logs:\n            self.socketio.emit('new_logs', {'logs': filtered_logs})\n\n        # 无论是否发送，都清空缓冲区\n        self.buffer = []\n        self.last_emit_time = time.time()\n\n    def _watch_log_file(self):\n        \"\"\"监控日志文件变化并推送新日志到WebSocket连接\"\"\"\n        if isinstance(BOT_LOG_PATH, str):\n            log_path = Path(BOT_LOG_PATH)\n        else:\n            log_path = BOT_LOG_PATH\n\n        while self.running:\n            try:\n                if not log_path.exists():\n                    time.sleep(1)\n                    continue\n\n                current_size = os.path.getsize(log_path)\n\n                # 如果文件大小变小，说明日志被轮转或清空，重置位置\n                if current_size < self.last_position:\n                    self.last_position = 0\n\n                # 有新日志\n                if current_size > self.last_position:\n                    with open(log_path, 'r', encoding='utf-8') as f:\n                        # 移动到上次读取的位置\n                        f.seek(self.last_position)\n                        # 读取新增内容\n                        new_lines = f.readlines()\n                        new_logs = [line.strip() for line in new_lines if line.strip()]\n\n                        if new_logs:\n                            # 添加到缓冲区\n                            self.buffer.extend(new_logs)\n\n                            # 检查是否应该发送\n                            current_time = time.time()\n                            if current_time - self.last_emit_time >= self.throttle_interval:\n                                self._emit_logs()\n\n                    # 更新位置指针\n                    self.last_position = current_size\n\n                # 如果缓冲区有内容且超过了节流时间，发送日志\n                elif self.buffer and time.time() - self.last_emit_time >= self.throttle_interval:\n                    self._emit_logs()\n\n            except Exception as e:\n                logger.error(f\"监控日志文件出错: {str(e)}\")\n\n            # 短暂休眠，避免过高CPU占用\n            time.sleep(0.5)\n\n    def get_historical_logs(self, n=100):\n        \"\"\"获取历史日志\n        \n        Args:\n            n: 要获取的日志行数，默认100行\n            \n        Returns:\n            list: 日志行列表\n        \"\"\"\n        try:\n            if isinstance(BOT_LOG_PATH, str):\n                log_path = Path(BOT_LOG_PATH)\n            else:\n                log_path = BOT_LOG_PATH\n\n            if not log_path.exists():\n                return []\n\n            # 使用tail命令的逻辑，从文件末尾读取n行\n            with open(log_path, 'r', encoding='utf-8') as f:\n                # 先获取所有行\n                all_lines = f.readlines()\n\n            # 获取最后n行\n            last_n_lines = all_lines[-n:] if len(all_lines) > n else all_lines\n\n            # 过滤并处理日志行\n            logs = [line.strip() for line in last_n_lines if line.strip()]\n            filtered_logs = [log for log in logs if not self._should_ignore_log(log)]\n\n            return filtered_logs\n        except Exception as e:\n            logger.error(f\"获取历史日志出错: {str(e)}\")\n            return []\n\n\n# 全局变量，保存LogWatcher实例\nlog_watcher = None\n\n\ndef init_websocket():\n    \"\"\"初始化WebSocket服务，启动日志监控器\"\"\"\n    global log_watcher\n\n    # 确保只初始化一次\n    if log_watcher is None:\n        log_watcher = LogWatcher(socketio)\n        log_watcher.start()\n\n        # 注册事件处理函数\n        @socketio.on('request_logs')\n        def handle_request_logs(data):\n            \"\"\"处理客户端请求日志事件\n            \n            Args:\n                data: 客户端发送的数据，包含n表示请求的日志行数\n            \"\"\"\n            try:\n                # 获取客户端请求的日志行数，默认100行\n                n = data.get('n', 100) if isinstance(data, dict) else 100\n                # 获取历史日志\n                logs = log_watcher.get_historical_logs(n)\n                # 发送给请求的客户端\n                emit('new_logs', {'logs': logs})\n            except Exception as e:\n                logger.error(f\"处理日志请求出错: {str(e)}\")\n    else:\n        logger.debug(\"WebSocket日志监控服务已初始化\")\n\n\ndef shutdown_websocket():\n    \"\"\"关闭WebSocket服务，停止日志监控器\"\"\"\n    global log_watcher\n\n    if log_watcher is not None:\n        log_watcher.stop()\n        log_watcher = None\n        logger.info(\"WebSocket日志监控服务已关闭\")\n"
  },
  {
    "path": "WebUI/static/css/components/cards.css",
    "content": ".status-card {\n    margin-bottom: 20px;\n}\n\n.status-card .card-title {\n    font-size: 2rem;\n    font-weight: bold;\n}\n\n.status-icon {\n    width: 60px;\n    height: 60px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n}\n\n.control-card .card-body {\n    padding: 1.25rem;\n}\n\n.log-card .card-body {\n    padding: 0;\n}\n\n.tool-card {\n    transition: transform 0.3s;\n}\n\n.tool-card:hover {\n    transform: translateY(-5px);\n}\n\n.status-indicator {\n    width: 10px;\n    height: 10px;\n    border-radius: 50%;\n    display: inline-block;\n    margin-right: 5px;\n}\n\n.status-online {\n    background-color: #28a745;\n}\n\n.status-offline {\n    background-color: #dc3545;\n} "
  },
  {
    "path": "WebUI/static/css/components/file_browser.css",
    "content": ".file-browser-container {\n    margin-bottom: 20px;\n}\n\n.file-path-breadcrumb {\n    font-size: 0.9rem;\n    margin-bottom: 15px;\n    border-radius: 4px;\n    position: relative;\n}\n\n.file-path-breadcrumb .breadcrumb-item + .breadcrumb-item::before {\n    content: \"/\";\n    float: none;\n    padding-right: 0.5rem;\n    padding-left: 0.5rem;\n    user-select: text;\n}\n\n.full-path-container {\n    position: relative;\n    width: 100%;\n}\n\n.full-path {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    opacity: 0;\n    height: 1px;\n    overflow: hidden;\n    white-space: nowrap;\n    user-select: all;\n}\n\n.file-tree {\n    max-height: 500px;\n    overflow-y: auto;\n    padding: 10px;\n}\n\n.file-tree ul {\n    list-style-type: none;\n    padding-left: 20px;\n}\n\n.file-tree li {\n    padding: 2px 0;\n}\n\n.file-tree .folder-node,\n.file-tree .file-node {\n    cursor: pointer;\n    padding: 3px 5px;\n    border-radius: 3px;\n}\n\n.file-tree .folder-node:hover,\n.file-tree .file-node:hover {\n    background-color: #f0f0f0;\n}\n\n.file-tree .folder-node.active,\n.file-tree .file-node.active {\n    background-color: #e9ecef;\n}\n\n.file-tree .folder-icon {\n    color: #ffc107;\n}\n\n.file-tree .file-icon {\n    color: #6c757d;\n}\n\n.file-list {\n    max-height: 500px;\n    overflow-y: auto;\n}\n\n.file-list table {\n    margin-bottom: 0;\n}\n\n.file-list .file-item {\n    cursor: pointer;\n}\n\n.file-list .file-item:hover {\n    background-color: #f8f9fa;\n}\n\n.file-grid {\n    display: flex;\n    flex-wrap: wrap;\n    padding: 10px;\n    gap: 15px;\n}\n\n.file-grid-item {\n    width: 100px;\n    height: 100px;\n    border: 1px solid #eee;\n    border-radius: 5px;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    cursor: pointer;\n    transition: all 0.2s;\n}\n\n.file-grid-item:hover {\n    background-color: #f8f9fa;\n    border-color: #ddd;\n}\n\n.file-grid-item .file-icon {\n    font-size: 2rem;\n    margin-bottom: 5px;\n}\n\n.file-grid-item .file-grid-name {\n    font-size: 0.8rem;\n    text-align: center;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    width: 90%;\n    white-space: nowrap;\n}\n\n.full-path-display {\n    font-family: monospace;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    border-radius: 4px;\n    user-select: text;\n    padding-left: 8px;\n    padding-right: 8px;\n}\n\n.full-path-display:empty {\n    display: none;\n} "
  },
  {
    "path": "WebUI/static/css/components/file_viewer.css",
    "content": "/* 文件查看器组件样式 */\n\n/* 容器样式 */\n.file-viewer-container {\n    margin-bottom: 30px;\n}\n\n/* 头部样式 */\n.file-viewer-header {\n    margin-bottom: 15px;\n}\n\n.file-name {\n    color: #2c3e50;\n    margin: 0;\n    font-weight: 500;\n}\n\n/* 文件信息样式 */\n.file-info-item {\n    display: inline-flex;\n    align-items: center;\n    padding: 6px 12px;\n    margin-right: 12px;\n    margin-bottom: 6px;\n    background-color: #f8f9fa;\n    border-radius: 4px;\n    font-size: 0.875rem;\n}\n\n.file-info-item strong {\n    color: #495057;\n    margin-right: 6px;\n}\n\n.file-info-item span {\n    color: #6c757d;\n}\n\n/* 编辑器控件样式 */\n.editor-controls {\n    display: flex;\n    align-items: center;\n    gap: 12px;\n    margin-bottom: 0;\n    flex: 1;\n    flex-wrap: nowrap;\n}\n\n.control-item {\n    flex: 0 0 auto;\n    min-width: auto;\n}\n\n.control-item .input-group {\n    border-radius: 4px;\n    overflow: hidden;\n    border: 1px solid #ced4da;\n}\n\n.control-item .input-group-text {\n    min-width: 85px;\n    font-size: 0.85rem;\n    background-color: #e9ecef;\n    border: none;\n    border-right: 1px solid #ced4da;\n    color: #495057;\n    font-weight: 500;\n}\n\n.control-item select,\n.control-item .input-group-text:last-child {\n    font-size: 0.85rem;\n    padding-right: 1.75rem;\n    border: none;\n    background-color: #f8f9fa;\n}\n\n.control-item .form-check-input {\n    margin: 0;\n    cursor: pointer;\n}\n\n/* 编辑器容器样式 */\n.monaco-editor-container {\n    border: 1px solid #dee2e6;\n    border-radius: 4px;\n    overflow: hidden;\n    height: 600px !important;\n    width: 100% !important;\n}\n\n/* 加载状态样式 */\n.loading-container {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    padding: 40px;\n    background-color: #f8f9fa;\n    border-radius: 4px;\n    height: 100%;\n}\n\n.loading-container i {\n    font-size: 2rem;\n    color: #6c757d;\n    margin-bottom: 15px;\n}\n\n.loading-container p {\n    color: #6c757d;\n    margin: 0;\n}\n\n/* 操作按钮样式 */\n.editor-action-buttons {\n    display: flex;\n    gap: 8px;\n    margin-left: 15px;\n    flex-shrink: 0;\n}\n\n.editor-action-buttons .btn {\n    display: inline-flex;\n    align-items: center;\n    gap: 6px;\n    padding: 0.375rem 0.75rem;\n    font-size: 0.875rem;\n}\n\n.editor-action-buttons .btn i {\n    font-size: 0.875rem;\n}\n\n/* 编辑器头部样式 */\n.card-header.bg-light {\n    background-color: #f8f9fa !important;\n    border-bottom: 1px solid #dee2e6;\n    padding: 1rem;\n}\n\n.card-header .d-flex {\n    flex-wrap: wrap;\n    gap: 1rem;\n}\n\n/* 响应式调整 */\n@media (max-width: 992px) {\n    .editor-controls {\n        flex-wrap: wrap;\n    }\n\n    .control-item {\n        flex: 1 1 auto;\n    }\n\n    .editor-action-buttons {\n        margin-top: 15px;\n        width: 100%;\n        justify-content: flex-end;\n        margin-left: 0;\n    }\n} "
  },
  {
    "path": "WebUI/static/css/components/loading.css",
    "content": ".loading-container {\n    padding: 15px;\n}\n\n.spinner-border-lg {\n    width: 3rem;\n    height: 3rem;\n}\n\n.full-page-loader {\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: rgba(255, 255, 255, 0.8);\n    z-index: 9999;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n}\n\n.loader-content {\n    text-align: center;\n    padding: 20px;\n    background-color: white;\n    border-radius: 8px;\n    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\n}\n\n.content-loader {\n    padding: 15px;\n    background-color: #fff;\n    border-radius: 5px;\n}\n\n.skeleton-line {\n    background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);\n    background-size: 200% 100%;\n    animation: shimmer 1.5s infinite;\n    border-radius: 4px;\n    margin-bottom: 10px;\n}\n\n@keyframes shimmer {\n    0% {\n        background-position: -200% 0;\n    }\n    100% {\n        background-position: 200% 0;\n    }\n} "
  },
  {
    "path": "WebUI/static/css/components/modals.css",
    "content": ".modal-content {\n    border: none;\n    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);\n}\n\n.modal-header {\n    border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n}\n\n.modal-footer {\n    border-top: 1px solid rgba(0, 0, 0, 0.05);\n}\n\n.modal-body {\n    padding: 1.5rem;\n}\n\n.modal-loading {\n    text-align: center;\n    padding: 2rem;\n}\n\n.modal-loading .spinner-border {\n    width: 3rem;\n    height: 3rem;\n}\n\n.modal-loading p {\n    margin-top: 1rem;\n    color: #6c757d;\n}\n\n.modal-form .form-group {\n    margin-bottom: 1rem;\n}\n\n.modal-form label {\n    font-weight: 500;\n}\n\n.modal-form .form-text {\n    font-size: 0.875rem;\n    color: #6c757d;\n}\n\n.modal-confirm .modal-body {\n    text-align: center;\n    padding: 2rem;\n}\n\n.modal-confirm .icon-box {\n    width: 80px;\n    height: 80px;\n    margin: 0 auto;\n    border-radius: 50%;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    margin-bottom: 1rem;\n}\n\n.modal-confirm .icon-box i {\n    font-size: 2rem;\n}\n\n.modal-confirm.success .icon-box {\n    background-color: #d4edda;\n    color: #28a745;\n}\n\n.modal-confirm.warning .icon-box {\n    background-color: #fff3cd;\n    color: #ffc107;\n}\n\n.modal-confirm.danger .icon-box {\n    background-color: #f8d7da;\n    color: #dc3545;\n} "
  },
  {
    "path": "WebUI/static/css/custom.css",
    "content": "/* 文件浏览器调整 */\n.file-browser-container {\n    border: 1px solid #dee2e6;\n    border-radius: 0.5rem;\n    background: white;\n}\n\n.file-browser-header {\n    padding: 1rem;\n    border-bottom: 1px solid #dee2e6;\n}\n\n/* 优化文件树结构 */\n.file-tree {\n    max-height: 70vh;\n    overflow-y: auto;\n    padding: 0.5rem;\n}\n\n.file-tree .folder-node,\n.file-tree .file-node {\n    padding: 0.25rem 0.5rem;\n    border-radius: 4px;\n    transition: all 0.2s;\n}\n\n.file-tree ul {\n    padding-left: 1rem;\n}\n\n/* 优化文件列表 */\n.file-list {\n    max-height: 70vh;\n    border-radius: 0.5rem;\n}\n\n.file-list table {\n    margin-bottom: 0;\n}\n\n.file-item:hover {\n    background-color: #f8f9fa;\n}\n\n/* 调整网格视图 */\n.file-grid {\n    gap: 0.75rem;\n    padding: 0.75rem;\n}\n\n.file-grid-item {\n    width: 120px;\n    height: 120px;\n    padding: 0.75rem;\n} "
  },
  {
    "path": "WebUI/static/css/pages/about.css",
    "content": "/* 关于页面样式 */\n.about-container {\n    padding: 20px;\n}\n\n.about-header {\n    margin-bottom: 30px;\n    text-align: center;\n}\n\n.about-header h1 {\n    font-size: 2.5rem;\n    color: #333;\n    margin-bottom: 15px;\n}\n\n.about-section {\n    margin-bottom: 40px;\n    padding: 20px;\n    background: #fff;\n    border-radius: 8px;\n    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n}\n\n.about-section h2 {\n    color: #2c3e50;\n    margin-bottom: 20px;\n    padding-bottom: 10px;\n    border-bottom: 2px solid #eee;\n}\n\n.about-section p {\n    color: #666;\n    line-height: 1.6;\n    margin-bottom: 15px;\n}\n\n.version-info {\n    background: #f8f9fa;\n    padding: 15px;\n    border-radius: 6px;\n    margin-bottom: 20px;\n}\n\n.version-info .label {\n    font-weight: bold;\n    color: #495057;\n    margin-right: 10px;\n}\n\n.version-info .value {\n    color: #6c757d;\n}\n\n.system-info {\n    display: grid;\n    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n    gap: 20px;\n    margin-top: 20px;\n}\n\n.system-info-item {\n    background: #f8f9fa;\n    padding: 15px;\n    border-radius: 6px;\n}\n\n.system-info-item .label {\n    font-weight: bold;\n    color: #495057;\n    margin-bottom: 5px;\n}\n\n.system-info-item .value {\n    color: #6c757d;\n    word-break: break-all;\n}\n\n/* 响应式调整 */\n@media (max-width: 768px) {\n    .about-container {\n        padding: 15px !important;\n    }\n\n    .about-header h1 {\n        font-size: 2rem;\n    }\n\n    .about-section {\n        padding: 15px;\n    }\n\n    .system-info {\n        grid-template-columns: 1fr;\n    }\n\n    .lead {\n        font-size: 1.1rem;\n    }\n} "
  },
  {
    "path": "WebUI/static/css/pages/auth.css",
    "content": "/* 登录页面专用样式 */\n.login-container {\n    max-width: 400px;\n    margin: 0 auto;\n    padding: 40px 0;\n    display: flex;\n    flex-direction: column;\n    justify-content: center;\n    min-height: 100vh;\n}\n\n.login-header {\n    text-align: center;\n    margin-bottom: 30px;\n}\n\n.login-header h1 {\n    font-size: 2.2rem;\n    font-weight: 600;\n    color: #333;\n}\n\n.login-card {\n    border-radius: 10px;\n    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.12);\n    border: none;\n}\n\n.login-card .card-title {\n    color: #444;\n    font-weight: 500;\n}\n\n.login-footer {\n    margin-top: 2rem;\n    text-align: center;\n}\n\n/* 输入框样式优化 */\n.login-card .form-control {\n    padding: 0.75rem 1rem;\n    border-radius: 5px;\n}\n\n.login-card .btn-primary {\n    padding: 0.75rem 1.5rem;\n    font-weight: 500;\n    border-radius: 5px;\n}\n\n/* 移除通知容器位置调整，使用style.css中的统一定义 */ "
  },
  {
    "path": "WebUI/static/css/pages/config.css",
    "content": "/* 配置页面样式 */\n.card {\n    margin-top: 15px;\n    margin-bottom: 15px;\n}\n\n.config-section {\n    margin-bottom: 20px;\n    border: 1px solid #ddd;\n    border-radius: 5px;\n    overflow: hidden;\n}\n\n.config-header {\n    background-color: #f8f9fa;\n    padding: 10px 15px;\n    border-bottom: 1px solid #ddd;\n    cursor: pointer;\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n}\n\n.config-header h5 {\n    margin: 0;\n}\n\n.config-body {\n    padding: 15px;\n}\n\n.config-field {\n    margin-bottom: 15px;\n}\n\n.config-field label {\n    font-weight: 500;\n}\n\n.config-field .form-text {\n    font-size: 0.85rem;\n}\n\n.save-btn-fixed {\n    position: fixed;\n    bottom: 20px;\n    right: 20px;\n    z-index: 1000;\n}\n\n.array-item {\n    margin-bottom: 10px;\n    display: flex;\n    align-items: center;\n}\n\n.array-item .form-control {\n    flex: 1;\n    margin-right: 10px;\n}\n\n.array-controls {\n    margin-top: 10px;\n}\n\n.object-property {\n    margin-bottom: 15px;\n    border: 1px solid #eee;\n    border-radius: 5px;\n    padding: 10px;\n}\n\n/* 配置表单验证样式 */\n.was-validated .form-control:invalid {\n    border-color: #dc3545;\n    padding-right: calc(1.5em + 0.75rem);\n    background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e\");\n    background-repeat: no-repeat;\n    background-position: right calc(0.375em + 0.1875rem) center;\n    background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .form-control:valid {\n    border-color: #28a745;\n    padding-right: calc(1.5em + 0.75rem);\n    background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\");\n    background-repeat: no-repeat;\n    background-position: right calc(0.375em + 0.1875rem) center;\n    background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n/* 配置折叠图标动画 */\n.config-header i {\n    transition: transform 0.2s ease-in-out;\n}\n\n.config-header[aria-expanded=\"true\"] i {\n    transform: rotate(180deg);\n}\n\n/* 配置描述文本样式 */\n.config-description {\n    color: #6c757d;\n    font-size: 0.9rem;\n    line-height: 1.5;\n}\n\n/* 配置字段组样式 */\n.config-group {\n    border: 1px solid #e9ecef;\n    border-radius: 4px;\n    padding: 15px;\n    margin-bottom: 20px;\n}\n\n.config-group-title {\n    font-size: 1rem;\n    font-weight: 500;\n    margin-bottom: 15px;\n    color: #495057;\n}\n\n/* 响应式调整 */\n@media (max-width: 768px) {\n    .save-btn-fixed {\n        width: calc(100% - 30px);\n        right: 15px;\n        bottom: 15px;\n    }\n\n    .config-header {\n        padding: 8px 12px;\n    }\n\n    .config-body {\n        padding: 12px;\n    }\n\n    .array-item {\n        flex-direction: column;\n    }\n\n    .array-item .form-control {\n        margin-right: 0;\n        margin-bottom: 10px;\n    }\n\n    .array-item .btn {\n        width: 100%;\n    }\n} "
  },
  {
    "path": "WebUI/static/css/pages/explorer.css",
    "content": "/* 文件浏览器页面样式 */\n.explorer-container {\n    min-height: calc(100vh - 120px);\n}\n\n/* 文件浏览器头部 */\n.explorer-header {\n    background-color: #f8f9fa;\n    border-bottom: 1px solid #dee2e6;\n    padding: 1rem;\n    margin-bottom: 1rem;\n}\n\n.explorer-title {\n    font-size: 1.25rem;\n    font-weight: 500;\n    margin: 0;\n}\n\n/* 文件浏览器工具栏 */\n.explorer-toolbar {\n    display: flex;\n    align-items: center;\n    gap: 0.5rem;\n    margin-bottom: 1rem;\n}\n\n.explorer-toolbar .btn-group {\n    margin-left: auto;\n}\n\n/* 文件浏览器内容区 */\n.explorer-content {\n    background-color: #fff;\n    border: 1px solid #dee2e6;\n    border-radius: 0.25rem;\n    padding: 1rem;\n}\n\n/* 文件浏览器面包屑导航 */\n.explorer-breadcrumb {\n    background-color: transparent;\n    padding: 0;\n    margin-bottom: 1rem;\n}\n\n.explorer-breadcrumb .breadcrumb-item + .breadcrumb-item::before {\n    content: \"/\";\n}\n\n/* 文件浏览器列表视图 */\n.explorer-list {\n    width: 100%;\n}\n\n.explorer-list th {\n    font-weight: 500;\n    border-top: none;\n}\n\n.explorer-list td {\n    vertical-align: middle;\n}\n\n/* 文件浏览器网格视图 */\n.explorer-grid {\n    display: grid;\n    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));\n    gap: 1rem;\n    padding: 1rem;\n}\n\n.explorer-grid-item {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    text-align: center;\n    padding: 1rem;\n    border: 1px solid #dee2e6;\n    border-radius: 0.25rem;\n    cursor: pointer;\n    transition: all 0.2s ease-in-out;\n}\n\n.explorer-grid-item:hover {\n    background-color: #f8f9fa;\n    border-color: #adb5bd;\n}\n\n.explorer-grid-item i {\n    font-size: 2rem;\n    margin-bottom: 0.5rem;\n}\n\n.explorer-grid-item span {\n    font-size: 0.875rem;\n    word-break: break-word;\n}\n\n/* 文件图标颜色 */\n.file-icon.folder {\n    color: #ffd700;\n}\n\n.file-icon.image {\n    color: #28a745;\n}\n\n.file-icon.text {\n    color: #17a2b8;\n}\n\n.file-icon.code {\n    color: #007bff;\n}\n\n.file-icon.archive {\n    color: #6c757d;\n}\n\n/* 响应式调整 */\n@media (max-width: 768px) {\n    .explorer-grid {\n        grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));\n    }\n\n    .explorer-toolbar {\n        flex-wrap: wrap;\n    }\n\n    .explorer-toolbar .btn-group {\n        margin-left: 0;\n        margin-top: 0.5rem;\n        width: 100%;\n    }\n\n    .explorer-toolbar .btn-group .btn {\n        flex: 1;\n    }\n} "
  },
  {
    "path": "WebUI/static/css/pages/logs.css",
    "content": "/* 日志页面特定样式 */\n.logs-container {\n    padding-top: 1.5rem;\n    padding-bottom: 2rem;\n    min-height: calc(100vh - 60px);\n}\n\n/* 日志容器样式 */\n.log-container {\n    height: 400px;\n    margin-bottom: 15px;\n}\n\n/* 控制按钮样式 */\n.log-controls {\n    margin-bottom: 10px;\n}\n\n.log-controls button {\n    margin-right: 5px;\n}\n\n/* 控制面板样式 */\n.log-panel {\n    margin-bottom: 20px;\n}\n\n.log-panel .card-header {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n}\n\n/* 暗色主题样式调整 */\n[data-bs-theme=\"dark\"] .log-container,\n[data-bs-theme=\"dark\"] .log-viewer {\n    background-color: #121212;\n    border: 1px solid #333;\n}\n\n/* 页脚间距 */\n.page-footer-space {\n    height: 30px;\n} "
  },
  {
    "path": "WebUI/static/css/pages/overview.css",
    "content": "/* 总览页面样式 */\n\n/* 页面布局 */\n.main-content {\n    padding-top: 1.5rem;\n    padding-bottom: 2rem;\n    min-height: calc(100vh - 60px); /* 防止页面过短导致滚动问题 */\n}\n\n/* 状态图标 */\n.status-icon {\n    transition: all 0.2s ease-in-out;\n}\n\n.status-icon:hover {\n    transform: scale(1.05);\n}\n\n/* 日志查看器 */\n.log-viewer {\n    background-color: #1e1e1e;\n    color: #f0f0f0;\n    font-family: monospace;\n    font-size: 14px;\n    overflow-y: auto;\n    border-radius: 5px;\n    padding: 0;\n    white-space: pre;\n    overscroll-behavior: contain;\n    height: 450px;\n}\n\n.log-line {\n    padding: 0 10px;\n    display: block;\n    letter-spacing: 0;\n    line-height: 1;\n}\n\n.log-line:hover {\n    background-color: rgba(255, 255, 255, 0.1);\n}\n\n/* 日志级别样式 */\n.log-debug {\n    color: rgba(14, 102, 234, 0.8);\n}\n\n.log-info {\n    color: #ffffff;\n}\n\n.log-warning {\n    color: #ffea00;\n}\n\n.log-error {\n    color: #f14c4c;\n}\n\n.log-success {\n    color: #01C801FF\n}\n\n.log-critical {\n    color: #ff0000;\n}\n\n.log-webui {\n    color: #2493cb;\n}\n\n/* 页面标题 */\n.xybot-title {\n    font-size: 1.5rem;\n    font-weight: bold;\n    color: #333;\n}\n\n/* 页面底部空间 */\n.page-footer-space {\n    height: 30px;\n}\n\n/* 卡片样式 */\n.card {\n    border: none;\n    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);\n    transition: all 0.3s ease;\n}\n\n.card:hover {\n    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\n}\n\n.card-header {\n    background-color: transparent;\n    border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n}\n\n.card-title {\n    color: #2c3e50;\n    margin-bottom: 0;\n}\n\n/* 表格样式 */\n.table-sm th {\n    font-weight: 500;\n    color: #6c757d;\n}\n\n.table-sm td {\n    color: #2c3e50;\n    text-align: right;\n}\n\n/* 确保状态徽章对齐一致 */\n.table-sm td .badge {\n    display: inline-block;\n    min-width: 60px;\n}\n\n/* 按钮样式 */\n.btn {\n    transition: all 0.2s ease;\n}\n\n.btn:hover {\n    transform: translateY(-1px);\n    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n}\n\n.btn:active {\n    transform: translateY(0);\n    box-shadow: none;\n}\n\n/* 账号信息卡片样式 */\n.account-info-card {\n    height: 100%;\n}\n\n.account-info-card .card-body {\n    padding: 2rem 1.5rem;\n}\n\n/* 头像样式 */\n.profile-picture-container {\n    position: relative;\n    width: 150px;\n    height: 150px;\n    min-width: 150px; /* 防止容器收缩 */\n    background-color: #f0f0f0;\n    margin: 0 auto;\n}\n\n.profile-picture {\n    width: 100%;\n    height: 100%;\n    border-radius: 0;\n    background-color: #f5f5f5;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    overflow: hidden;\n    border: none;\n    box-shadow: none;\n}\n\n.profile-picture img {\n    width: 100%;\n    height: 100%;\n    object-fit: cover;\n}\n\n.profile-picture i {\n    font-size: 5rem;\n    color: #aaa;\n}\n\n/* 响应式调整 */\n@media (max-width: 768px) {\n    .main-content {\n        padding-top: 1rem;\n        padding-bottom: 1rem;\n    }\n\n    .log-viewer {\n        height: 300px;\n    }\n\n    .card {\n        margin-bottom: 1rem;\n    }\n\n    .btn {\n        padding: 0.375rem 0.75rem;\n        font-size: 0.875rem;\n    }\n\n    .profile-picture-container {\n        width: 100px;\n        height: 100px;\n    }\n\n    .profile-picture i {\n        font-size: 3.5rem;\n    }\n} "
  },
  {
    "path": "WebUI/static/css/pages/plugin.css",
    "content": "/* 插件管理页面样式 */\n\n/* 容器样式 */\n.plugin-container {\n    padding: 1.5rem 1rem;\n}\n\n/* 头部样式 */\n.plugin-header {\n    margin-bottom: 1.5rem;\n}\n\n/* 搜索框样式 */\n.plugin-search {\n    width: 200px;\n    margin-left: 8px;\n}\n\n.plugin-search .form-control {\n    border-radius: 4px 0 0 4px;\n    padding: 0.25rem 1rem;\n    transition: all 0.2s ease;\n}\n\n.plugin-search .input-group-text {\n    border-radius: 0 4px 4px 0;\n    background-color: #f8f9fa;\n    border-left: none;\n}\n\n.plugin-search .form-control:focus {\n    box-shadow: 0 0 0 0.2rem rgba(58, 134, 255, 0.25);\n    border-color: #3a86ff;\n    z-index: 3;\n}\n\n/* 插件卡片样式 */\n.plugin-card {\n    margin-bottom: 1rem;\n    transition: all 0.3s ease;\n}\n\n.plugin-card:hover {\n    transform: translateY(-2px);\n    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n}\n\n.plugin-info {\n    padding: 1rem;\n}\n\n.plugin-title {\n    font-size: 1.25rem;\n    font-weight: 600;\n    color: #2c3e50;\n    margin-bottom: 0.5rem;\n}\n\n.plugin-meta {\n    font-size: 0.875rem;\n    color: #6c757d;\n    margin-bottom: 0.5rem;\n}\n\n.plugin-description {\n    color: #495057;\n    font-size: 0.9rem;\n    line-height: 1.5;\n}\n\n/* 按钮样式 */\n.btn-soft-primary {\n    color: #3a86ff;\n    background-color: rgba(58, 134, 255, 0.1);\n    border: 1px solid rgba(58, 134, 255, 0.1);\n}\n\n.btn-soft-primary:hover {\n    color: white;\n    background-color: #3a86ff;\n}\n\n.btn-soft-success {\n    color: #38b000;\n    background-color: rgba(56, 176, 0, 0.1);\n    border: 1px solid rgba(56, 176, 0, 0.1);\n}\n\n.btn-soft-success:hover {\n    color: white;\n    background-color: #38b000;\n}\n\n.btn-soft-danger {\n    color: #ff006e;\n    background-color: rgba(255, 0, 110, 0.1);\n    border: 1px solid rgba(255, 0, 110, 0.1);\n}\n\n.btn-soft-danger:hover {\n    color: white;\n    background-color: #ff006e;\n}\n\n.btn-soft-secondary {\n    color: #6c757d;\n    background-color: rgba(108, 117, 125, 0.1);\n    border: 1px solid rgba(108, 117, 125, 0.1);\n}\n\n.btn-soft-secondary:hover {\n    color: white;\n    background-color: #6c757d;\n}\n\n/* 按钮动效 */\n.plugin-action-btn, .reload-btn, .config-btn {\n    transition: all 0.2s ease;\n    min-width: 38px;\n}\n\n.plugin-action-btn {\n    min-width: 100px;\n}\n\n.plugin-action-btn:not(:disabled):hover {\n    transform: translateY(-1px);\n    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n}\n\n.reload-btn:not(:disabled):hover {\n    transform: rotate(360deg);\n}\n\n.config-btn:not(:disabled):hover {\n    transform: scale(1.1);\n}\n\n.btn:disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n}\n\n/* 按钮间距 */\n.gap-3 > * {\n    margin: 0 4px;\n}\n\n.gap-4 {\n    gap: 1.5rem !important;\n}\n\n/* 插件详情模态框样式 */\n.plugin-detail-modal .modal-body {\n    padding: 1.5rem;\n}\n\n.plugin-detail-tabs {\n    margin-bottom: 1.5rem;\n}\n\n.plugin-info-section {\n    margin-bottom: 2rem;\n}\n\n.plugin-info-section dt {\n    color: #6c757d;\n    font-weight: 500;\n}\n\n.plugin-info-section dd {\n    color: #2c3e50;\n}\n\n/* Markdown内容样式 */\n.markdown-content {\n    padding: 1rem;\n    background-color: #f8f9fa;\n    border-radius: 4px;\n}\n\n/* 配置编辑器样式 */\n#plugin-config-editor {\n    height: 400px;\n    margin-bottom: 1rem;\n}\n\n/* 文件浏览器模态框样式 */\n#fileBrowserModal .modal-dialog {\n    max-width: 90%;\n    height: 90vh;\n}\n\n#fileBrowserModal .modal-body {\n    height: calc(90vh - 120px);\n    overflow: hidden;\n}\n\n#plugin-file-browser .file-list {\n    max-height: none;\n    height: 100%;\n}\n\n/* 响应式调整 */\n@media (max-width: 768px) {\n    .plugin-search {\n        width: 150px;\n    }\n\n    .plugin-action-btn {\n        min-width: 80px;\n    }\n\n    .plugin-card .card-body {\n        padding: 1rem;\n    }\n\n    .plugin-meta {\n        flex-direction: column;\n    }\n\n    .plugin-meta > * {\n        margin-bottom: 0.5rem;\n    }\n} "
  },
  {
    "path": "WebUI/static/css/pages/tools.css",
    "content": "/* 工具箱页面样式 */\n\n/* 工具卡片样式 */\n.tool-card {\n    transition: all 0.3s ease;\n    border: 1px solid #dee2e6;\n    border-radius: 0.25rem;\n    overflow: hidden;\n    height: 100%;\n    display: flex;\n    flex-direction: column;\n}\n\n/* 工具卡片头部 */\n.tool-header {\n    background-color: #f8f9fa;\n    padding: 15px;\n    display: flex;\n    align-items: center;\n    border-bottom: 1px solid #dee2e6;\n}\n\n.tool-icon {\n    font-size: 2rem;\n    margin-right: 15px;\n    color: #007bff;\n}\n\n.tool-title {\n    margin: 0;\n    font-size: 1.2rem;\n    font-weight: 500;\n}\n\n/* 工具卡片内容 */\n.tool-body {\n    padding: 15px;\n    flex-grow: 1;\n}\n\n.tool-description {\n    color: #6c757d;\n    margin-bottom: 15px;\n}\n\n/* 工具卡片底部 */\n.tool-footer {\n    padding: 15px;\n    border-top: 1px solid #dee2e6;\n    display: flex;\n    justify-content: flex-end;\n}\n\n/* 执行状态样式 */\n.execution-status {\n    margin-top: 10px;\n    padding: 10px;\n    border-radius: 5px;\n    display: none;\n}\n\n.execution-status.success {\n    background-color: #d4edda;\n    color: #155724;\n    border: 1px solid #c3e6cb;\n}\n\n.execution-status.error {\n    background-color: #f8d7da;\n    color: #721c24;\n    border: 1px solid #f5c6cb;\n}\n\n.execution-status.loading {\n    background-color: #e2e3e5;\n    color: #383d41;\n    border: 1px solid #d6d8db;\n}\n\n/* 执行日志样式 */\n.execution-log {\n    max-height: 300px;\n    overflow-y: auto;\n    font-family: monospace;\n    background-color: #f8f9fa;\n    padding: 10px;\n    border-radius: 5px;\n    font-size: 0.9rem;\n    margin-top: 10px;\n    white-space: pre-wrap;\n    word-break: break-all;\n}\n\n/* 加载状态样式 */\n.loading-container {\n    text-align: center;\n    padding: 3rem 0;\n}\n\n.loading-container .spinner-border {\n    width: 3rem;\n    height: 3rem;\n}\n\n.loading-container p {\n    margin-top: 1rem;\n    color: #6c757d;\n}\n\n/* 响应式调整 */\n@media (max-width: 768px) {\n    .tool-header {\n        padding: 12px;\n    }\n\n    .tool-icon {\n        font-size: 1.5rem;\n        margin-right: 12px;\n    }\n\n    .tool-title {\n        font-size: 1.1rem;\n    }\n\n    .tool-body,\n    .tool-footer {\n        padding: 12px;\n    }\n\n    .execution-log {\n        max-height: 200px;\n        font-size: 0.8rem;\n    }\n} "
  },
  {
    "path": "WebUI/static/css/style.css",
    "content": "/* ========== 全局基础样式 ========== */\nbody {\n    padding-top: 0;\n    min-height: 100vh;\n    display: flex;\n    flex-direction: column;\n    overflow-x: hidden;\n}\n\n.content {\n    flex: 1;\n    overflow-y: auto;\n    height: 100vh;\n    margin-left: 16.666667%;\n    width: 83.333333%;\n    padding: 15px;\n}\n\n/* ========== 侧边栏样式 ========== */\n.sidebar {\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 16.666667%;\n    height: 100vh;\n    background-color: #343a40;\n    overflow-y: hidden;\n    z-index: 100;\n    display: flex;\n    flex-direction: column;\n}\n\n.sidebar-header {\n    padding: 15px;\n    background-color: #2c3136;\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n.sidebar-header .navbar-brand {\n    color: #fff;\n    font-size: 1.25rem;\n    margin-right: 0;\n    font-weight: bold;\n    text-decoration: none;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n}\n\n.sidebar-actions {\n    display: flex;\n    align-items: center;\n    padding: 10px 15px;\n    border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n.sidebar-actions .nav-link {\n    color: rgba(255, 255, 255, 0.75);\n    padding: 5px 10px;\n    font-size: 0.9rem;\n}\n\n.sidebar-actions .nav-link:hover {\n    color: #fff;\n}\n\n.sidebar-content {\n    padding-top: 15px;\n    overflow-y: hidden;\n    flex: 1;\n    display: flex;\n    flex-direction: column;\n    justify-content: space-between;\n}\n\n.sidebar-menu {\n    flex: 1;\n}\n\n.sidebar-footer {\n    padding: 10px 15px;\n    border-top: 1px solid rgba(255, 255, 255, 0.1);\n}\n\n.sidebar .nav-link {\n    color: rgba(255, 255, 255, 0.75);\n    padding: 8px 15px;\n}\n\n.sidebar .nav-link:hover {\n    color: rgba(255, 255, 255, 1);\n}\n\n.sidebar .nav-link.active {\n    color: #fff;\n    background-color: rgba(255, 255, 255, 0.1);\n}\n\n.sidebar .nav-link i {\n    margin-right: 10px;\n    width: 20px;\n    text-align: center;\n}\n\n/* ========== 页脚样式 ========== */\n.footer {\n    background-color: #f5f5f5;\n    padding: 1rem 0;\n    margin-top: auto;\n    width: 83.333333%;\n    margin-left: 16.666667%;\n}\n\n/* ========== 通用卡片样式 ========== */\n.card {\n    margin-bottom: 20px;\n    border: none;\n    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);\n    transition: all 0.3s ease;\n}\n\n.card:hover {\n    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\n}\n\n.card-header {\n    background-color: transparent;\n    border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n}\n\n.card-title {\n    color: #2c3e50;\n    margin-bottom: 0;\n}\n\n/* ========== 通用按钮样式 ========== */\n.btn {\n    transition: all 0.2s ease;\n}\n\n.btn:hover {\n    transform: translateY(-1px);\n    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n}\n\n.btn:active {\n    transform: translateY(0);\n    box-shadow: none;\n}\n\n.btn-primary {\n    background-color: #3a86ff;\n    border-color: #3a86ff;\n    padding: 0.5rem 1.5rem;\n}\n\n.btn-primary:hover {\n    background-color: #2c3e50;\n    border-color: #2c3e50;\n    transform: translateY(-2px);\n    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\n}\n\n.btn-primary:active {\n    transform: translateY(0);\n}\n\n.btn-icon {\n    padding: 0.25rem 0.5rem;\n}\n\n/* ========== 通用表格样式 ========== */\n.table-sm th {\n    font-weight: 500;\n    color: #6c757d;\n}\n\n.table-sm td {\n    color: #2c3e50;\n}\n\n.table-hover tbody tr:hover {\n    background-color: rgba(0, 0, 0, 0.03);\n}\n\n/* ========== 纯净通知系统 ========== */\n#notificationContainer {\n    position: fixed;\n    top: 20px;\n    left: 50%;\n    transform: translateX(-50%);\n    z-index: 9999;\n    pointer-events: none;\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n}\n\n.pure-notification {\n    background: #fff;\n    padding: 0.75rem 1.25rem;\n    border-radius: 4px;\n    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n    margin-bottom: 10px;\n    opacity: 0;\n    transform: translateY(-20px);\n    transition: all 0.3s ease-in-out;\n    pointer-events: auto;\n    text-align: center;\n    white-space: nowrap;\n    display: inline-block;\n    min-width: min-content;\n}\n\n/* 添加.show类以确保通知显示动画正常工作 */\n.pure-notification.show {\n    opacity: 1;\n    transform: translateY(0);\n}\n\n/* 删除所有旧的notification-toast相关样式 */\n.notification-toast {\n    display: none !important;\n}\n\n/* 确保登录页面也能正确显示通知 */\nbody.login-page #notificationContainer {\n    position: fixed !important;\n    top: 20px !important;\n    left: 50% !important;\n    transform: translateX(-50%) !important;\n    z-index: 9999 !important;\n    align-items: center !important;\n}\n\n/* 添加通知类型样式 */\n.pure-notification.info-notification {\n    background: #e3f2fd !important;\n    border-color: #90caf9 !important;\n}\n\n.pure-notification.success-notification {\n    background: #e8f5e9 !important;\n    border-color: #a5d6a7 !important;\n}\n\n.pure-notification.warning-notification {\n    background: #fff3e0 !important;\n    border-color: #ffcc80 !important;\n}\n\n.pure-notification.error-notification {\n    background: #ffebee !important;\n    border-color: #ef9a9a !important;\n}\n\n/* ========== 自定义滚动条 ========== */\n::-webkit-scrollbar {\n    width: 8px;\n    height: 8px;\n}\n\n::-webkit-scrollbar-track {\n    background: #f1f1f1;\n}\n\n::-webkit-scrollbar-thumb {\n    background: #888;\n    border-radius: 4px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n    background: #555;\n}\n\n/* ========== 日志查看器通用样式 ========== */\n.log-viewer {\n    background-color: #1e1e1e;\n    color: #f0f0f0;\n    font-family: 'Consolas', 'Courier New', monospace;\n    font-size: 14px;\n    max-height: 400px;\n    overflow-y: auto;\n    border-radius: 5px;\n    border: 1px solid #444;\n    padding: 0;\n    margin-bottom: 15px;\n    white-space: pre-wrap;\n    word-wrap: break-word;\n    overscroll-behavior: contain;\n    position: relative;\n}\n\n.log-line {\n    padding: 2px 10px;\n    display: block;\n    border-bottom: 1px solid rgba(255, 255, 255, 0.05);\n    white-space: pre-wrap;\n    word-wrap: break-word;\n}\n\n.log-line:hover {\n    background-color: rgba(255, 255, 255, 0.1);\n}\n\n/* 日志级别样式 */\n.log-debug {\n    color: #42a7ff;\n}\n\n.log-info {\n    color: #ffffff;\n}\n\n.log-warning {\n    color: #ffc107;\n}\n\n.log-error {\n    color: #f14c4c;\n}\n\n.log-critical {\n    color: #ff0000;\n    font-weight: bold;\n    background-color: rgba(255, 0, 0, 0.2);\n}\n\n/* ========== 加载提示样式 ========== */\n.loading-container {\n    text-align: center;\n    padding: 3rem 0;\n}\n\n.loading-container .spinner-border {\n    width: 3rem;\n    height: 3rem;\n}\n\n.loading-container p {\n    margin-top: 1rem;\n    color: #6c757d;\n}\n\n.full-page-loader {\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    background-color: rgba(255, 255, 255, 0.8);\n    z-index: 9999;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n}\n\n.loader-content {\n    text-align: center;\n    padding: 20px;\n    background-color: white;\n    border-radius: 8px;\n    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\n}\n\n/* ========== 响应式调整 ========== */\n@media (max-width: 768px) {\n    .content {\n        margin-left: 0;\n        width: 100%;\n    }\n\n    .footer {\n        width: 100%;\n        margin-left: 0;\n    }\n\n    .sidebar {\n        transform: translateX(-100%);\n        transition: transform 0.3s ease;\n    }\n\n    .sidebar.show {\n        transform: translateX(0);\n    }\n\n    .btn {\n        padding: 0.375rem 0.75rem;\n        font-size: 0.875rem;\n    }\n\n    .loading-container .spinner-border {\n        width: 2rem;\n        height: 2rem;\n    }\n}\n\n/* ========== 登录页面通用样式 ========== */\nbody.login-page {\n    background-color: #f8f9fa;\n    padding-top: 0 !important;\n}\n\nbody.login-page .navbar,\nbody.login-page .sidebar {\n    display: none !important;\n}\n\nbody.login-page .content,\nbody.login-page main {\n    margin-left: 0 !important;\n    padding-left: 0 !important;\n    width: 100% !important;\n}\n\n/* ========== 模态框样式 ========== */\n.modal-content {\n    border: none;\n    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);\n}\n\n.modal-header {\n    border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n}\n\n.modal-footer {\n    border-top: 1px solid rgba(0, 0, 0, 0.05);\n}\n\n.modal-body {\n    padding: 1.5rem;\n}\n\n/* ========== 内容骨架屏 ========== */\n.content-loader {\n    padding: 15px;\n    background-color: #fff;\n    border-radius: 5px;\n}\n\n.skeleton-line {\n    background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);\n    background-size: 200% 100%;\n    animation: shimmer 1.5s infinite;\n    border-radius: 4px;\n    margin-bottom: 10px;\n}\n\n@keyframes shimmer {\n    0% {\n        background-position: -200% 0;\n    }\n    100% {\n        background-position: 200% 0;\n    }\n}\n\n/* 状态指示器 */\n.status-indicator {\n    width: 10px;\n    height: 10px;\n    border-radius: 50%;\n    display: inline-block;\n    margin-right: 5px;\n}\n\n.status-online {\n    background-color: #28a745;\n}\n\n.status-offline {\n    background-color: #dc3545;\n}\n\n/* 状态卡片 */\n.status-card .card-title {\n    font-size: 2rem;\n    font-weight: bold;\n}\n\n.status-icon {\n    width: 60px;\n    height: 60px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n}\n\n/* 控制卡片 */\n.control-card .card-body {\n    padding: 1.25rem;\n}\n\n/* 日志卡片 */\n.log-card .card-body {\n    padding: 0;\n}\n\n/* 工具卡片 */\n.tool-card {\n    transition: transform 0.3s;\n}\n\n.tool-card:hover {\n    transform: translateY(-5px);\n}\n\n/* 登录页面特殊样式 */\n.login-page {\n    background-color: #f8f9fa;\n}\n\n/* 表单样式 */\n.form-control:focus {\n    box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);\n    border-color: #86b7fe;\n}\n\n/* 补充缺失的版本信息样式 */\n.version-info {\n    background: #f8f9fa;\n    padding: 15px;\n    border-radius: 6px;\n    margin-bottom: 20px;\n}\n\n/* 添加全局按钮重置 */\nbutton.btn-close {\n    display: none !important;\n    visibility: hidden !important;\n    opacity: 0 !important;\n    width: 0 !important;\n    height: 0 !important;\n    padding: 0 !important;\n    margin: 0 !important;\n} "
  },
  {
    "path": "WebUI/static/js/components/cards.js",
    "content": "class StatusCard {\n    constructor(element) {\n        this.element = element;\n        this.title = element.querySelector('.card-title');\n        this.icon = element.querySelector('.status-icon i');\n        this.value = element.querySelector('.card-value');\n    }\n\n    update(value) {\n        if (this.value) {\n            this.value.textContent = value;\n        }\n    }\n\n    setIcon(iconClass) {\n        if (this.icon) {\n            this.icon.className = iconClass;\n        }\n    }\n}\n\nclass ControlCard {\n    constructor(element, options = {}) {\n        this.element = element;\n        this.startBtn = element.querySelector('[data-action=\"start\"]');\n        this.stopBtn = element.querySelector('[data-action=\"stop\"]');\n        this.statusIndicator = element.querySelector('.status-indicator');\n        this.options = options;\n\n        this.init();\n    }\n\n    init() {\n        if (this.startBtn) {\n            this.startBtn.addEventListener('click', () => this.start());\n        }\n        if (this.stopBtn) {\n            this.stopBtn.addEventListener('click', () => this.stop());\n        }\n    }\n\n    start() {\n        if (this.options.onStart) {\n            this.options.onStart();\n        }\n    }\n\n    stop() {\n        if (this.options.onStop) {\n            this.options.onStop();\n        }\n    }\n\n    setStatus(isRunning) {\n        if (this.startBtn) {\n            this.startBtn.style.display = isRunning ? 'none' : 'inline-block';\n        }\n        if (this.stopBtn) {\n            this.stopBtn.style.display = isRunning ? 'inline-block' : 'none';\n        }\n        if (this.statusIndicator) {\n            this.statusIndicator.className = `status-indicator ${isRunning ? 'status-online' : 'status-offline'}`;\n        }\n    }\n}\n\nclass LogCard {\n    constructor(element, options = {}) {\n        this.element = element;\n        this.logViewer = element.querySelector('.log-viewer');\n        this.refreshBtn = element.querySelector('[data-action=\"refresh\"]');\n        this.clearBtn = element.querySelector('[data-action=\"clear\"]');\n        this.options = options;\n\n        this.init();\n    }\n\n    init() {\n        if (this.refreshBtn) {\n            this.refreshBtn.addEventListener('click', () => this.refresh());\n        }\n        if (this.clearBtn) {\n            this.clearBtn.addEventListener('click', () => this.clear());\n        }\n    }\n\n    refresh() {\n        if (this.options.onRefresh) {\n            this.options.onRefresh();\n        }\n    }\n\n    clear() {\n        if (this.logViewer) {\n            this.logViewer.innerHTML = '';\n        }\n        if (this.options.onClear) {\n            this.options.onClear();\n        }\n    }\n\n    appendLog(logText, level = 'info') {\n        if (!this.logViewer) return;\n\n        const logLine = document.createElement('div');\n        logLine.className = `log-line log-${level}`;\n        logLine.textContent = logText;\n        this.logViewer.appendChild(logLine);\n        this.scrollToBottom();\n    }\n\n    scrollToBottom() {\n        if (this.logViewer) {\n            this.logViewer.scrollTop = this.logViewer.scrollHeight;\n        }\n    }\n}\n\n// 导出组件类\nwindow.StatusCard = StatusCard;\nwindow.ControlCard = ControlCard;\nwindow.LogCard = LogCard; "
  },
  {
    "path": "WebUI/static/js/components/file_browser.js",
    "content": "(function () {\n    // 检查全局工具函数是否已存在，不存在则初始化\n    if (!window.FileBrowserUtils) {\n        window.FileBrowserUtils = {\n            getLoadingTemplate: () => `\n                <div class=\"text-center p-3\">\n                    <i class=\"fas fa-spinner fa-spin\"></i> 加载中...\n                </div>\n            `,\n\n            handleResponse: async (response, successHandler) => {\n                const data = await response.json();\n                if (!response.ok) {\n                    throw new Error(data.error || `HTTP错误: ${response.status}`);\n                }\n                return successHandler(data);\n            },\n\n            handleError: (context, error) => {\n                console.error(`${context}:`, error);\n                alert(`${context}: ${error.message}`);\n            },\n\n            formatFileSize: (bytes) => {\n                if (bytes === 0) return '0 B';\n                const units = ['B', 'KB', 'MB', 'GB', 'TB'];\n                const i = Math.floor(Math.log(bytes) / Math.log(1024));\n                return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + ' ' + units[i];\n            },\n\n            formatDateTime: (timestamp) => {\n                const date = new Date(timestamp * 1000);\n                return date.toLocaleString();\n            },\n\n            getFileIcon: (filename) => {\n                const extension = filename.split('.').pop().toLowerCase();\n                const iconMap = {\n                    'txt': 'far fa-file-alt text-secondary',\n                    'log': 'fas fa-file-alt text-info',\n                    'py': 'fab fa-python text-primary',\n                    'js': 'fab fa-js-square text-warning',\n                    'html': 'fab fa-html5 text-danger',\n                    'css': 'fab fa-css3-alt text-primary',\n                    'json': 'fas fa-file-code text-secondary',\n                    'md': 'fas fa-file-alt text-primary',\n                    'jpg': 'far fa-file-image text-success',\n                    'jpeg': 'far fa-file-image text-success',\n                    'png': 'far fa-file-image text-success'\n                };\n                return iconMap[extension] || 'far fa-file text-secondary';\n            },\n\n            getParentPath: (path) => {\n                if (!path) return '';\n                const lastSlashIndex = path.lastIndexOf('/');\n                return lastSlashIndex === -1 ? '' : path.substring(0, lastSlashIndex);\n            }\n        };\n    }\n\n    // 全局暴露文件浏览器初始化函数\n    window.initFileBrowser = function (containerId, initialPath = '') {\n        const container = document.getElementById(containerId);\n        if (!container) return;\n\n        // 状态管理对象\n        const state = {\n            currentPath: initialPath,\n            viewMode: 'list'\n        };\n\n        // DOM元素引用\n        const dom = {\n            fileList: document.getElementById(`${containerId}-list`),\n            breadcrumb: container.querySelector('.file-path-breadcrumb'),\n            fullPath: document.getElementById(`${containerId}-full-path`),\n            refreshBtn: document.getElementById(`${containerId}-refresh`),\n            viewToggleBtn: document.getElementById(`${containerId}-view-toggle`)\n        };\n\n        // 初始化事件监听\n        function initEventListeners() {\n            // 面包屑导航\n            if (dom.breadcrumb) {\n                dom.breadcrumb.addEventListener('click', handleBreadcrumbClick);\n            }\n\n            // 刷新按钮\n            if (dom.refreshBtn) {\n                dom.refreshBtn.addEventListener('click', () => {\n                    core.loadFileList(state.currentPath);\n                });\n            }\n\n            // 视图切换\n            if (dom.viewToggleBtn) {\n                dom.viewToggleBtn.addEventListener('click', toggleViewMode);\n            }\n        }\n\n        // 核心功能方法\n        const core = {\n            // 加载文件列表\n            async loadFileList(path) {\n                try {\n                    const response = await fetch(`/file/api/list?path=${encodeURIComponent(path)}`);\n                    await FileBrowserUtils.handleResponse(response, (data) => {\n\n                        if (data.files && Array.isArray(data.files)) {\n                            state.currentPath = data.path || path;\n                            render.fileList(data.files);\n                            updateBreadcrumb();\n                        } else if (Array.isArray(data)) {\n                            state.currentPath = path;\n                            render.fileList(data);\n                            updateBreadcrumb();\n                        } else {\n                            console.error('无效的API响应格式', data);\n                            dom.fileList.innerHTML = `<div class=\"alert alert-danger m-3\">无法加载文件列表：无效的数据格式</div>`;\n                        }\n                    });\n                } catch (error) {\n                    FileBrowserUtils.handleError('加载文件列表失败', error);\n                    dom.fileList.innerHTML = `<div class=\"alert alert-danger m-3\">加载失败: ${error.message}</div>`;\n                }\n            }\n        };\n\n        // 内部辅助函数\n        function getFileListTemplate(files, isRoot) {\n            return `\n                <table class=\"table table-hover table-sm\">\n                    <thead>\n                        <tr>\n                            <th width=\"50%\">名称</th>\n                            <th width=\"20%\">大小</th>\n                            <th width=\"30%\">修改时间</th>\n                        </tr>\n                    </thead>\n                    <tbody>\n                        ${!isRoot ? `\n                        <tr class=\"file-item\" data-path=\"${FileBrowserUtils.getParentPath(state.currentPath)}\" data-type=\"dir\">\n                            <td><i class=\"fas fa-level-up-alt\"></i> 上级目录</td>\n                            <td></td>\n                            <td></td>\n                        </tr>` : ''}\n                        ${files.map(file => `\n                        <tr class=\"file-item\" data-path=\"${file.path}\" data-type=\"${file.is_dir ? 'dir' : 'file'}\">\n                            <td>\n                                <i class=\"${file.is_dir ? 'fas fa-folder text-warning' : FileBrowserUtils.getFileIcon(file.name)}\"></i>\n                                ${file.name}\n                            </td>\n                            <td>${file.is_dir ? '-' : FileBrowserUtils.formatFileSize(file.size)}</td>\n                            <td>${FileBrowserUtils.formatDateTime(file.modified)}</td>\n                        </tr>`).join('')}\n                    </tbody>\n                </table>\n            `;\n        }\n\n        function getFileGridTemplate(files, isRoot) {\n            return `\n                <div class=\"file-grid\">\n                    ${!isRoot ? `\n                    <div class=\"file-grid-item\" data-path=\"${FileBrowserUtils.getParentPath(state.currentPath)}\" data-type=\"dir\">\n                        <div class=\"file-icon\"><i class=\"fas fa-level-up-alt\"></i></div>\n                        <div class=\"file-grid-name\">上级目录</div>\n                    </div>` : ''}\n                    ${files.map(file => `\n                    <div class=\"file-grid-item\" data-path=\"${file.path}\" data-type=\"${file.is_dir ? 'dir' : 'file'}\">\n                        <div class=\"file-icon\">\n                            <i class=\"${file.is_dir ? 'fas fa-folder text-warning' : FileBrowserUtils.getFileIcon(file.name)}\"></i>\n                        </div>\n                        <div class=\"file-grid-name\">${file.name}</div>\n                    </div>`).join('')}\n                </div>\n            `;\n        }\n\n        // 视图渲染方法\n        const render = {\n            fileList(files) {\n                files = files.filter(file => {\n                    return !file.permissions || (file.permissions & 0o400) !== 0;\n                });\n\n                const isRoot = !state.currentPath;\n                const template = state.viewMode === 'list'\n                    ? getFileListTemplate(files, isRoot)\n                    : getFileGridTemplate(files, isRoot);\n\n                dom.fileList.innerHTML = template;\n                bindFileItemEvents();\n            }\n        };\n\n        // 事件处理\n        function handleBreadcrumbClick(e) {\n            if (e.target.tagName === 'A') {\n                e.preventDefault();\n                const path = e.target.dataset.path || e.target.getAttribute('data-path');\n                core.loadFileList(path);\n            }\n        }\n\n        function toggleViewMode() {\n            state.viewMode = state.viewMode === 'list' ? 'grid' : 'list';\n            core.loadFileList(state.currentPath);\n            updateViewToggleIcon();\n        }\n\n        function updateViewToggleIcon() {\n            if (!dom.viewToggleBtn) return;\n\n            const icon = dom.viewToggleBtn.querySelector('i');\n            if (icon) {\n                icon.className = state.viewMode === 'list' ? 'fas fa-th' : 'fas fa-th-list';\n            }\n        }\n\n        function updateBreadcrumb() {\n            if (dom.fullPath) {\n                let displayPath = state.currentPath;\n                if (!displayPath) {\n                    displayPath = '/';\n                } else if (!displayPath.startsWith('/')) {\n                    displayPath = '/' + displayPath;\n                }\n                dom.fullPath.textContent = displayPath;\n            }\n\n            if (dom.breadcrumb) {\n                dom.breadcrumb.innerHTML = '';\n\n                if (!state.currentPath) {\n                    dom.breadcrumb.innerHTML = '<li class=\"breadcrumb-item active\">根目录</li>';\n                    return;\n                }\n\n                const parts = state.currentPath.split('/');\n                let currentPath = '';\n\n                const rootLi = document.createElement('li');\n                rootLi.className = 'breadcrumb-item';\n                const rootLink = document.createElement('a');\n                rootLink.href = '#';\n                rootLink.dataset.path = '';\n                rootLink.textContent = '根目录';\n                rootLi.appendChild(rootLink);\n                dom.breadcrumb.appendChild(rootLi);\n\n                parts.forEach((part, index) => {\n                    if (!part) return;\n\n                    currentPath += (currentPath ? '/' : '') + part;\n\n                    const li = document.createElement('li');\n                    li.className = 'breadcrumb-item';\n\n                    if (index === parts.length - 1) {\n                        li.classList.add('active');\n                        li.textContent = part;\n                    } else {\n                        const link = document.createElement('a');\n                        link.href = '#';\n                        link.dataset.path = currentPath;\n                        link.textContent = part;\n                        li.appendChild(link);\n                    }\n\n                    dom.breadcrumb.appendChild(li);\n                });\n            }\n        }\n\n        function bindFileItemEvents() {\n            dom.fileList.querySelectorAll('.file-item, .file-grid-item').forEach(item => {\n                item.addEventListener('click', () => {\n                    const path = item.dataset.path;\n                    const type = item.dataset.type;\n\n                    if (type === 'dir') {\n                        core.loadFileList(path);\n                    } else {\n                        openFileViewer(path);\n                    }\n                });\n            });\n        }\n\n        function openFileViewer(path) {\n            window.location.href = `/explorer/view/${encodeURIComponent(path)}`;\n        }\n\n        // 初始化执行\n        function init() {\n            initEventListeners();\n            core.loadFileList(state.currentPath);\n        }\n\n        init();\n    }\n})(); "
  },
  {
    "path": "WebUI/static/js/components/file_viewer.js",
    "content": "(function () {\n    // 检查全局工具函数是否已存在，不存在则初始化\n    if (!window.FileViewerUtils) {\n        window.FileViewerUtils = {\n            handleResponse: async (response, successHandler) => {\n                if (!response.ok) {\n                    const text = await response.text();\n                    try {\n                        const data = JSON.parse(text);\n                        throw new Error(data.error || `HTTP错误: ${response.status}`);\n                    } catch (e) {\n                        throw new Error(`服务器错误(${response.status}): ${text.substring(0, 100)}`);\n                    }\n                }\n                return successHandler(await response.json());\n            },\n\n            handleError: (context, error) => {\n                console.error(`${context}:`, error);\n                alert(`${context}: ${error.message}`);\n            },\n\n            formatFileSize: (bytes) => {\n                if (bytes === 0) return '0 B';\n                if (bytes === undefined || bytes === null || isNaN(bytes)) return '未知';\n                try {\n                    const units = ['B', 'KB', 'MB', 'GB', 'TB'];\n                    const i = Math.floor(Math.log(bytes) / Math.log(1024));\n                    return parseFloat((bytes / Math.pow(1024, i)).toFixed(2)) + ' ' + units[i];\n                } catch (e) {\n                    console.error('格式化文件大小出错:', e);\n                    return '未知';\n                }\n            },\n\n            formatDateTime: (timestamp) => {\n                if (timestamp === undefined || timestamp === null || isNaN(timestamp)) return '未知';\n                try {\n                    const date = new Date(timestamp * 1000);\n                    if (isNaN(date.getTime())) return '未知';\n                    return date.toLocaleString();\n                } catch (e) {\n                    console.error('格式化时间出错:', e);\n                    return '未知';\n                }\n            },\n\n            getFileExtension: (filename) => {\n                return filename.slice((filename.lastIndexOf(\".\") - 1 >>> 0) + 2).toLowerCase();\n            },\n\n            guessLanguage: (filename) => {\n                const ext = window.FileViewerUtils.getFileExtension(filename);\n\n                const extensionMap = {\n                    'js': 'javascript',\n                    'ts': 'typescript',\n                    'py': 'python',\n                    'html': 'html',\n                    'css': 'css',\n                    'json': 'json',\n                    'md': 'markdown',\n                    'txt': 'plaintext',\n                    'xml': 'xml',\n                    'sh': 'shell',\n                    'bash': 'shell',\n                    'yml': 'yaml',\n                    'yaml': 'yaml',\n                    'toml': 'toml',\n                    'java': 'java',\n                    'c': 'c',\n                    'cpp': 'cpp',\n                    'cs': 'csharp',\n                    'go': 'go',\n                    'php': 'php',\n                    'rb': 'ruby',\n                    'rs': 'rust',\n                    'sql': 'sql'\n                };\n\n                return extensionMap[ext] || 'plaintext';\n            }\n        };\n    }\n\n    // 全局暴露文件查看器初始化函数\n    window.initFileViewer = function (containerId, filePath) {\n        // 状态变量\n        const state = {\n            originalContent: '',\n            editor: null\n        };\n\n        // DOM元素\n        const dom = {\n            monacoContainer: document.getElementById(`${containerId}-monaco-editor`),\n            reloadBtn: document.getElementById(`${containerId}-reload`),\n            saveBtn: document.getElementById(`${containerId}-save`),\n            lineWrapCheck: document.getElementById(`${containerId}-line-wrap`),\n            fileSizeEl: document.getElementById(`${containerId}-size`),\n            fileModifiedEl: document.getElementById(`${containerId}-modified`),\n            editorLanguageSelect: document.getElementById(`${containerId}-editor-language`),\n            fontSizeSelect: document.getElementById(`${containerId}-font-size`)\n        };\n\n        // 初始化方法\n        async function init() {\n            loadFileData();\n            initEventListeners();\n        }\n\n        // 核心功能方法\n        async function loadFileData() {\n            try {\n                dom.monacoContainer.innerHTML = `\n                    <div class=\"loading-container\">\n                        <i class=\"fas fa-file-alt\"></i>\n                        <p>正在加载文件内容...</p>\n                    </div>\n                `;\n\n                const response = await fetch(`/file/api/content?path=${encodeURIComponent(filePath)}`);\n\n                await FileViewerUtils.handleResponse(response, async data => {\n                    updateFileInfo(data.info);\n\n                    let fileContent = data.content;\n\n                    if (data.info && data.info.total_lines && data.content.length < data.info.total_lines) {\n\n                        dom.monacoContainer.innerHTML = `\n                            <div class=\"loading-container\">\n                                <i class=\"fas fa-spinner fa-spin\"></i>\n                                <p>正在加载大文件，请稍候...</p>\n                            </div>\n                        `;\n\n                        const totalLines = data.info.total_lines;\n                        let completeContent = [...fileContent];\n\n                        let currentLine = fileContent.length;\n\n                        while (currentLine < totalLines) {\n\n                            const nextResponse = await fetch(\n                                `/file/api/content?path=${encodeURIComponent(filePath)}&start=${currentLine}`\n                            );\n\n                            const nextData = await nextResponse.json();\n                            if (nextData.content && nextData.content.length > 0) {\n                                completeContent = completeContent.concat(nextData.content);\n                                currentLine += nextData.content.length;\n                            } else {\n                                break;\n                            }\n                        }\n\n                        fileContent = completeContent;\n                    }\n\n                    initMonacoEditor(fileContent.join('\\n'));\n                });\n            } catch (error) {\n                FileViewerUtils.handleError('文件加载失败', error);\n                dom.monacoContainer.innerHTML = `<div class=\"alert alert-danger m-3\">加载失败: ${error.message}</div>`;\n            }\n        }\n\n        async function saveFileContent() {\n            try {\n                const content = state.editor.getValue();\n\n                dom.saveBtn.innerHTML = '<i class=\"fas fa-spinner fa-spin\"></i> 保存中...';\n                dom.saveBtn.disabled = true;\n\n                const response = await fetch('/file/api/save', {\n                    method: 'POST',\n                    headers: {\n                        'Content-Type': 'application/json',\n                    },\n                    body: JSON.stringify({\n                        path: filePath,\n                        content: content\n                    })\n                });\n\n                await FileViewerUtils.handleResponse(response, data => {\n                    if (data.success) {\n                        alert('文件保存成功');\n                        state.originalContent = content;\n                    } else {\n                        throw new Error(data.error || '保存失败');\n                    }\n                });\n            } catch (error) {\n                FileViewerUtils.handleError('保存文件失败', error);\n            } finally {\n                dom.saveBtn.innerHTML = '<i class=\"fas fa-save\"></i> 保存';\n                dom.saveBtn.disabled = false;\n            }\n        }\n\n        // Monaco编辑器初始化\n        function initMonacoEditor(content) {\n            // 只有在全局没有配置过Monaco时才进行配置\n            if (!window.monacoConfigured) {\n                require.config({paths: {'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs'}});\n                window.monacoConfigured = true;\n            }\n\n            // 如果Monaco已加载，直接创建编辑器\n            if (window.monaco) {\n                createEditor(content);\n                return;\n            }\n\n            // 否则加载Monaco\n            require(['vs/editor/editor.main'], function () {\n                createEditor(content);\n            });\n        }\n\n        // 创建编辑器\n        function createEditor(content) {\n            const language = FileViewerUtils.guessLanguage(filePath);\n\n            if (dom.editorLanguageSelect) {\n                if (dom.editorLanguageSelect.querySelector(`option[value=\"${language}\"]`)) {\n                    dom.editorLanguageSelect.value = language;\n                }\n            }\n\n            const fontSize = dom.fontSizeSelect ? parseInt(dom.fontSizeSelect.value, 10) : 16;\n\n            dom.monacoContainer.innerHTML = '';\n            state.editor = monaco.editor.create(dom.monacoContainer, {\n                value: content,\n                language: language,\n                theme: 'vs-dark',\n                automaticLayout: true,\n                minimap: {enabled: true},\n                scrollBeyondLastLine: false,\n                lineNumbers: 'on',\n                renderLineHighlight: 'all',\n                wordWrap: dom.lineWrapCheck && dom.lineWrapCheck.checked ? 'on' : 'off',\n                fontSize: fontSize,\n                tabSize: 4,\n                insertSpaces: true,\n                fontFamily: \"'JetBrains Mono', 'Source Code Pro', 'Menlo', 'Ubuntu Mono', 'Fira Code', monospace\",\n                fontLigatures: true,\n                letterSpacing: 0.5\n            });\n\n            state.originalContent = content;\n        }\n\n        // 事件处理\n        function initEventListeners() {\n            if (dom.reloadBtn) {\n                dom.reloadBtn.addEventListener('click', function () {\n                    if (state.editor && state.editor.getValue() !== state.originalContent) {\n                        if (confirm('您有未保存的更改，确定要刷新吗？')) {\n                            loadFileData();\n                        }\n                    } else {\n                        loadFileData();\n                    }\n                });\n            }\n\n            if (dom.saveBtn) {\n                dom.saveBtn.addEventListener('click', function () {\n                    saveFileContent();\n                });\n            }\n\n            if (dom.editorLanguageSelect) {\n                dom.editorLanguageSelect.addEventListener('change', function () {\n                    if (state.editor) {\n                        monaco.editor.setModelLanguage(\n                            state.editor.getModel(),\n                            this.value\n                        );\n                    }\n                });\n            }\n\n            if (dom.fontSizeSelect) {\n                dom.fontSizeSelect.addEventListener('change', function () {\n                    if (state.editor) {\n                        const fontSize = parseInt(this.value, 10);\n                        state.editor.updateOptions({fontSize: fontSize});\n                    }\n                });\n            }\n\n            if (dom.lineWrapCheck) {\n                dom.lineWrapCheck.addEventListener('change', function () {\n                    if (state.editor) {\n                        state.editor.updateOptions({wordWrap: this.checked ? 'on' : 'off'});\n                    }\n                });\n            }\n        }\n\n        // 更新文件信息\n        function updateFileInfo(info) {\n            if (!info || typeof info !== 'object') {\n                console.warn('缺少文件信息数据');\n                info = {size: null, modified: null};\n            }\n\n            if (dom.fileSizeEl) {\n                dom.fileSizeEl.textContent = FileViewerUtils.formatFileSize(info.size);\n            }\n\n            if (dom.fileModifiedEl) {\n                dom.fileModifiedEl.textContent = FileViewerUtils.formatDateTime(info.modified);\n            }\n\n            const filePathElements = document.querySelectorAll('.file-path');\n            filePathElements.forEach(el => {\n                if (el.textContent.trim() === '') {\n                    el.textContent = filePath || '未知';\n                }\n            });\n        }\n\n        // 初始化执行\n        init();\n    }\n})(); "
  },
  {
    "path": "WebUI/static/js/components/loading.js",
    "content": "class LoadingSpinner {\n    constructor(options = {}) {\n        this.options = {\n            size: options.size || 'md',\n            color: options.color || 'primary',\n            text: options.text || '加载中...'\n        };\n    }\n\n    render() {\n        const sizes = {\n            \"sm\": \"spinner-border-sm\",\n            \"md\": \"\",\n            \"lg\": \"spinner-border spinner-border-lg\"\n        };\n\n        return `\n            <div class=\"d-flex justify-content-center align-items-center loading-container\">\n                <div class=\"spinner-border text-${this.options.color} ${sizes[this.options.size]}\" role=\"status\">\n                    <span class=\"visually-hidden\">${this.options.text}</span>\n                </div>\n                ${this.options.text ? `<span class=\"ms-2\">${this.options.text}</span>` : ''}\n            </div>\n        `;\n    }\n}\n\nclass ProgressBar {\n    constructor(options = {}) {\n        this.options = {\n            value: options.value || 0,\n            color: options.color || 'primary',\n            striped: options.striped !== undefined ? options.striped : true,\n            animated: options.animated !== undefined ? options.animated : true,\n            label: options.label !== undefined ? options.label : true\n        };\n    }\n\n    render() {\n        return `\n            <div class=\"progress\" style=\"height: 20px;\">\n                <div class=\"progress-bar bg-${this.options.color} \n                    ${this.options.striped ? 'progress-bar-striped' : ''} \n                    ${this.options.animated ? 'progress-bar-animated' : ''}\"\n                    role=\"progressbar\" \n                    style=\"width: ${this.options.value}%\"\n                    aria-valuenow=\"${this.options.value}\" \n                    aria-valuemin=\"0\" \n                    aria-valuemax=\"100\">\n                    ${this.options.label ? `${this.options.value}%` : ''}\n                </div>\n            </div>\n        `;\n    }\n\n    update(value) {\n        this.options.value = value;\n        return this.render();\n    }\n}\n\nclass FullPageLoader {\n    constructor(options = {}) {\n        this.options = {\n            message: options.message || '页面加载中...',\n            id: options.id || 'fullPageLoader'\n        };\n        this.element = null;\n    }\n\n    show() {\n        if (!this.element) {\n            this.element = document.createElement('div');\n            this.element.id = this.options.id;\n            this.element.className = 'full-page-loader';\n            this.element.innerHTML = `\n                <div class=\"loader-content\">\n                    <div class=\"spinner-grow text-primary\" role=\"status\">\n                        <span class=\"visually-hidden\">加载中...</span>\n                    </div>\n                    ${this.options.message ? `<p class=\"mt-3\">${this.options.message}</p>` : ''}\n                </div>\n            `;\n            document.body.appendChild(this.element);\n        }\n        this.element.style.display = 'flex';\n    }\n\n    hide() {\n        if (this.element) {\n            this.element.style.display = 'none';\n        }\n    }\n\n    setMessage(message) {\n        this.options.message = message;\n        if (this.element) {\n            const messageElement = this.element.querySelector('p');\n            if (messageElement) {\n                messageElement.textContent = message;\n            }\n        }\n    }\n}\n\nclass ContentLoader {\n    constructor(options = {}) {\n        this.options = {\n            size: options.size || 'md',\n            containerClass: options.containerClass || ''\n        };\n    }\n\n    render() {\n        let skeletonLines = '';\n        switch (this.options.size) {\n            case 'sm':\n                skeletonLines = `\n                    <div class=\"skeleton-line\" style=\"width: 30%; height: 15px;\"></div>\n                    <div class=\"skeleton-line\" style=\"width: 80%; height: 15px;\"></div>\n                `;\n                break;\n            case 'lg':\n                skeletonLines = `\n                    <div class=\"skeleton-line\" style=\"width: 40%; height: 25px;\"></div>\n                    <div class=\"skeleton-line\" style=\"width: 90%; height: 15px;\"></div>\n                    <div class=\"skeleton-line\" style=\"width: 60%; height: 15px;\"></div>\n                    <div class=\"skeleton-line\" style=\"width: 75%; height: 15px;\"></div>\n                `;\n                break;\n            default:\n                skeletonLines = `\n                    <div class=\"skeleton-line\" style=\"width: 40%; height: 20px;\"></div>\n                    <div class=\"skeleton-line\" style=\"width: 90%; height: 15px;\"></div>\n                    <div class=\"skeleton-line\" style=\"width: 60%; height: 15px;\"></div>\n                `;\n        }\n\n        return `\n            <div class=\"content-loader ${this.options.containerClass}\">\n                <div class=\"loader-skeleton\">\n                    ${skeletonLines}\n                </div>\n            </div>\n        `;\n    }\n}\n\n// 导出组件类\nwindow.LoadingSpinner = LoadingSpinner;\nwindow.ProgressBar = ProgressBar;\nwindow.FullPageLoader = FullPageLoader;\nwindow.ContentLoader = ContentLoader; "
  },
  {
    "path": "WebUI/static/js/components/modals.js",
    "content": "class BaseModal {\n    constructor(options = {}) {\n        this.options = {\n            id: options.id || 'modal',\n            title: options.title || '',\n            size: options.size || 'md',\n            backdrop: options.backdrop !== undefined ? options.backdrop : true,\n            keyboard: options.keyboard !== undefined ? options.keyboard : true\n        };\n        this.modal = null;\n    }\n\n    createModal() {\n        const modalHtml = `\n            <div class=\"modal fade\" id=\"${this.options.id}\" tabindex=\"-1\" aria-labelledby=\"${this.options.id}Label\" aria-hidden=\"true\">\n                <div class=\"modal-dialog modal-${this.options.size}\">\n                    <div class=\"modal-content\">\n                        <div class=\"modal-header\">\n                            <h5 class=\"modal-title\" id=\"${this.options.id}Label\">${this.options.title}</h5>\n                            <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"关闭\"></button>\n                        </div>\n                        <div class=\"modal-body\">\n                            ${this.getModalBody()}\n                        </div>\n                        ${this.getModalFooter()}\n                    </div>\n                </div>\n            </div>\n        `;\n\n        const modalElement = document.createElement('div');\n        modalElement.innerHTML = modalHtml;\n        document.body.appendChild(modalElement.firstElementChild);\n        this.modal = new bootstrap.Modal(document.getElementById(this.options.id), {\n            backdrop: this.options.backdrop,\n            keyboard: this.options.keyboard\n        });\n    }\n\n    getModalBody() {\n        return '';\n    }\n\n    getModalFooter() {\n        return '';\n    }\n\n    show() {\n        if (!this.modal) {\n            this.createModal();\n        }\n        this.modal.show();\n    }\n\n    hide() {\n        if (this.modal) {\n            this.modal.hide();\n        }\n    }\n\n    dispose() {\n        if (this.modal) {\n            this.modal.dispose();\n            document.getElementById(this.options.id).remove();\n            this.modal = null;\n        }\n    }\n}\n\nclass ConfirmModal extends BaseModal {\n    constructor(options = {}) {\n        super(options);\n        this.options.message = options.message || '您确定要执行此操作吗？';\n        this.options.confirmText = options.confirmText || '确认';\n        this.options.cancelText = options.cancelText || '取消';\n        this.options.onConfirm = options.onConfirm || (() => {\n        });\n        this.options.onCancel = options.onCancel || (() => {\n        });\n    }\n\n    getModalBody() {\n        return `<p>${this.options.message}</p>`;\n    }\n\n    getModalFooter() {\n        return `\n            <div class=\"modal-footer\">\n                <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">${this.options.cancelText}</button>\n                <button type=\"button\" class=\"btn btn-primary\" id=\"${this.options.id}-confirm\">${this.options.confirmText}</button>\n            </div>\n        `;\n    }\n\n    createModal() {\n        super.createModal();\n        document.getElementById(`${this.options.id}-confirm`).addEventListener('click', () => {\n            this.options.onConfirm();\n            this.hide();\n        });\n    }\n}\n\nclass FormModal extends BaseModal {\n    constructor(options = {}) {\n        super(options);\n        this.options.saveText = options.saveText || '保存';\n        this.options.cancelText = options.cancelText || '取消';\n        this.options.onSave = options.onSave || (() => {\n        });\n        this.options.formContent = options.formContent || '';\n    }\n\n    getModalBody() {\n        return `\n            <form id=\"${this.options.id}-form\" class=\"modal-form\">\n                ${this.options.formContent}\n            </form>\n        `;\n    }\n\n    getModalFooter() {\n        return `\n            <div class=\"modal-footer\">\n                <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">${this.options.cancelText}</button>\n                <button type=\"button\" class=\"btn btn-primary\" id=\"${this.options.id}-save\">${this.options.saveText}</button>\n            </div>\n        `;\n    }\n\n    createModal() {\n        super.createModal();\n        document.getElementById(`${this.options.id}-save`).addEventListener('click', () => {\n            const form = document.getElementById(`${this.options.id}-form`);\n            if (form.checkValidity()) {\n                this.options.onSave(new FormData(form));\n                this.hide();\n            } else {\n                form.reportValidity();\n            }\n        });\n    }\n}\n\nclass AjaxModal extends BaseModal {\n    constructor(options = {}) {\n        super(options);\n        this.options.url = options.url || '';\n        this.options.method = options.method || 'GET';\n        this.options.data = options.data || null;\n    }\n\n    async loadContent() {\n        try {\n            const response = await fetch(this.options.url, {\n                method: this.options.method,\n                headers: {\n                    'Content-Type': 'application/json'\n                },\n                body: this.options.data ? JSON.stringify(this.options.data) : null\n            });\n\n            if (!response.ok) {\n                throw new Error(`HTTP error! status: ${response.status}`);\n            }\n\n            const content = await response.text();\n            document.querySelector(`#${this.options.id} .modal-body`).innerHTML = content;\n        } catch (error) {\n            document.querySelector(`#${this.options.id} .modal-body`).innerHTML = `\n                <div class=\"alert alert-danger\">\n                    加载失败: ${error.message}\n                </div>\n            `;\n        }\n    }\n\n    getModalBody() {\n        return `\n            <div class=\"modal-loading\">\n                <div class=\"spinner-border text-primary\" role=\"status\">\n                    <span class=\"visually-hidden\">加载中...</span>\n                </div>\n                <p>正在加载内容，请稍候...</p>\n            </div>\n        `;\n    }\n\n    show() {\n        super.show();\n        this.loadContent();\n    }\n}\n\n// 导出模态框类\nwindow.BaseModal = BaseModal;\nwindow.ConfirmModal = ConfirmModal;\nwindow.FormModal = FormModal;\nwindow.AjaxModal = AjaxModal; "
  },
  {
    "path": "WebUI/static/js/components/notification.js",
    "content": "const NotificationManager = {\n    /**\n     * 显示通知\n     * @param {string} message - 通知消息\n     * @param {string} type - 通知类型: 'info', 'success', 'warning', 'error'\n     * @param {number} duration - 通知显示持续时间(毫秒)\n     */\n    show: function (message, type = 'info', duration = 3000) {\n        let container = document.getElementById('notificationContainer');\n\n        // 确保容器唯一性\n        if (!container) {\n            container = document.createElement('div');\n            container.id = 'notificationContainer';\n            document.body.appendChild(container);\n        }\n\n        // 类型映射 - 确保使用正确的CSS类\n        let cssType = 'info';\n        switch (type) {\n            case 'success':\n                cssType = 'success';\n                break;\n            case 'error':\n            case 'danger':\n                cssType = 'error';\n                break;\n            case 'warning':\n                cssType = 'warning';\n                break;\n            default:\n                cssType = 'info';\n        }\n\n        // 创建纯净通知元素\n        const notification = document.createElement('div');\n        notification.className = `pure-notification ${cssType}-notification`;\n\n        // 使用文本节点避免HTML解析\n        const textNode = document.createTextNode(message);\n        notification.appendChild(textNode);\n\n        container.appendChild(notification);\n\n        // 强制布局刷新\n        notification.offsetHeight;\n\n        // 显示动画 - 添加show类并设置内联样式\n        notification.classList.add('show');\n        notification.style.opacity = '1';\n        notification.style.transform = 'translateY(0)';\n\n        // 自动移除\n        setTimeout(() => {\n            notification.classList.remove('show');\n            notification.style.opacity = '0';\n            notification.style.transform = 'translateY(-20px)';\n            setTimeout(() => {\n                if (notification.parentNode) {\n                    container.removeChild(notification);\n                }\n            }, 300);\n        }, duration);\n    }\n};\n\n// 导出到全局作用域\nwindow.NotificationManager = NotificationManager; "
  },
  {
    "path": "WebUI/static/js/logs.js",
    "content": "async function loadInitialLogs(limit = 100) {\n    try {\n        const response = await fetch('/logs/api/recent?limit=' + limit);\n        const logs = await response.json();\n        displayLogs(logs);\n    } catch (error) {\n        console.error('加载日志失败:', error);\n    }\n}\n\n// 定时获取新日志\nfunction startLogUpdates(interval = 2000) {\n    return setInterval(async () => {\n        try {\n            const response = await fetch('/logs/api/new');\n            const newLogs = await response.json();\n            if (newLogs && newLogs.length > 0) {\n                appendLogs(newLogs);\n            }\n        } catch (error) {\n            console.error('获取新日志失败:', error);\n        }\n    }, interval);\n}\n\n// 显示日志\nfunction displayLogs(logs) {\n    const logContainer = document.getElementById('log-container');\n    if (!logContainer) return;\n\n    logContainer.innerHTML = '';\n    logs.forEach(log => {\n        appendLogLine(log);\n    });\n    scrollToBottom();\n}\n\n// 添加新日志\nfunction appendLogs(logs) {\n    logs.forEach(log => {\n        appendLogLine(log);\n    });\n    scrollToBottom();\n}\n\n// 添加单行日志\nfunction appendLogLine(log) {\n    const logContainer = document.getElementById('log-container');\n    if (!logContainer) return;\n\n    const logLine = document.createElement('div');\n    logLine.className = 'log-line';\n    logLine.textContent = log;\n    logContainer.appendChild(logLine);\n\n    // 尝试根据日志内容添加级别样式\n    applyLogLevelStyle(logLine, log);\n}\n\n// 应用日志级别样式\nfunction applyLogLevelStyle(logElement, logText) {\n    if (!logElement || !logText) return;\n\n    logText = logText.toLowerCase();\n    if (logText.includes('debug')) {\n        logElement.classList.add('log-debug');\n    } else if (logText.includes('info')) {\n        logElement.classList.add('log-info');\n    } else if (logText.includes('warning') || logText.includes('warn')) {\n        logElement.classList.add('log-warning');\n    } else if (logText.includes('error')) {\n        logElement.classList.add('log-error');\n    } else if (logText.includes('critical') || logText.includes('fatal')) {\n        logElement.classList.add('log-critical');\n    }\n}\n\n// 滚动到底部\nfunction scrollToBottom() {\n    const logContainer = document.getElementById('log-container');\n    if (!logContainer) return;\n\n    const autoScroll = document.getElementById('auto-scroll');\n    if (!autoScroll || autoScroll.checked) {\n        logContainer.scrollTop = logContainer.scrollHeight;\n    }\n}\n\n// 页面加载完成时初始化实时日志\nfunction initRealtimeLog() {\n    // 检查当前页面是否包含实时日志容器\n    const realtimeLogContainer = document.getElementById('realtime-log-container');\n    if (realtimeLogContainer) {\n        loadInitialLogs();\n        const updateInterval = startLogUpdates();\n\n        // 页面关闭时清除定时器\n        window.addEventListener('beforeunload', () => {\n            clearInterval(updateInterval);\n        });\n    }\n}\n\n// 阻止日志滚动事件传播\nfunction preventScrollPropagation() {\n    const logContainers = document.querySelectorAll('.log-container, .log-viewer');\n    logContainers.forEach(container => {\n        if (container) {\n            container.addEventListener('wheel', function (e) {\n                // 如果容器已滚动到底部或顶部，则不阻止传播\n                const isAtBottom = container.scrollHeight - container.scrollTop <= container.clientHeight + 1;\n                const isAtTop = container.scrollTop <= 0;\n\n                if (e.deltaY > 0 && !isAtBottom) {\n                    // 向下滚动且不在底部\n                    e.stopPropagation();\n                } else if (e.deltaY < 0 && !isAtTop) {\n                    // 向上滚动且不在顶部\n                    e.stopPropagation();\n                }\n            });\n        }\n    });\n}\n\n// 页面加载完成时初始化\ndocument.addEventListener('DOMContentLoaded', () => {\n    initRealtimeLog();\n    preventScrollPropagation();\n\n    // 检查是否需要为非实时页面的日志查看器添加样式\n    const logViewers = document.querySelectorAll('.log-viewer');\n    logViewers.forEach(viewer => {\n        const logLines = viewer.querySelectorAll('pre');\n        logLines.forEach(line => {\n            // 为已存在的pre标签添加样式\n            if (line.parentNode === viewer) {\n                const lineContent = line.textContent;\n                const lines = lineContent.split('\\n');\n\n                if (lines.length > 1) {\n                    line.remove();\n                    lines.forEach(text => {\n                        if (text.trim() !== '') {\n                            const logLine = document.createElement('div');\n                            logLine.className = 'log-line';\n                            logLine.textContent = text;\n                            viewer.appendChild(logLine);\n                            applyLogLevelStyle(logLine, text);\n                        }\n                    });\n                }\n            }\n        });\n    });\n}); "
  },
  {
    "path": "WebUI/static/js/main.js",
    "content": "document.addEventListener('DOMContentLoaded', function () {\n    // 激活当前页面的侧边栏菜单\n    const path = window.location.pathname;\n    document.querySelectorAll('.sidebar .nav-link').forEach(link => {\n        const href = link.getAttribute('href');\n        if (href !== '/' && path.includes(href)) {\n            link.classList.add('active');\n        } else if (href === '/' && path === '/') {\n            link.classList.add('active');\n        }\n    });\n\n    // 禁用侧边栏的鼠标滚轮事件\n    const sidebar = document.querySelector('.sidebar');\n    if (sidebar) {\n        sidebar.addEventListener('wheel', (e) => {\n            e.preventDefault();\n            e.stopPropagation();\n            return false;\n        });\n    }\n\n    // 添加退出登录确认\n    const logoutBtn = document.getElementById('logoutBtn');\n    if (logoutBtn) {\n        logoutBtn.addEventListener('click', function (e) {\n            e.preventDefault();\n            confirmAction('确定要退出登录吗？', function () {\n                window.location.href = '/auth/logout';\n            });\n        });\n    }\n\n    // 初始化Bootstrap工具提示\n    var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle=\"tooltip\"]'));\n    tooltipTriggerList.map(function (tooltipTriggerEl) {\n        return new bootstrap.Tooltip(tooltipTriggerEl);\n    });\n\n    // 初始化Bootstrap下拉菜单\n    var dropdownElementList = [].slice.call(document.querySelectorAll('.dropdown-toggle'));\n    dropdownElementList.map(function (dropdownToggleEl) {\n        return new bootstrap.Dropdown(dropdownToggleEl);\n    });\n\n    // 在这里添加其他全局JavaScript功能\n});\n\n// 显示通知\nfunction showNotification(message, type = 'info') {\n    // 使用通知管理器\n    if (window.NotificationManager) {\n        NotificationManager.show(message, type);\n        return;\n    }\n\n    // 如果通知管理器不可用，使用备用实现\n    let container = document.getElementById('notificationContainer');\n\n    // 确保容器唯一性\n    if (!container) {\n        container = document.createElement('div');\n        container.id = 'notificationContainer';\n        document.body.appendChild(container);\n    }\n\n    // 类型映射 - 确保使用正确的CSS类\n    let cssType = 'info';\n    switch (type) {\n        case 'success':\n            cssType = 'success';\n            break;\n        case 'error':\n        case 'danger':\n            cssType = 'error';\n            break;\n        case 'warning':\n            cssType = 'warning';\n            break;\n        default:\n            cssType = 'info';\n    }\n\n    // 创建纯净通知元素\n    const notification = document.createElement('div');\n    notification.className = `pure-notification ${cssType}-notification`;\n\n    // 使用文本节点避免HTML解析\n    const textNode = document.createTextNode(message);\n    notification.appendChild(textNode);\n\n    container.appendChild(notification);\n\n    // 强制布局刷新\n    notification.offsetHeight;\n\n    // 显示动画 - 添加show类并设置内联样式\n    notification.classList.add('show');\n    notification.style.opacity = '1';\n    notification.style.transform = 'translateY(0)';\n\n    // 自动移除\n    setTimeout(() => {\n        notification.classList.remove('show');\n        notification.style.opacity = '0';\n        notification.style.transform = 'translateY(-20px)';\n        setTimeout(() => {\n            if (notification.parentNode) {\n                container.removeChild(notification);\n            }\n        }, 300);\n    }, 3000);\n}\n\n// 格式化日期时间\nfunction formatDateTime(timestamp) {\n    if (!timestamp) return '';\n    const date = new Date(timestamp * 1000);\n    return date.toLocaleString('zh-CN', {\n        year: 'numeric',\n        month: '2-digit',\n        day: '2-digit',\n        hour: '2-digit',\n        minute: '2-digit',\n        second: '2-digit'\n    });\n}\n\n// 显示确认对话框\nfunction confirmAction(message, callback) {\n    if (confirm(message)) {\n        callback();\n    }\n}\n\n// 防抖函数\nfunction debounce(func, wait) {\n    let timeout;\n    return function () {\n        const context = this;\n        const args = arguments;\n        clearTimeout(timeout);\n        timeout = setTimeout(() => func.apply(context, args), wait);\n    };\n}\n\n// 节流函数\nfunction throttle(func, wait) {\n    let lastExec = 0;\n    return function () {\n        const context = this;\n        const args = arguments;\n        const now = Date.now();\n        if (now - lastExec >= wait) {\n            func.apply(context, args);\n            lastExec = now;\n        }\n    };\n}\n\n// 导出全局函数\nwindow.showNotification = showNotification;\nwindow.formatDateTime = formatDateTime;\nwindow.confirmAction = confirmAction;\nwindow.debounce = debounce;\nwindow.throttle = throttle; "
  },
  {
    "path": "WebUI/static/js/pages/about.js",
    "content": "document.addEventListener('DOMContentLoaded', function () {\n    // 检查最新版本\n    checkLatestVersion();\n\n    // 添加平滑滚动效果\n    document.querySelectorAll('a[href^=\"#\"]').forEach(anchor => {\n        anchor.addEventListener('click', function (e) {\n            e.preventDefault();\n            const target = document.querySelector(this.getAttribute('href'));\n            if (target) {\n                target.scrollIntoView({\n                    behavior: 'smooth',\n                    block: 'start'\n                });\n            }\n        });\n    });\n});\n\n// 检查最新版本\nfunction checkLatestVersion() {\n    const currentVersion = document.getElementById('currentVersion');\n    const latestVersion = document.getElementById('latestVersion');\n    const versionStatus = document.getElementById('versionStatus');\n\n    if (!currentVersion || !latestVersion || !versionStatus) return;\n\n    fetch('/about/api/version')\n        .then(response => response.json())\n        .then(data => {\n            if (data.latest_version) {\n                latestVersion.textContent = data.latest_version;\n\n                // 比较版本\n                if (data.latest_version === currentVersion.textContent) {\n                    versionStatus.className = 'badge bg-success';\n                    versionStatus.textContent = '已是最新版本';\n                } else {\n                    versionStatus.className = 'badge bg-warning';\n                    versionStatus.textContent = '有新版本可用';\n                }\n            }\n        })\n        .catch(error => {\n            console.error('检查版本出错:', error);\n            versionStatus.className = 'badge bg-secondary';\n            versionStatus.textContent = '版本检查失败';\n        });\n}\n\n// 复制文本到剪贴板\nfunction copyToClipboard(text) {\n    const textarea = document.createElement('textarea');\n    textarea.value = text;\n    document.body.appendChild(textarea);\n    textarea.select();\n\n    try {\n        document.execCommand('copy');\n        showToast('复制成功！');\n    } catch (err) {\n        console.error('复制失败:', err);\n        showToast('复制失败，请手动复制');\n    }\n\n    document.body.removeChild(textarea);\n}\n\n// 显示提示消息\nfunction showToast(message) {\n    const toast = document.createElement('div');\n    toast.className = 'toast align-items-center text-white bg-success';\n    toast.setAttribute('role', 'alert');\n    toast.setAttribute('aria-live', 'assertive');\n    toast.setAttribute('aria-atomic', 'true');\n\n    toast.innerHTML = `\n        <div class=\"d-flex\">\n            <div class=\"toast-body\">\n                ${message}\n            </div>\n            <button type=\"button\" class=\"btn-close btn-close-white me-2 m-auto\" data-bs-dismiss=\"toast\" aria-label=\"Close\"></button>\n        </div>\n    `;\n\n    const container = document.createElement('div');\n    container.className = 'toast-container position-fixed bottom-0 end-0 p-3';\n    container.appendChild(toast);\n    document.body.appendChild(container);\n\n    const bsToast = new bootstrap.Toast(toast);\n    bsToast.show();\n\n    toast.addEventListener('hidden.bs.toast', () => {\n        document.body.removeChild(container);\n    });\n} "
  },
  {
    "path": "WebUI/static/js/pages/auth.js",
    "content": "document.addEventListener('DOMContentLoaded', function () {\n    // 添加登录页面专用类\n    document.body.classList.add('login-page');\n\n    // 处理表单验证\n    const form = document.querySelector('form');\n    if (form) {\n        form.addEventListener('submit', function (event) {\n            if (!form.checkValidity()) {\n                event.preventDefault();\n                event.stopPropagation();\n            }\n            form.classList.add('was-validated');\n        });\n    }\n\n    // 处理记住我复选框\n    const rememberMe = document.querySelector('#remember_me');\n    if (rememberMe) {\n        // 从localStorage读取之前的选择\n        const remembered = localStorage.getItem('remember_me') === 'true';\n        rememberMe.checked = remembered;\n\n        // 保存选择到localStorage\n        rememberMe.addEventListener('change', function () {\n            localStorage.setItem('remember_me', this.checked);\n        });\n    }\n\n    // 处理用户名自动填充\n    const usernameInput = document.querySelector('#username');\n    if (usernameInput) {\n        const savedUsername = localStorage.getItem('last_username');\n        if (savedUsername) {\n            usernameInput.value = savedUsername;\n        }\n\n        // 保存用户名到localStorage\n        form.addEventListener('submit', function () {\n            if (rememberMe && rememberMe.checked) {\n                localStorage.setItem('last_username', usernameInput.value);\n            } else {\n                localStorage.removeItem('last_username');\n            }\n        });\n    }\n\n    // 处理闪现消息\n    const messages = document.querySelectorAll('.flash-message');\n    messages.forEach(message => {\n        const type = message.dataset.type || 'info';\n        const text = message.textContent;\n        showNotification(text, type);\n        message.remove();\n    });\n}); "
  },
  {
    "path": "WebUI/static/js/pages/config.js",
    "content": "let isSaving = false;\n\n// 页面加载完成后执行\ndocument.addEventListener('DOMContentLoaded', function () {\n    // 加载配置schema和配置数据\n    loadConfigSchemas();\n\n    // 创建节流版本的保存函数，500毫秒内只能触发一次\n    const throttledSave = throttle(function () {\n        // 如果已经在保存中，直接返回\n        if (isSaving) {\n            showNotification('正在保存，请稍候...', 'warning');\n            return;\n        }\n\n        // 设置保存状态\n        isSaving = true;\n\n        // 立即禁用按钮，防止多次点击\n        this.disabled = true;\n\n        // 执行保存操作\n        saveAllConfigs();\n    }, 500);\n\n    // 绑定保存按钮事件\n    const saveButton = document.getElementById('saveConfig');\n    if (saveButton) {\n        saveButton.addEventListener('click', throttledSave);\n    }\n\n    // 绑定动态添加的元素事件\n    document.addEventListener('click', function (e) {\n        // 处理添加数组项按钮点击\n        if (e.target.closest('.add-array-item')) {\n            const button = e.target.closest('.add-array-item');\n            const fieldId = button.dataset.fieldId;\n            const configName = button.dataset.config;\n            const propPath = button.dataset.path;\n            handleAddArrayItem(fieldId, configName, propPath);\n        }\n\n        // 处理删除数组项按钮点击\n        if (e.target.closest('.remove-array-item')) {\n            const button = e.target.closest('.remove-array-item');\n            button.closest('.array-item').remove();\n        }\n\n        // 处理配置折叠按钮点击\n        if (e.target.closest('.config-header')) {\n            const header = e.target.closest('.config-header');\n            const icon = header.querySelector('i');\n            icon.classList.toggle('fa-chevron-down');\n            icon.classList.toggle('fa-chevron-up');\n        }\n    });\n});\n\n// 加载配置schema\nasync function loadConfigSchemas() {\n    try {\n        const response = await fetch('/config/api/schemas');\n        const data = await response.json();\n\n        if (data.code === 0 && data.data) {\n            const schemas = data.data;\n            const schemaKeys = Object.keys(schemas);\n\n            if (schemaKeys.length === 0) {\n                console.warn(\"Schema数据为空对象\");\n                document.getElementById('configSections').innerHTML = '<div class=\"alert alert-warning\">没有可用的配置schema</div>';\n                return;\n            }\n\n            // 加载配置数据\n            await loadConfigs(schemas);\n        } else {\n            throw new Error(data.msg || '加载配置schema失败');\n        }\n    } catch (error) {\n        console.error('加载配置schema失败:', error);\n        document.getElementById('configSections').innerHTML = `<div class=\"alert alert-danger\">加载配置schema失败: ${error.message}</div>`;\n    }\n}\n\n// 加载配置数据\nasync function loadConfigs(schemas) {\n    try {\n        const response = await fetch('/config/api/config');\n        const data = await response.json();\n\n        if (data.code === 0 && data.data) {\n            renderConfigForm(schemas, data.data);\n        } else {\n            throw new Error(data.msg || '加载配置数据失败');\n        }\n    } catch (error) {\n        console.error('加载配置数据失败:', error);\n        document.getElementById('configSections').innerHTML = `<div class=\"alert alert-danger\">加载配置数据失败: ${error.message}</div>`;\n    }\n}\n\n// 渲染配置表单\nfunction renderConfigForm(schemas, configs) {\n    const configSections = document.getElementById('configSections');\n    configSections.innerHTML = '';\n    \n    // 获取配置节顺序\n    const sectionsOrder = schemas._meta && schemas._meta.sectionsOrder \n        ? schemas._meta.sectionsOrder \n        : Object.keys(schemas).filter(name => name !== '_meta');\n    \n    console.log('按原始顺序渲染配置节:', sectionsOrder);\n    \n    // 删除元数据，防止渲染成配置节\n    delete schemas._meta;\n\n    // 按原始顺序遍历配置节\n    for (const configName of sectionsOrder) {\n        if (!schemas[configName]) continue;\n        \n        const schema = schemas[configName];\n        const config = configs[configName] || {};\n\n        const section = document.createElement('div');\n        section.className = 'config-section';\n        section.dataset.config = configName;\n\n        section.innerHTML = `\n            <div class=\"config-header\" data-toggle=\"collapse\" data-target=\"#config-${configName}\">\n                <h5>${schema.title || configName}</h5>\n                <i class=\"fas fa-chevron-down\"></i>\n            </div>\n            <div class=\"config-body collapse show\" id=\"config-${configName}\">\n                <div class=\"config-description mb-3\">\n                    ${schema.description ? `<p class=\"text-muted\">${schema.description}</p>` : ''}\n                </div>\n                <div class=\"config-fields\">\n                    ${renderSchemaProperties(schema, config, configName)}\n                </div>\n            </div>\n        `;\n\n        configSections.appendChild(section);\n    }\n}\n\n// 渲染schema属性\nfunction renderSchemaProperties(schema, config, configName, parentPath = '') {\n    let html = '';\n\n    if (schema.properties) {\n        // 获取属性按照配置文件中的原始顺序\n        const propertyOrder = schema.propertyOrder || Object.keys(schema.properties);\n        \n        // 按原始顺序遍历属性\n        for (const propName of propertyOrder) {\n            // 跳过不存在的属性\n            if (!schema.properties[propName]) continue;\n            \n            const propSchema = schema.properties[propName];\n            const propValue = config[propName];\n            const propPath = parentPath ? `${parentPath}.${propName}` : propName;\n\n            html += renderPropertyField(propName, propSchema, propValue, configName, propPath);\n        }\n    }\n\n    return html;\n}\n\n// 渲染属性字段\nfunction renderPropertyField(propName, propSchema, propValue, configName, propPath) {\n    const fieldId = `${configName}-${propPath.replace(/\\./g, '-')}`;\n    const fieldName = `${configName}[${propPath}]`;\n    const required = propSchema.required && propSchema.required.includes(propName);\n\n    let fieldHtml = `\n        <div class=\"config-field\" data-path=\"${propPath}\">\n            <label for=\"${fieldId}\">${propSchema.title || propName}${required ? ' <span class=\"text-danger\">*</span>' : ''}</label>\n    `;\n\n    switch (propSchema.type) {\n        case 'string':\n            if (propSchema.enum) {\n                fieldHtml += renderEnumField(fieldId, fieldName, propSchema, propValue, required);\n            } else if (propSchema.format === 'password') {\n                fieldHtml += renderPasswordField(fieldId, fieldName, propValue, required);\n            } else if (propSchema.format === 'textarea') {\n                fieldHtml += renderTextareaField(fieldId, fieldName, propValue, required);\n            } else {\n                fieldHtml += renderTextField(fieldId, fieldName, propValue, required);\n            }\n            break;\n\n        case 'number':\n        case 'integer':\n            fieldHtml += renderNumberField(fieldId, fieldName, propSchema, propValue, required);\n            break;\n\n        case 'boolean':\n            fieldHtml += renderBooleanField(fieldId, fieldName, propValue);\n            break;\n\n        case 'array':\n            fieldHtml += renderArrayField(fieldId, propSchema, propValue || [], configName, propPath);\n            break;\n\n        case 'object':\n            fieldHtml += renderObjectField(fieldId, propSchema, propValue || {}, configName, propPath);\n            break;\n\n        default:\n            fieldHtml += `<div class=\"alert alert-warning\">不支持的类型: ${propSchema.type}</div>`;\n    }\n\n    if (propSchema.description) {\n        fieldHtml += `<small class=\"form-text text-muted\">${propSchema.description}</small>`;\n    }\n\n    fieldHtml += '</div>';\n    return fieldHtml;\n}\n\n// 渲染枚举字段\nfunction renderEnumField(fieldId, fieldName, schema, value, required) {\n    return `\n        <select class=\"form-control\" id=\"${fieldId}\" name=\"${fieldName}\" ${required ? 'required' : ''}>\n            <option value=\"\">-- 请选择 --</option>\n            ${schema.enum.map(option => `\n                <option value=\"${option}\" ${value === option ? 'selected' : ''}>${option}</option>\n            `).join('')}\n        </select>\n    `;\n}\n\n// 渲染密码字段\nfunction renderPasswordField(fieldId, fieldName, value, required) {\n    return `\n        <input type=\"password\" class=\"form-control\" id=\"${fieldId}\" name=\"${fieldName}\" \n            value=\"${value || ''}\" ${required ? 'required' : ''}>\n    `;\n}\n\n// 渲染文本区域字段\nfunction renderTextareaField(fieldId, fieldName, value, required) {\n    return `\n        <textarea class=\"form-control\" id=\"${fieldId}\" name=\"${fieldName}\" \n            rows=\"4\" ${required ? 'required' : ''}>${value || ''}</textarea>\n    `;\n}\n\n// 渲染文本字段\nfunction renderTextField(fieldId, fieldName, value, required) {\n    return `\n        <input type=\"text\" class=\"form-control\" id=\"${fieldId}\" name=\"${fieldName}\" \n            value=\"${value || ''}\" ${required ? 'required' : ''}>\n    `;\n}\n\n// 渲染数字字段\nfunction renderNumberField(fieldId, fieldName, schema, value, required) {\n    return `\n        <input type=\"number\" class=\"form-control\" id=\"${fieldId}\" name=\"${fieldName}\" \n            value=\"${value !== undefined ? value : ''}\" \n            ${schema.minimum !== undefined ? `min=\"${schema.minimum}\"` : ''} \n            ${schema.maximum !== undefined ? `max=\"${schema.maximum}\"` : ''} \n            ${required ? 'required' : ''}>\n    `;\n}\n\n// 渲染布尔字段\nfunction renderBooleanField(fieldId, fieldName, value) {\n    return `\n        <div class=\"form-check\">\n            <input type=\"checkbox\" class=\"form-check-input\" id=\"${fieldId}\" name=\"${fieldName}\" \n                ${value ? 'checked' : ''}>\n            <label class=\"form-check-label\" for=\"${fieldId}\">启用</label>\n        </div>\n    `;\n}\n\n// 渲染数组字段\nfunction renderArrayField(fieldId, schema, value, configName, propPath) {\n    let html = `\n        <div class=\"array-container\" id=\"${fieldId}-container\">\n            <div class=\"array-items\">\n    `;\n\n    // 确保value是有效的数组\n    const arrayValue = Array.isArray(value) ? value : [];\n    \n    console.log(`渲染数组字段 ${propPath}，值:`, arrayValue);\n\n    if (arrayValue.length > 0) {\n        arrayValue.forEach((item, index) => {\n            html += renderArrayItem(fieldId, schema, item, index, configName, propPath);\n        });\n    } else {\n        // 如果数组为空，添加一个空元素让用户有起点\n        html += renderArrayItem(fieldId, schema, \"\", 0, configName, propPath);\n    }\n\n    html += `\n            </div>\n            <div class=\"array-controls\">\n                <button type=\"button\" class=\"btn btn-sm btn-primary add-array-item\" \n                    data-field-id=\"${fieldId}\" data-config=\"${configName}\" data-path=\"${propPath}\">\n                    <i class=\"fas fa-plus\"></i> 添加项\n                </button>\n            </div>\n        </div>\n    `;\n\n    return html;\n}\n\n// 渲染数组项\nfunction renderArrayItem(fieldId, schema, value, index, configName, propPath) {\n    const itemId = `${fieldId}-item-${index}`;\n    const itemName = `${configName}[${propPath}][${index}]`;\n\n    let html = `\n        <div class=\"array-item\" data-index=\"${index}\">\n    `;\n\n    if (schema.items.type === 'string') {\n        // 确保value是字符串，避免undefined或null\n        const itemValue = value !== null && value !== undefined ? String(value) : '';\n        html += `\n            <input type=\"text\" class=\"form-control\" id=\"${itemId}\" name=\"${itemName}\" value=\"${itemValue}\">\n        `;\n    } else if (schema.items.type === 'number' || schema.items.type === 'integer') {\n        // 确保value是数字，避免undefined或null\n        const itemValue = value !== null && value !== undefined ? Number(value) : '';\n        html += `\n            <input type=\"number\" class=\"form-control\" id=\"${itemId}\" name=\"${itemName}\" value=\"${itemValue}\">\n        `;\n    } else if (schema.items.type === 'object') {\n        html += `\n            <div class=\"object-property w-100\">\n                ${renderSchemaProperties(schema.items, value || {}, configName, `${propPath}[${index}]`)}\n            </div>\n        `;\n    }\n\n    html += `\n            <button type=\"button\" class=\"btn btn-sm btn-danger remove-array-item ml-2\">\n                <i class=\"fas fa-trash\"></i>\n            </button>\n        </div>\n    `;\n\n    return html;\n}\n\n// 渲染对象字段\nfunction renderObjectField(fieldId, schema, value, configName, propPath) {\n    return `\n        <div class=\"object-container\">\n            <div class=\"object-properties\">\n                ${renderSchemaProperties(schema, value, configName, propPath)}\n            </div>\n        </div>\n    `;\n}\n\n// 处理添加数组项\nasync function handleAddArrayItem(fieldId, configName, propPath) {\n    try {\n        const container = document.querySelector(`#${fieldId}-container .array-items`);\n        const index = container.children.length;\n\n        const response = await fetch('/config/api/schemas');\n        const data = await response.json();\n\n        if (data.code === 0 && data.data) {\n            const schemas = data.data;\n            const schema = schemas[configName];\n\n            if (!schema) {\n                throw new Error(`未找到配置 '${configName}' 的schema`);\n            }\n\n            // 找到数组的schema\n            const pathParts = propPath.split('.');\n            let currentSchema = schema;\n\n            for (const part of pathParts) {\n                if (currentSchema.properties && currentSchema.properties[part]) {\n                    currentSchema = currentSchema.properties[part];\n                } else {\n                    throw new Error(`在路径 '${propPath}' 中未找到部分 '${part}' 的schema`);\n                }\n            }\n\n            if (currentSchema.type !== 'array') {\n                throw new Error(`路径 '${propPath}' 的schema不是数组类型`);\n            }\n\n            if (!currentSchema.items) {\n                throw new Error('数组schema缺少items定义');\n            }\n\n            const newItem = renderArrayItem(fieldId, currentSchema, null, index, configName, propPath);\n            container.insertAdjacentHTML('beforeend', newItem);\n        } else {\n            throw new Error(data.msg || '获取schema失败');\n        }\n    } catch (error) {\n        console.error('添加数组项失败:', error);\n        showNotification(error.message, 'error');\n    }\n}\n\n// 保存所有配置\nasync function saveAllConfigs() {\n    const configs = {};\n    const saveButton = document.getElementById('saveConfig');\n    const originalBtnText = saveButton.innerHTML;\n\n    try {\n        // 验证表单\n        if (!validateForm()) {\n            saveButton.disabled = false;\n            saveButton.innerHTML = originalBtnText;\n            isSaving = false;\n            return;\n        }\n\n        // 收集所有配置\n        document.querySelectorAll('.config-section').forEach(section => {\n            const configName = section.dataset.config;\n            configs[configName] = collectConfigData(configName);\n        });\n\n        const configNames = Object.keys(configs);\n        let failedConfigs = 0;\n\n        // 逐个保存配置\n        for (let i = 0; i < configNames.length; i++) {\n            const configName = configNames[i];\n            const progress = Math.round((i / configNames.length) * 100);\n\n            saveButton.innerHTML = `<i class=\"fas fa-spinner fa-spin\"></i> 保存中 ${progress}%`;\n\n            try {\n                const success = await saveConfig(configName, configs[configName]);\n                if (!success) failedConfigs++;\n            } catch (error) {\n                console.error(`保存配置 ${configName} 失败:`, error);\n                failedConfigs++;\n            }\n        }\n\n        // 显示保存结果\n        if (failedConfigs > 0) {\n            showNotification(`保存完成，但有 ${failedConfigs} 个配置保存失败`, 'error');\n        } else {\n            showNotification('所有配置保存成功', 'success');\n        }\n    } catch (error) {\n        console.error('保存配置失败:', error);\n        showNotification('保存配置失败: ' + error.message, 'error');\n    } finally {\n        saveButton.disabled = false;\n        saveButton.innerHTML = originalBtnText;\n        isSaving = false;\n    }\n}\n\n// 验证表单\nfunction validateForm() {\n    const requiredInputs = document.querySelectorAll('input[required], select[required], textarea[required]');\n    let isValid = true;\n    let firstInvalidElement = null;\n\n    // 清除之前的验证错误提示\n    document.querySelectorAll('.invalid-feedback').forEach(el => el.remove());\n    document.querySelectorAll('.is-invalid').forEach(el => el.classList.remove('is-invalid'));\n\n    // 验证每个必填字段\n    requiredInputs.forEach(input => {\n        const value = input.value;\n\n        if (!value || value.trim() === '') {\n            isValid = false;\n            input.classList.add('is-invalid');\n\n            const feedback = document.createElement('div');\n            feedback.className = 'invalid-feedback';\n            feedback.textContent = '此字段不能为空';\n            input.parentNode.appendChild(feedback);\n\n            if (!firstInvalidElement) {\n                firstInvalidElement = input;\n            }\n        }\n    });\n\n    // 滚动到第一个错误字段\n    if (!isValid && firstInvalidElement) {\n        firstInvalidElement.scrollIntoView({behavior: 'smooth', block: 'center'});\n        showNotification('表单验证失败，请检查必填字段', 'error');\n    }\n\n    return isValid;\n}\n\n// 收集配置数据\nfunction collectConfigData(configName) {\n    const config = {};\n    const sectionElement = document.querySelector(`.config-section[data-config=\"${configName}\"]`);\n    \n    // 处理基本输入字段\n    sectionElement.querySelectorAll('input[type=\"text\"], input[type=\"number\"], input[type=\"password\"], select, textarea').forEach(input => {\n        if (!input.name || !input.name.startsWith(`${configName}[`)) return;\n        \n        const path = input.name.match(/\\[(.*?)\\]/)[1];\n        let value = input.value;\n        \n        // 处理数字类型\n        if (input.type === 'number') {\n            value = value ? Number(value) : null;\n        }\n        \n        setNestedProperty(config, path, value);\n    });\n    \n    // 处理复选框\n    sectionElement.querySelectorAll('input[type=\"checkbox\"]').forEach(input => {\n        if (!input.name || !input.name.startsWith(`${configName}[`)) return;\n        \n        const path = input.name.match(/\\[(.*?)\\]/)[1];\n        const value = input.checked;\n        \n        setNestedProperty(config, path, value);\n    });\n    \n    // 处理数组类型\n    sectionElement.querySelectorAll('.array-container').forEach(container => {\n        const fieldId = container.id.replace('-container', '');\n        const fieldMatch = fieldId.match(new RegExp(`${configName}-(.+)`));\n        if (!fieldMatch) return;\n        \n        const path = fieldMatch[1].replace(/-/g, '.');\n        const arrayItems = [];\n        \n        // 收集数组中的所有项\n        container.querySelectorAll('.array-item').forEach(item => {\n            const input = item.querySelector('input, select, textarea');\n            if (input) {\n                let value = input.value;\n                if (input.type === 'number') {\n                    value = value ? Number(value) : null;\n                }\n                arrayItems.push(value);\n            }\n        });\n        \n        // 设置数组值\n        setNestedProperty(config, path, arrayItems);\n    });\n    \n    console.log(`收集的配置数据 ${configName}:`, config);\n    return config;\n}\n\n// 设置嵌套属性\nfunction setNestedProperty(obj, path, value) {\n    // 如果路径中包含连字符，直接设置为字段名，不再拆分为嵌套路径\n    if (path.includes('-')) {\n        obj[path] = value;\n        return;\n    }\n    \n    const parts = path.split('.');\n    let current = obj;\n    \n    for (let i = 0; i < parts.length - 1; i++) {\n        const part = parts[i];\n        if (current[part] === undefined) {\n            // 检查下一部分是否是数字，决定创建对象还是数组\n            const nextPart = parts[i + 1];\n            current[part] = !isNaN(parseInt(nextPart)) ? [] : {};\n        }\n        current = current[part];\n    }\n    \n    const lastPart = parts[parts.length - 1];\n    current[lastPart] = value;\n}\n\n// 保存单个配置\nasync function saveConfig(configName, configData) {\n    console.log(`发送配置数据 ${configName}:`, JSON.stringify(configData, null, 2));\n    \n    try {\n        const response = await fetch(`/config/api/config/${configName}`, {\n            method: 'POST',\n            headers: {\n                'Content-Type': 'application/json'\n            },\n            body: JSON.stringify(configData)\n        });\n\n        const data = await response.json();\n        if (data.code !== 0) {\n            console.error(`保存配置 ${configName} 失败:`, data.msg);\n            showNotification(`保存配置 ${configName} 失败: ${data.msg}`, 'error');\n            return false;\n        }\n        return true;\n    } catch (error) {\n        console.error(`保存配置 ${configName} 失败:`, error);\n        showNotification(`保存配置 ${configName} 失败: ${error.message}`, 'error');\n        return false;\n    }\n} "
  },
  {
    "path": "WebUI/static/js/pages/explorer.js",
    "content": "document.addEventListener('DOMContentLoaded', function () {\n    // 初始化文件浏览器\n    initFileBrowser('file-explorer', window.initialPath || '');\n\n    // 处理视图切换\n    const viewToggleBtn = document.getElementById('view-toggle');\n    if (viewToggleBtn) {\n        viewToggleBtn.addEventListener('click', function () {\n            const currentView = localStorage.getItem('explorer-view') || 'list';\n            const newView = currentView === 'list' ? 'grid' : 'list';\n\n            // 保存视图选择\n            localStorage.setItem('explorer-view', newView);\n\n            // 更新视图\n            updateExplorerView(newView);\n        });\n\n        // 初始化视图\n        const savedView = localStorage.getItem('explorer-view') || 'list';\n        updateExplorerView(savedView);\n    }\n\n    // 更新视图显示\n    function updateExplorerView(view) {\n        const container = document.querySelector('.file-browser-container');\n        if (!container) return;\n\n        if (view === 'grid') {\n            container.classList.add('view-grid');\n            container.classList.remove('view-list');\n            viewToggleBtn.innerHTML = '<i class=\"fas fa-list\"></i>';\n        } else {\n            container.classList.add('view-list');\n            container.classList.remove('view-grid');\n            viewToggleBtn.innerHTML = '<i class=\"fas fa-th\"></i>';\n        }\n    }\n\n    // 处理文件操作\n    function handleFileOperation(operation, path) {\n        switch (operation) {\n            case 'open':\n                if (isImageFile(path)) {\n                    previewImage(path);\n                } else if (isTextFile(path)) {\n                    openFileViewer(path);\n                } else {\n                    downloadFile(path);\n                }\n                break;\n            case 'download':\n                downloadFile(path);\n                break;\n            case 'delete':\n                deleteFile(path);\n                break;\n        }\n    }\n\n    // 检查文件类型\n    function isImageFile(path) {\n        const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'];\n        return imageExtensions.some(ext => path.toLowerCase().endsWith(ext));\n    }\n\n    function isTextFile(path) {\n        const textExtensions = ['.txt', '.log', '.md', '.json', '.yml', '.yaml', '.toml', '.ini', '.conf'];\n        return textExtensions.some(ext => path.toLowerCase().endsWith(ext));\n    }\n\n    // 预览图片\n    function previewImage(path) {\n        const modal = new ImagePreviewModal({\n            title: '图片预览',\n            imagePath: path\n        });\n        modal.show();\n    }\n\n    // 打开文件查看器\n    function openFileViewer(path) {\n        window.location.href = `/explorer/view/${encodeURIComponent(path)}`;\n    }\n\n    // 下载文件\n    function downloadFile(path) {\n        window.location.href = `/file/download?path=${encodeURIComponent(path)}`;\n    }\n\n    // 删除文件\n    function deleteFile(path) {\n        const modal = new ConfirmModal({\n            title: '确认删除',\n            message: `确定要删除 ${path} 吗？此操作不可恢复。`,\n            confirmText: '删除',\n            cancelText: '取消',\n            onConfirm: async () => {\n                try {\n                    const response = await fetch('/file/api/delete', {\n                        method: 'POST',\n                        headers: {\n                            'Content-Type': 'application/json'\n                        },\n                        body: JSON.stringify({path})\n                    });\n\n                    const result = await response.json();\n                    if (result.success) {\n                        showNotification('文件删除成功', 'success');\n                        // 重新加载文件列表\n                        const refreshBtn = document.getElementById('file-explorer-refresh');\n                        if (refreshBtn) {\n                            refreshBtn.click();\n                        }\n                    } else {\n                        showNotification(result.message || '删除失败', 'error');\n                    }\n                } catch (error) {\n                    console.error('删除文件失败:', error);\n                    showNotification('删除文件失败', 'error');\n                }\n            }\n        });\n        modal.show();\n    }\n});\n\n// 图片预览模态框类\nclass ImagePreviewModal extends BaseModal {\n    constructor(options = {}) {\n        super({\n            ...options,\n            size: 'lg'\n        });\n        this.imagePath = options.imagePath;\n    }\n\n    getModalBody() {\n        return `\n            <div class=\"text-center\">\n                <img src=\"/file/preview?path=${encodeURIComponent(this.imagePath)}\" \n                     class=\"img-fluid\" \n                     alt=\"图片预览\"\n                     style=\"max-height: 80vh;\">\n            </div>\n        `;\n    }\n\n    getModalFooter() {\n        return `\n            <div class=\"modal-footer\">\n                <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">关闭</button>\n                <a href=\"/file/download?path=${encodeURIComponent(this.imagePath)}\" \n                   class=\"btn btn-primary\">\n                    <i class=\"fas fa-download\"></i> 下载\n                </a>\n            </div>\n        `;\n    }\n} "
  },
  {
    "path": "WebUI/static/js/pages/overview.js",
    "content": "let autoRefreshTimer = null;\nlet previousStatus = null; // 保存上一次的状态数据\nlet socket;\nlet logViewer; // 全局日志查看器元素\nlet notificationContainer; // 全局通知容器元素\n\n// 页面加载完成后执行\ndocument.addEventListener('DOMContentLoaded', function () {\n    // 初始化变量 - 获取DOM元素\n    const refreshBtn = document.getElementById('refreshBtn');\n    const startBtn = document.getElementById('startBtn');\n    const stopBtn = document.getElementById('stopBtn');\n    const restartBtn = document.getElementById('restartBtn');\n\n    // 全局元素\n    logViewer = document.getElementById('logViewer');\n    notificationContainer = document.getElementById('notificationContainer');\n\n    // 防止日志滚动传播到页面\n    if (logViewer) {\n        logViewer.addEventListener('wheel', function (e) {\n            if (e.deltaY !== 0) {\n                e.stopPropagation();\n            }\n        });\n    }\n\n    // 绑定事件\n    if (refreshBtn) {\n        refreshBtn.addEventListener('click', refreshAllData);\n    }\n\n    if (startBtn) {\n        startBtn.addEventListener('click', handleBotStart);\n    }\n\n    if (stopBtn) {\n        stopBtn.addEventListener('click', handleBotStop);\n    }\n\n    if (restartBtn) {\n        restartBtn.addEventListener('click', handleBotRestart);\n    }\n\n    // 加载所有数据\n    refreshAllData();\n\n    // 初始化WebSocket连接\n    initWebSocket();\n\n    // 启动自动刷新\n    startAutoRefresh();\n\n    // 页面关闭时清理\n    window.addEventListener('beforeunload', function () {\n        stopAutoRefresh();\n        if (socket && socket.connected) {\n            socket.disconnect();\n        }\n    });\n});\n\n// 初始化WebSocket连接\nfunction initWebSocket() {\n    try {\n        // 确保logViewer已存在\n        if (!logViewer) {\n            logViewer = document.getElementById('logViewer');\n            if (!logViewer) {\n                return;\n            }\n        }\n\n        // 显示连接中状态\n        logViewer.innerHTML = '<div class=\"text-center p-3\"><div class=\"spinner-border text-primary\" role=\"status\"><span class=\"visually-hidden\">连接中...</span></div><p class=\"mt-2 text-muted\">正在连接到日志服务器...</p></div>';\n\n        // 连接WebSocket，指定明确的选项\n        const serverUrl = window.location.protocol + '//' + window.location.host;\n        socket = io(serverUrl, {\n            transports: ['websocket', 'polling'],\n            reconnectionAttempts: 5,\n            reconnectionDelay: 1000,\n            timeout: 5000\n        });\n\n        // 连接建立事件\n        socket.on('connect', function () {\n            // 移除加载中状态\n            logViewer.innerHTML = '<div class=\"text-muted p-3\">正在请求日志...</div>';\n\n            // 连接后请求初始日志\n            socket.emit('request_logs', {n: 100});\n        });\n\n        // 断开连接事件\n        socket.on('disconnect', function () {\n            if (logViewer) {\n                logViewer.innerHTML = '<div class=\"text-danger p-3\">与服务器的连接已断开</div>';\n            }\n        });\n\n        // 接收日志响应\n        socket.on('logs_response', function (data) {\n            if (data && data.logs) {\n                displayLogs(data.logs);\n            } else {\n                if (logViewer) {\n                    logViewer.innerHTML = '<div class=\"text-warning p-3\">收到的日志数据无效</div>';\n                }\n            }\n        });\n\n        // 新日志事件\n        socket.on('new_logs', function (data) {\n            if (data && data.logs) {\n                appendLogs(data.logs);\n            }\n        });\n\n        // 连接错误事件\n        socket.on('connect_error', function (error) {\n            if (logViewer) {\n                logViewer.innerHTML = '<div class=\"text-danger p-3\">连接服务器失败</div>';\n            }\n            showNotification('日志连接失败，将在稍后重试...', 'warning');\n        });\n\n    } catch (error) {\n        if (logViewer) {\n            logViewer.innerHTML = '<div class=\"text-danger p-3\">初始化日志连接失败</div>';\n        }\n    }\n}\n\n// 显示初始日志\nfunction displayLogs(logs) {\n    if (!logViewer) {\n        return;\n    }\n\n    try {\n        // 清空现有内容\n        logViewer.innerHTML = '';\n\n        // 检查日志数据\n        if (!logs || !Array.isArray(logs) || logs.length === 0) {\n            logViewer.innerHTML = '<div class=\"text-muted p-3\">暂无日志数据</div>';\n            return;\n        }\n\n        // 创建文档片段，提高性能\n        const fragment = document.createDocumentFragment();\n\n        // 处理每行日志\n        logs.forEach((log, index) => {\n            // 创建日志行\n            const logLine = document.createElement('div');\n            logLine.className = 'log-line';\n            logLine.textContent = log;\n\n            // 应用样式\n            applyLogLevelStyle(logLine, log);\n\n            // 添加到文档片段\n            fragment.appendChild(logLine);\n        });\n\n        // 一次性添加所有日志行到DOM\n        logViewer.appendChild(fragment);\n\n        // 滚动到底部\n        logViewer.scrollTop = logViewer.scrollHeight;\n    } catch (error) {\n        logViewer.innerHTML = '<div class=\"text-danger p-3\">显示日志时出错</div>';\n    }\n}\n\n// 追加新日志\nfunction appendLogs(logs) {\n    if (!logViewer) {\n        return;\n    }\n\n    if (!logs || !Array.isArray(logs) || logs.length === 0) {\n        return;\n    }\n\n    try {\n        // 在更新前检查是否在底部\n        const isScrolledToBottom = logViewer.scrollHeight - logViewer.clientHeight <= logViewer.scrollTop + 5;\n\n        // 创建文档片段\n        const fragment = document.createDocumentFragment();\n\n        logs.forEach(log => {\n            // 创建日志行\n            const logLine = document.createElement('div');\n            logLine.className = 'log-line';\n            logLine.textContent = log;\n\n            // 应用样式\n            applyLogLevelStyle(logLine, log);\n\n            // 添加到文档片段\n            fragment.appendChild(logLine);\n        });\n\n        // 添加到日志查看器\n        logViewer.appendChild(fragment);\n\n        // 只有当之前在底部时才滚动到底部\n        if (isScrolledToBottom) {\n            logViewer.scrollTop = logViewer.scrollHeight;\n        }\n    } catch (error) {\n        // 出错时静默处理，不中断用户体验\n    }\n}\n\n// 应用日志级别样式\nfunction applyLogLevelStyle(logElement, logText) {\n    if (!logElement || !logText) return;\n\n    if (logText.includes('DEBUG')) {\n        logElement.classList.add('log-debug');\n    } else if (logText.includes('INFO')) {\n        logElement.classList.add('log-info');\n    } else if (logText.includes('SUCCESS')) {\n        logElement.classList.add('log-success');\n    } else if (logText.includes('WARNING')) {\n        logElement.classList.add('log-warning');\n    } else if (logText.includes('ERROR')) {\n        logElement.classList.add('log-error');\n    } else if (logText.includes('CRITICAL')) {\n        logElement.classList.add('log-critical');\n    } else if (logText.includes('WEBUI')) {\n        logElement.classList.add('log-webui');\n    }\n}\n\n// 刷新状态\nfunction refreshStatus() {\n    fetch('/overview/api/status')\n        .then(response => response.json())\n        .then(status => {\n            // 检查数据是否有变化，如果没有变化则不更新\n            if (previousStatus && JSON.stringify(previousStatus) === JSON.stringify(status)) {\n                return;\n            }\n\n            // 保存当前数据用于下次比较\n            previousStatus = JSON.parse(JSON.stringify(status));\n\n            // 更新状态信息\n            updateStatusDisplay(status);\n            updateControlButtons(status);\n\n            // 更新指标数据\n            updateMetricsDisplay(status);\n        })\n        .catch(error => {\n            // 出错时静默处理\n        });\n}\n\n// 更新状态显示\nfunction updateStatusDisplay(status) {\n    const statusIndicator = document.querySelector('tbody tr:first-child td span');\n    const pidCell = document.querySelector('tbody tr:nth-child(2) td');\n    const startTimeCell = document.querySelector('tbody tr:nth-child(3) td');\n\n    if (status.running) {\n        statusIndicator.className = 'badge bg-success';\n        statusIndicator.textContent = '运行中';\n    } else {\n        statusIndicator.className = 'badge bg-danger';\n        statusIndicator.textContent = '已停止';\n    }\n\n    pidCell.textContent = status.pid || '无';\n    startTimeCell.textContent = status.start_time || '未启动';\n}\n\n// 更新指标显示\nfunction updateMetricsDisplay(status) {\n    // 更新卡片上的指标数据\n    document.querySelectorAll('.status-card-value').forEach(function (element) {\n        const metricType = element.dataset.metric;\n        if (metricType && status[metricType] !== undefined) {\n            element.textContent = status[metricType];\n        }\n    });\n\n    // 更新头像和账号信息\n    const profilePicture = document.getElementById('profilePicture');\n    const nicknameElement = document.getElementById('botNickname');\n    const wxidElement = document.getElementById('botWxid');\n    const aliasElement = document.getElementById('botAlias');\n\n    if (profilePicture) profilePicture.innerHTML = status.avatar ? `<i><img src=\"${status.avatar}\" alt=\"QRCode\"></i>` : '<i class=\"fas fa-user-circle\"></i>';\n    if (nicknameElement) nicknameElement.textContent = status.nickname || '未登陆';\n    if (wxidElement) wxidElement.textContent = status.wxid || '未登陆';\n    if (aliasElement) aliasElement.textContent = status.alias || '未登陆';\n}\n\n// 更新控制按钮\nfunction updateControlButtons(status) {\n    const startBtn = document.getElementById('startBtn');\n    const stopBtn = document.getElementById('stopBtn');\n    const restartBtn = document.getElementById('restartBtn');\n\n    if (startBtn) {\n        startBtn.style.display = status.running ? 'none' : 'inline-block';\n    }\n\n    if (stopBtn) {\n        stopBtn.style.display = status.running ? 'inline-block' : 'none';\n    }\n\n    if (restartBtn) {\n        restartBtn.style.display = status.running ? 'inline-block' : 'none';\n    }\n}\n\n// 处理机器人启动\nfunction handleBotStart() {\n    const btn = this;\n    btn.disabled = true;\n\n    fetch('/bot/api/start', {method: 'POST'})\n        .then(response => response.json())\n        .then(result => {\n            btn.disabled = false;\n            if (result.success) {\n                showNotification(result.message, 'success');\n                refreshAllData();\n            } else {\n                showNotification(result.message, 'danger');\n            }\n        })\n        .catch(error => {\n            btn.disabled = false;\n            showNotification('启动机器人失败', 'danger');\n        });\n}\n\n// 处理机器人停止\nfunction handleBotStop() {\n    const btn = this;\n    btn.disabled = true;\n\n    fetch('/bot/api/stop', {method: 'POST'})\n        .then(response => response.json())\n        .then(result => {\n            btn.disabled = false;\n            if (result.success) {\n                showNotification(result.message, 'warning');\n                refreshAllData();\n            } else {\n                showNotification(result.message, 'danger');\n            }\n        })\n        .catch(error => {\n            btn.disabled = false;\n            showNotification('停止机器人失败', 'danger');\n        });\n}\n\n// 处理机器人重启\nfunction handleBotRestart() {\n    const btn = this;\n    btn.disabled = true;\n\n    fetch('/bot/api/restart', {method: 'POST'})\n        .then(response => response.json())\n        .then(result => {\n            btn.disabled = false;\n            if (result.success) {\n                showNotification(result.message, 'success');\n                refreshAllData();\n            } else {\n                showNotification(result.message, 'danger');\n            }\n        })\n        .catch(error => {\n            btn.disabled = false;\n            showNotification('重启机器人失败', 'danger');\n        });\n}\n\n// 刷新所有数据\nfunction refreshAllData() {\n    refreshStatus();\n}\n\n// 启动自动刷新\nfunction startAutoRefresh() {\n    stopAutoRefresh();\n    autoRefreshTimer = setInterval(refreshAllData, 10000);\n}\n\n// 停止自动刷新\nfunction stopAutoRefresh() {\n    if (autoRefreshTimer) {\n        clearInterval(autoRefreshTimer);\n        autoRefreshTimer = null;\n    }\n}\n\n// 显示通知\nfunction showNotification(message, type = 'info') {\n    // 使用通知管理器\n    if (window.NotificationManager) {\n        NotificationManager.show(message, type);\n        return;\n    }\n\n    // 以下是备用实现，当通知管理器不可用时使用\n    let container = document.getElementById('notificationContainer');\n\n    // 确保容器唯一性\n    if (!container) {\n        container = document.createElement('div');\n        container.id = 'notificationContainer';\n        document.body.appendChild(container);\n    }\n\n    // 类型映射 - 确保使用正确的CSS类\n    let cssType = 'info';\n    switch (type) {\n        case 'success':\n            cssType = 'success';\n            break;\n        case 'error':\n        case 'danger':\n            cssType = 'error';\n            break;\n        case 'warning':\n            cssType = 'warning';\n            break;\n        default:\n            cssType = 'info';\n    }\n\n    // 创建纯净通知元素\n    const notification = document.createElement('div');\n    notification.className = `pure-notification ${cssType}-notification`;\n\n    // 使用文本节点避免HTML解析\n    const textNode = document.createTextNode(message);\n    notification.appendChild(textNode);\n\n    container.appendChild(notification);\n\n    // 强制布局刷新\n    notification.offsetHeight;\n\n    // 显示动画 - 添加show类并设置内联样式\n    notification.classList.add('show');\n    notification.style.opacity = '1';\n    notification.style.transform = 'translateY(0)';\n\n    // 自动移除\n    setTimeout(() => {\n        notification.classList.remove('show');\n        notification.style.opacity = '0';\n        notification.style.transform = 'translateY(-20px)';\n        setTimeout(() => {\n            if (notification.parentNode) {\n                container.removeChild(notification);\n            }\n        }, 300);\n    }, 3000);\n}"
  },
  {
    "path": "WebUI/static/js/pages/plugin.js",
    "content": "let currentPluginId = null;\nlet jsonEditor = null;\nlet botActive = false;\nlet allPlugins = []; // 存储所有插件数据用于搜索\n\n// 页面加载完成后执行\n$(document).ready(function () {\n    // 初始化检查\n    initializePluginManager();\n\n    // 绑定事件\n    bindEvents();\n});\n\n// 初始化插件管理器\nfunction initializePluginManager() {\n    checkBotStatus()\n        .done((res) => {\n            botActive = res?.running || false;\n            if (!botActive) {\n                $('.plugin-action-btn, .reload-btn')\n                    .tooltip('dispose')\n                    .attr('title', '请先启动机器人')\n                    .tooltip({trigger: 'hover'});\n            }\n        })\n        .fail(() => {\n            botActive = false;\n            showNotification('机器人状态检查失败，部分功能受限', 'warning');\n        })\n        .always(loadPlugins);\n}\n\n// 绑定事件\nfunction bindEvents() {\n    // 刷新按钮点击事件\n    $('#refreshPlugins').click(loadPlugins);\n\n    // 插件列表容器事件委托\n    $('#pluginListContainer')\n        .on('click', '.plugin-action-btn', handlePluginActionClick)\n        .on('click', '.reload-btn:not(:disabled)', handlePluginReloadClick)\n        .on('click', '.config-btn', handleConfigButtonClick);\n\n    // 模态框按钮事件\n    $('#enableDisablePlugin').click(handleEnableDisableClick);\n    $('#reloadPlugin').click(handleReloadClick);\n    $('#savePluginConfig').click(handleSaveConfigClick);\n\n    // 搜索框输入事件\n    $('#pluginSearch').on('input', handlePluginSearch);\n}\n\n// 检查机器人状态\nfunction checkBotStatus() {\n    return $.ajax({\n        url: '/bot/api/status',\n        type: 'GET'\n    });\n}\n\n// 加载插件列表\nfunction loadPlugins() {\n    $('#pluginListContainer').html('<div class=\"text-center p-4\"><i class=\"fas fa-spinner fa-spin me-2\"></i>正在加载插件列表...</div>');\n\n    return $.ajax({\n        url: '/plugin/api/list',\n        type: 'GET',\n        success: function (response) {\n            if (response.code === 0) {\n                allPlugins = response.data || [];\n                renderPluginList(allPlugins);\n            } else {\n                showLoadError(response.msg);\n            }\n        },\n        error: function (xhr, status, error) {\n            showLoadError(error);\n        }\n    });\n}\n\n// 显示加载错误\nfunction showLoadError(error) {\n    $('#pluginListContainer').html(`\n        <div class=\"alert alert-danger m-4\">\n            <i class=\"fas fa-exclamation-triangle me-2\"></i>\n            加载失败: ${error}\n            <button class=\"btn btn-outline-danger btn-sm float-end\" onclick=\"loadPlugins()\">\n                <i class=\"fas fa-redo\"></i> 重试\n            </button>\n        </div>\n    `);\n}\n\n// 渲染插件列表\nfunction renderPluginList(plugins) {\n    const container = $('#pluginListContainer');\n    container.empty();\n\n    if (!plugins || plugins.length === 0) {\n        container.html('<div class=\"text-center p-4 text-gray-500\">没有可用的插件</div>');\n        return;\n    }\n\n    plugins.forEach(plugin => {\n        plugin.id = plugin.id || plugin.name;\n        const isEnabled = plugin.enabled;\n\n        const card = $(`\n            <div class=\"card plugin-card shadow-sm\">\n                <div class=\"card-body d-flex justify-content-between align-items-center p-4\">\n                    <div class=\"flex-grow-1\">\n                        <div class=\"d-flex align-items-center gap-3 mb-2\">\n                            <h5 class=\"plugin-title mb-0\">${plugin.name}</h5>\n                            <span class=\"badge bg-blue-100 text-blue-800 text-sm px-2 py-1 rounded-full\">v${plugin.version || '未知'}</span>\n                        </div>\n                        \n                        <div class=\"plugin-meta\">\n                            <i class=\"fas fa-folder-open mr-1\"></i>${plugin.directory || '未知'}\n                            <span class=\"mx-2 text-gray-300\">|</span>\n                            <i class=\"fas fa-user mr-1\"></i>${plugin.author || '未知'} \n                            <span class=\"mx-2 text-gray-300\">|</span>\n                            <i class=\"fas fa-circle text-xs ${isEnabled ? 'text-emerald-500' : 'text-gray-400'}\"></i>\n                            ${isEnabled ? '已加载' : '已卸载'}\n                        </div>\n                        \n                        <p class=\"plugin-description mb-0\">${plugin.description || '无描述信息'}</p>\n                    </div>\n\n                    <div class=\"d-flex align-items-center gap-3 me-3\">\n                        <button class=\"btn btn-sm ${isEnabled ? 'btn-danger' : 'btn-success'} plugin-action-btn\"\n                                data-id=\"${plugin.id}\"\n                                data-action=\"${isEnabled ? 'unload' : 'load'}\"\n                                ${!botActive ? 'disabled title=\"机器人未启动\"' : ''}>\n                            <i class=\"fas ${isEnabled ? 'fa-power-off' : 'fa-plug'} me-1\"></i>\n                            ${isEnabled ? '卸载' : '加载'}\n                        </button>\n\n                        <button class=\"btn btn-sm btn-outline-warning reload-btn\"\n                                data-id=\"${plugin.id}\"\n                                ${!botActive || !isEnabled ? 'disabled' : ''}\n                                title=\"${!botActive ? '机器人未启动' : '重新加载插件'}\">\n                            <i class=\"fas fa-redo\"></i>\n                        </button>\n\n                        <button class=\"btn btn-sm btn-outline-primary config-btn\"\n                                data-id=\"${plugin.id}\"\n                                data-directory=\"${plugin.directory}\" \n                                title=\"打开文件目录\">\n                            <i class=\"fas fa-folder-open\"></i>\n                        </button>\n                    </div>\n                </div>\n            </div>\n        `);\n\n        container.append(card);\n    });\n}\n\n// 处理插件操作按钮点击\nfunction handlePluginActionClick() {\n    const pluginId = $(this).data('id');\n    const action = $(this).data('action');\n    handlePluginAction(pluginId, action);\n}\n\n// 处理插件重载按钮点击\nfunction handlePluginReloadClick() {\n    const pluginId = $(this).data('id');\n    reloadPlugin(pluginId);\n}\n\n// 处理配置按钮点击\nfunction handleConfigButtonClick() {\n    const directory = $(this).data('directory');\n    window.location.href = `/explorer/?path=${encodeURIComponent(directory)}`;\n}\n\n// 处理启用/禁用按钮点击\nfunction handleEnableDisableClick() {\n    if (!currentPluginId) return;\n\n    const action = $(this).text();\n    try {\n        if (action === '加载') {\n            enablePlugin(currentPluginId);\n        } else {\n            disablePlugin(currentPluginId);\n        }\n        $('#pluginDetailModal').modal('hide');\n    } catch (e) {\n        showNotification('处理失败: ' + e.message, 'error');\n    }\n}\n\n// 处理重载按钮点击\nfunction handleReloadClick() {\n    if (!currentPluginId) return;\n    reloadPlugin(currentPluginId);\n}\n\n// 处理保存配置按钮点击\nfunction handleSaveConfigClick() {\n    if (!currentPluginId || !jsonEditor) return;\n\n    try {\n        const config = jsonEditor.get();\n        savePluginConfig(currentPluginId, config);\n    } catch (e) {\n        showNotification('配置格式错误: ' + e.message, 'error');\n    }\n}\n\n// 处理插件搜索\nfunction handlePluginSearch() {\n    const keyword = $(this).val().toLowerCase();\n    const filtered = allPlugins.filter(plugin => {\n        return (\n            plugin.name.toLowerCase().includes(keyword) ||\n            (plugin.author && plugin.author.toLowerCase().includes(keyword)) ||\n            (plugin.description && plugin.description.toLowerCase().includes(keyword))\n        );\n    });\n    renderPluginList(filtered);\n}\n\n// 处理插件操作\nfunction handlePluginAction(pluginId, action) {\n    const btn = $(`.plugin-action-btn[data-id=\"${pluginId}\"]`);\n    const apiUrl = `/plugin/api/${action === 'load' ? 'enable' : 'disable'}/${pluginId}`;\n\n    btn.prop('disabled', true).html('<i class=\"fas fa-spinner fa-spin me-1\"></i>处理中...');\n\n    $.ajax({\n        url: apiUrl,\n        type: 'POST',\n        success: (res) => {\n            if (res.code === 0) {\n                showNotification(`插件${action === 'load' ? '加载' : '卸载'}成功`, 'success');\n                loadPlugins();\n            } else {\n                showNotification(res.msg, 'error');\n            }\n        },\n        error: (xhr) => {\n            showNotification(`操作失败: ${xhr.statusText}`, 'error');\n        },\n        complete: () => btn.prop('disabled', false)\n    });\n}\n\n// 启用插件\nfunction enablePlugin(pluginId) {\n    return $.ajax({\n        url: `/plugin/api/enable/${pluginId}`,\n        type: 'POST'\n    });\n}\n\n// 禁用插件\nfunction disablePlugin(pluginId) {\n    return $.ajax({\n        url: `/plugin/api/disable/${pluginId}`,\n        type: 'POST'\n    });\n}\n\n// 重新加载插件\nfunction reloadPlugin(pluginId) {\n    return $.ajax({\n        url: `/plugin/api/reload/${pluginId}`,\n        type: 'POST'\n    });\n}\n\n// 保存插件配置\nfunction savePluginConfig(pluginId, config) {\n    return $.ajax({\n        url: `/plugin/api/config/${pluginId}`,\n        type: 'POST',\n        contentType: 'application/json',\n        data: JSON.stringify(config)\n    });\n}\n\n// 显示通知\nfunction showNotification(message, type = 'info') {\n    // 使用通知管理器\n    if (window.NotificationManager) {\n        NotificationManager.show(message, type);\n        return;\n    }\n\n    // 以下是备用实现，当通知管理器不可用时使用\n    let container = document.getElementById('notificationContainer');\n\n    // 确保容器唯一性\n    if (!container) {\n        container = document.createElement('div');\n        container.id = 'notificationContainer';\n        document.body.appendChild(container);\n    }\n\n    // 类型映射 - 确保使用正确的CSS类\n    let cssType = 'info';\n    switch (type) {\n        case 'success':\n            cssType = 'success';\n            break;\n        case 'error':\n        case 'danger':\n            cssType = 'error';\n            break;\n        case 'warning':\n            cssType = 'warning';\n            break;\n        default:\n            cssType = 'info';\n    }\n\n    // 创建纯净通知元素\n    const notification = document.createElement('div');\n    notification.className = `pure-notification ${cssType}-notification`;\n\n    // 使用文本节点避免HTML解析\n    const textNode = document.createTextNode(message);\n    notification.appendChild(textNode);\n\n    container.appendChild(notification);\n\n    // 强制布局刷新\n    notification.offsetHeight;\n\n    // 显示动画 - 添加show类并设置内联样式\n    notification.classList.add('show');\n    notification.style.opacity = '1';\n    notification.style.transform = 'translateY(0)';\n\n    // 自动移除\n    setTimeout(() => {\n        notification.classList.remove('show');\n        notification.style.opacity = '0';\n        notification.style.transform = 'translateY(-20px)';\n        setTimeout(() => {\n            if (notification.parentNode) {\n                container.removeChild(notification);\n            }\n        }, 300);\n    }, 3000);\n} "
  },
  {
    "path": "WebUI/static/js/pages/tools.js",
    "content": "$(document).ready(function () {\n    // 加载工具列表\n    loadTools();\n});\n\n// 加载工具列表\nfunction loadTools() {\n    $.ajax({\n        url: '/tools/api/list',\n        type: 'GET',\n        dataType: 'json',\n        success: function (response) {\n            if (response.code === 0) {\n                renderTools(response.data);\n            } else {\n                showNotification('加载工具列表失败: ' + response.msg, 'danger');\n                $('#toolsContainer').html('<div class=\"col-12\"><div class=\"alert alert-danger\">加载工具列表失败</div></div>');\n            }\n        },\n        error: function (xhr, status, error) {\n            showNotification('加载工具列表失败: ' + error, 'danger');\n            $('#toolsContainer').html('<div class=\"col-12\"><div class=\"alert alert-danger\">加载工具列表失败</div></div>');\n        }\n    });\n}\n\n// 渲染工具列表\nfunction renderTools(tools) {\n    const toolsContainer = $('#toolsContainer');\n\n    if (!tools || tools.length === 0) {\n        toolsContainer.html('<div class=\"col-12\"><div class=\"alert alert-warning\">暂无可用工具</div></div>');\n        return;\n    }\n\n    let html = '';\n\n    tools.forEach(function (tool) {\n        html += `\n            <div class=\"col-md-4 mb-4\">\n                <div class=\"tool-card\">\n                    <div class=\"tool-header\">\n                        <div class=\"tool-icon\">\n                            <i class=\"fas fa-${tool.icon}\"></i>\n                        </div>\n                        <h5 class=\"tool-title\">${tool.title}</h5>\n                    </div>\n                    <div class=\"tool-body\">\n                        <p class=\"tool-description\">${tool.description}</p>\n                        <div class=\"execution-status\" id=\"status-${tool.id}\">\n                            <span class=\"status-text\"></span>\n                        </div>\n                    </div>\n                    <div class=\"tool-footer\">\n                        <button type=\"button\" class=\"btn btn-primary execute-tool\" data-tool-id=\"${tool.id}\">\n                            <i class=\"fas fa-play mr-1\"></i>执行\n                        </button>\n                    </div>\n                </div>\n            </div>\n        `;\n    });\n\n    toolsContainer.html(html);\n\n    // 绑定执行按钮事件\n    $('.execute-tool').click(function () {\n        const toolId = $(this).data('tool-id');\n        const toolTitle = $(this).closest('.tool-card').find('.tool-title').text();\n\n        // 显示确认对话框\n        showConfirmModal('确认执行', `确定要执行\"${toolTitle}\"吗？`, function () {\n            executeTool(toolId);\n        });\n    });\n}\n\n// 执行工具\nfunction executeTool(toolId) {\n    // 显示加载状态\n    const statusElement = $(`#status-${toolId}`);\n    statusElement.removeClass('success error').addClass('loading');\n    statusElement.find('.status-text').text('正在执行中...');\n    statusElement.show();\n\n    $.ajax({\n        url: `/tools/api/execute/${toolId}`,\n        type: 'POST',\n        contentType: 'application/json',\n        data: JSON.stringify({}),\n        dataType: 'json',\n        success: function (response) {\n            if (response.code === 0) {\n                // 更新状态为成功\n                statusElement.removeClass('loading error').addClass('success');\n                statusElement.find('.status-text').text(response.data.message || '执行成功');\n\n                // 显示执行详情\n                showExecutionDetail(true, response.data);\n            } else {\n                // 更新状态为失败\n                statusElement.removeClass('loading success').addClass('error');\n                statusElement.find('.status-text').text(response.msg);\n\n                // 显示执行详情\n                showExecutionDetail(false, response.data);\n            }\n        },\n        error: function (xhr, status, error) {\n            // 更新状态为失败\n            statusElement.removeClass('loading success').addClass('error');\n            statusElement.find('.status-text').text('执行失败: ' + error);\n\n            // 显示执行详情\n            showExecutionDetail(false, {error: error});\n        }\n    });\n}\n\n// 显示执行详情\nfunction showExecutionDetail(success, data) {\n    const detailStatus = $('#detailExecutionStatus');\n    const detailStatusText = $('#detailStatusText');\n    const executionLog = $('#executionLog');\n\n    if (success) {\n        detailStatus.removeClass('error loading').addClass('success');\n        detailStatusText.text(data.message || '执行成功');\n    } else {\n        detailStatus.removeClass('success loading').addClass('error');\n        detailStatusText.text(data.error || '执行失败');\n    }\n\n    // 显示执行日志\n    let logText = '';\n\n    if (data.stack) {\n        logText += data.stack;\n    } else if (typeof data === 'object') {\n        logText = JSON.stringify(data, null, 2);\n    } else {\n        logText = String(data);\n    }\n\n    executionLog.text(logText);\n\n    // 显示模态框\n    $('#executeDetailModal').modal('show');\n}\n\n// 显示确认对话框\nfunction showConfirmModal(title, message, callback) {\n    $('#confirmModalLabel').text(title);\n    $('#confirmModalBody').text(message);\n\n    $('#confirmModalConfirm').off('click').on('click', function () {\n        $('#confirmModal').modal('hide');\n        callback();\n    });\n\n    $('#confirmModal').modal('show');\n} "
  },
  {
    "path": "WebUI/templates/about/index.html",
    "content": "{% extends \"base.html\" %}\n\n{% block title %}关于 - {{ app_name }}{% endblock %}\n\n{% block styles %}\n{{ super() }}\n<link href=\"{{ url_for('static', filename='css/pages/about.css') }}\" rel=\"stylesheet\">\n{% endblock %}\n\n{% block content %}\n<div class=\"container py-5\">\n    <div class=\"row justify-content-center\">\n        <div class=\"col-md-8\">\n            <div class=\"card shadow-sm\">\n                <div class=\"card-body text-center py-5\">\n                    <h1 class=\"display-4 mb-4\">XYBot V2</h1>\n                    <p class=\"lead mb-4\">🤖 功能丰富的微信机器人框架</p>\n\n                    <div class=\"version-info mb-4\">\n                        <p class=\"mb-2\">\n                            当前版本：<span>V1.0.0</span>\n                        </p>\n                        <p class=\"mb-2\">\n                            作者：<a href=\"https://github.com/HenryXiaoYang\" target=\"_blank\">HenryXiaoYang</a>\n                        </p>\n                    </div>\n\n                    <a class=\"btn btn-primary\"\n                       href=\"https://github.com/HenryXiaoYang/XYBotV2\"\n                       target=\"_blank\">\n                        <i class=\"fab fa-github me-2\"></i> 访问 GitHub\n                    </a>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n{% endblock %} "
  },
  {
    "path": "WebUI/templates/auth/login.html",
    "content": "{% extends \"base.html\" %}\n\n{% block title %}登录 - {{ app_name }}{% endblock %}\n\n{% block styles %}\n<link href=\"{{ url_for('static', filename='css/pages/auth.css') }}\" rel=\"stylesheet\">\n{% endblock %}\n\n{% block content %}\n<div class=\"login-container\">\n    <div class=\"login-header\">\n        <h1>{{ app_name }}</h1>\n        <p class=\"text-muted\">微信机器人管理面板</p>\n    </div>\n    <div class=\"card login-card\">\n        <div class=\"card-body p-4\">\n            <h3 class=\"card-title text-center mb-4\">管理员登录</h3>\n\n            <form class=\"needs-validation\" method=\"POST\" novalidate>\n                {{ form.hidden_tag() }}\n                <div class=\"mb-3\">\n                    {{ form.username.label(class=\"form-label\") }}\n                    {{ form.username(class=\"form-control\" + (\" is-invalid\" if form.username.errors else \"\"),\n                    placeholder=\"请输入用户名\") }}\n                    {% if form.username.errors %}\n                    <div class=\"invalid-feedback\">\n                        {% for error in form.username.errors %}\n                        {{ error }}\n                        {% endfor %}\n                    </div>\n                    {% endif %}\n                </div>\n                <div class=\"mb-3\">\n                    {{ form.password.label(class=\"form-label\") }}\n                    {{ form.password(class=\"form-control\" + (\" is-invalid\" if form.password.errors else \"\"),\n                    placeholder=\"请输入密码\") }}\n                    {% if form.password.errors %}\n                    <div class=\"invalid-feedback\">\n                        {% for error in form.password.errors %}\n                        {{ error }}\n                        {% endfor %}\n                    </div>\n                    {% endif %}\n                </div>\n                <div class=\"mb-3 form-check\">\n                    {{ form.remember_me(class=\"form-check-input\") }}\n                    {{ form.remember_me.label(class=\"form-check-label\") }}\n                </div>\n                <div class=\"d-grid\">\n                    {{ form.submit(class=\"btn btn-primary w-100\") }}\n                </div>\n            </form>\n        </div>\n    </div>\n    <div class=\"login-footer\">\n        <p class=\"text-muted\">&copy; {{ now.year }} XYBotV2. 保留所有权利。</p>\n    </div>\n</div>\n{% endblock %}\n\n{% block scripts %}\n<script src=\"{{ url_for('static', filename='js/pages/auth.js') }}\"></script>\n{% endblock %} "
  },
  {
    "path": "WebUI/templates/base.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta content=\"width=device-width, initial-scale=1.0\" name=\"viewport\">\n    <title>{% block title %}{{ app_name }}{% endblock %}</title>\n    <!-- 第三方CSS -->\n    <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n    <link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css\" rel=\"stylesheet\">\n\n    <!-- 全局样式 - 包含所有通用样式：布局、卡片、按钮、表格、通知、日志查看器等 -->\n    <link href=\"{{ url_for('static', filename='css/style.css') }}\" rel=\"stylesheet\">\n\n    <!-- 组件样式 - 特定的UI组件样式 -->\n    <link href=\"{{ url_for('static', filename='css/components/cards.css') }}\" rel=\"stylesheet\">\n    <link href=\"{{ url_for('static', filename='css/components/file_browser.css') }}\" rel=\"stylesheet\">\n    <link href=\"{{ url_for('static', filename='css/components/file_viewer.css') }}\" rel=\"stylesheet\">\n    <link href=\"{{ url_for('static', filename='css/components/loading.css') }}\" rel=\"stylesheet\">\n    <link href=\"{{ url_for('static', filename='css/components/modals.css') }}\" rel=\"stylesheet\">\n\n    <!-- 页面特定样式 - 由各个页面根据需要引入 -->\n    {% block styles %}{% endblock %}\n</head>\n<body>\n<!-- 通知容器 - 用于显示所有系统通知 -->\n<div id=\"notificationContainer\"></div>\n\n<!-- 侧边栏 -->\n<div class=\"sidebar\">\n    <!-- 侧边栏头部 -->\n    <div class=\"sidebar-header\">\n        <a class=\"navbar-brand\" href=\"/\">{{ app_name }}</a>\n    </div>\n\n    <!-- 侧边栏内容区 -->\n    <div class=\"sidebar-content\">\n        <div class=\"sidebar-menu\">\n            <ul class=\"nav flex-column\">\n                <li class=\"nav-item\">\n                    <a class=\"nav-link {% if request.path == '/' %}active{% endif %}\" href=\"/\">\n                        <i class=\"fas fa-home\"></i> 首页\n                    </a>\n                </li>\n                <li class=\"nav-item\">\n                    <a class=\"nav-link {% if '/plugin' in request.path %}active{% endif %}\" href=\"/plugin\">\n                        <i class=\"fas fa-puzzle-piece\"></i> 插件管理\n                    </a>\n                </li>\n                <li class=\"nav-item\">\n                    <a class=\"nav-link {% if '/logs' in request.path %}active{% endif %}\" href=\"/logs\">\n                        <i class=\"fas fa-file-alt\"></i> 日志管理\n                    </a>\n                </li>\n                <li class=\"nav-item\">\n                    <a class=\"nav-link {% if '/config' in request.path %}active{% endif %}\" href=\"/config\">\n                        <i class=\"fas fa-cogs\"></i> 配置管理\n                    </a>\n                </li>\n                <li class=\"nav-item\">\n                    <a class=\"nav-link {% if '/tools' in request.path %}active{% endif %}\" href=\"/tools\">\n                        <i class=\"fas fa-tools\"></i> 工具箱\n                    </a>\n                </li>\n                <li class=\"nav-item\">\n                    <a class=\"nav-link {% if '/explorer' in request.path %}active{% endif %}\" href=\"/explorer\">\n                        <i class=\"fas fa-folder-open\"></i> 文件浏览器\n                    </a>\n                </li>\n                <li class=\"nav-item\">\n                    <a class=\"nav-link {% if '/about' in request.path %}active{% endif %}\" href=\"/about\">\n                        <i class=\"fas fa-info-circle\"></i> 关于\n                    </a>\n                </li>\n            </ul>\n        </div>\n        <div class=\"sidebar-footer\">\n            <a class=\"nav-link\" href=\"/auth/logout\" id=\"logoutBtn\">\n                <i class=\"fas fa-sign-out-alt\"></i> 退出登录\n            </a>\n        </div>\n    </div>\n</div>\n\n<!-- 主内容区 -->\n<main class=\"content\" role=\"main\">\n    {% block content %}{% endblock %}\n</main>\n\n<!-- 页脚 -->\n<footer class=\"footer\">\n    <div class=\"container text-center\">\n        <span class=\"text-muted\">2025 By HenryXiaoYang https://github.com/HenryXiaoYang/XYBotV2</span>\n    </div>\n</footer>\n\n<!-- 处理Flash消息 -->\n{% with messages = get_flashed_messages(with_categories=true) %}\n{% if messages %}\n<script>\n    // 页面加载完成后显示通知\n    document.addEventListener('DOMContentLoaded', function() {\n        {% for category, message in messages %}\n        if (window.NotificationManager) {\n            NotificationManager.show({{ message|tojson }}, {{ category|tojson }});\n        } else {\n            showNotification({{ message|tojson }}, {{ category|tojson }});\n        }\n        {% endfor %}\n    });\n</script>\n{% endif %}\n{% endwith %}\n\n<!-- 第三方JS -->\n<script src=\"https://code.jquery.com/jquery-3.5.1.min.js\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.min.js\"></script>\n\n<!-- 通知模块 - 统一管理系统通知 -->\n<script src=\"{{ url_for('static', filename='js/components/notification.js') }}\"></script>\n\n<!-- 全局脚本 -->\n<script src=\"{{ url_for('static', filename='js/main.js') }}\"></script>\n<!-- 组件脚本 -->\n<script src=\"{{ url_for('static', filename='js/components/cards.js') }}\"></script>\n<script src=\"{{ url_for('static', filename='js/components/file_browser.js') }}\"></script>\n<script src=\"{{ url_for('static', filename='js/components/file_viewer.js') }}\"></script>\n<script src=\"{{ url_for('static', filename='js/components/loading.js') }}\"></script>\n<script src=\"{{ url_for('static', filename='js/components/modals.js') }}\"></script>\n\n{% block scripts %}{% endblock %}\n</body>\n</html> "
  },
  {
    "path": "WebUI/templates/components/cards.html",
    "content": "{% macro status_card(title, value, icon=\"fa-tachometer-alt\", color=\"primary\", size=\"md\", metric_type=\"\") %}\n<div class=\"card status-card mb-3\">\n    <div class=\"card-body\">\n        <div class=\"d-flex justify-content-between align-items-center\">\n            <div>\n                <h6 class=\"card-subtitle mb-2 text-muted\">{{ title }}</h6>\n                <h2 {% if metric_type %}data-metric=\"{{ metric_type }}\" {% endif %}\n                    class=\"card-title mb-0 text-{{ color }} status-card-value\">{{ value }}</h2>\n            </div>\n            <div class=\"status-icon rounded-circle p-3\"\n                 style=\"border: 2px solid var(--bs-{{ color }}); background-color: transparent;\">\n                <i class=\"fas {{ icon }} text-{{ color }} fa-2x\"></i>\n            </div>\n        </div>\n    </div>\n</div>\n{% endmacro %}\n\n{% macro control_card(title, status=\"stopped\", start_url=\"#\", stop_url=\"#\", id=\"botControl\") %}\n<div class=\"card control-card mb-3\">\n    <div class=\"card-header\">\n        <h5 class=\"card-title mb-0\">{{ title }}</h5>\n    </div>\n    <div class=\"card-body\">\n        <div class=\"d-flex justify-content-between align-items-center\">\n            <div>\n                <p class=\"mb-0\">当前状态:\n                    {% if status == \"running\" %}\n                    <span class=\"badge bg-success\">运行中</span>\n                    {% else %}\n                    <span class=\"badge bg-danger\">已停止</span>\n                    {% endif %}\n                </p>\n            </div>\n            <div>\n                <button\n                        class=\"btn btn-success me-2 {% if status == 'running' %}d-none{% endif %}\"\n                        data-action-url=\"{{ start_url }}\"\n                        id=\"{{ id }}-start\">\n                    <i class=\"fas fa-play me-1\"></i> 启动\n                </button>\n                <button\n                        class=\"btn btn-danger {% if status == 'stopped' %}d-none{% endif %}\"\n                        data-action-url=\"{{ stop_url }}\"\n                        id=\"{{ id }}-stop\">\n                    <i class=\"fas fa-stop me-1\"></i> 停止\n                </button>\n            </div>\n        </div>\n    </div>\n</div>\n{% endmacro %}\n\n{% macro log_card(title, id=\"logViewer\", height=\"400px\") %}\n<div class=\"card log-card mb-3\">\n    <div class=\"card-header d-flex justify-content-between align-items-center\">\n        <h5 class=\"card-title mb-0\">{{ title }}</h5>\n        <div>\n            <button class=\"btn btn-outline-secondary btn-sm\" id=\"{{ id }}-refresh\">\n                <i class=\"fas fa-sync-alt\"></i> 刷新\n            </button>\n            <button class=\"btn btn-outline-secondary btn-sm\" id=\"{{ id }}-clear\">\n                <i class=\"fas fa-eraser\"></i> 清空\n            </button>\n        </div>\n    </div>\n    <div class=\"card-body p-0\">\n        <div class=\"log-viewer\" id=\"{{ id }}\" style=\"height: {{ height }}\">\n            <div class=\"text-muted p-3\">正在加载日志...</div>\n        </div>\n    </div>\n</div>\n{% endmacro %} "
  },
  {
    "path": "WebUI/templates/components/file_browser.html",
    "content": "{% macro file_browser(container_id='file-browser', initial_path='') %}\n<link href=\"{{ url_for('static', filename='css/components/file_browser.css') }}\" rel=\"stylesheet\">\n\n<div class=\"file-browser-container\" id=\"{{ container_id }}\">\n    <div class=\"file-browser-header\">\n        <div class=\"d-flex justify-content-end w-100\">\n            <div class=\"btn-group\">\n                <button class=\"btn btn-sm btn-outline-secondary\" id=\"{{ container_id }}-refresh\" type=\"button\">\n                    <i class=\"fas fa-sync-alt\"></i> 刷新\n                </button>\n                <button class=\"btn btn-sm btn-outline-secondary\" id=\"{{ container_id }}-view-toggle\" type=\"button\">\n                    <i class=\"fas fa-th-list\"></i> 切换视图\n                </button>\n            </div>\n        </div>\n        <div class=\"full-path-display\" id=\"{{ container_id }}-full-path\"></div>\n    </div>\n\n    <div class=\"card\">\n        <div class=\"card-body p-0\">\n            <div class=\"file-list\" id=\"{{ container_id }}-list\"></div>\n        </div>\n    </div>\n</div>\n\n<template id=\"file-list-template\">\n    <table class=\"table table-hover table-sm\">\n        <thead>\n        <tr>\n            <th width=\"50%\">名称</th>\n            <th width=\"20%\">大小</th>\n            <th width=\"30%\">修改时间</th>\n        </tr>\n        </thead>\n        <tbody>\n        <!-- 将通过JavaScript填充内容 -->\n        </tbody>\n    </table>\n</template>\n\n<template id=\"file-grid-template\">\n    <div class=\"file-grid\">\n        <!-- 将通过JavaScript填充内容 -->\n    </div>\n</template>\n\n<template id=\"file-item-template\">\n    <tr class=\"file-item\" data-path=\"{path}\" data-type=\"{type}\">\n        <td>\n            <i class=\"{icon}\"></i>\n            <span class=\"file-name\">{name}</span>\n        </td>\n        <td>{size}</td>\n        <td>{modified}</td>\n    </tr>\n</template>\n\n<template id=\"file-grid-item-template\">\n    <div class=\"file-grid-item\" data-path=\"{path}\" data-type=\"{type}\">\n        <div class=\"file-icon\">\n            <i class=\"{icon}\"></i>\n        </div>\n        <div class=\"file-grid-name\">{name}</div>\n    </div>\n</template>\n\n<script>\n    document.addEventListener('DOMContentLoaded', function () {\n        initFileBrowser('{{ container_id }}', '{{ initial_path }}');\n    });\n</script>\n{% endmacro %}"
  },
  {
    "path": "WebUI/templates/components/file_viewer.html",
    "content": "{% macro file_viewer(file_path, container_id='file-viewer') %}\n<link href=\"{{ url_for('static', filename='css/components/file_viewer.css') }}\" rel=\"stylesheet\">\n<link href=\"https://cdn.jsdelivr.net/npm/@fontsource/jetbrains-mono@4.5.0/index.css\" rel=\"stylesheet\">\n<link href=\"https://cdn.jsdelivr.net/npm/@fontsource/source-code-pro@4.5.0/index.css\" rel=\"stylesheet\">\n\n<div class=\"file-viewer-container\" id=\"{{ container_id }}\">\n    <div class=\"file-viewer-header\">\n        <div class=\"d-flex align-items-center justify-content-between mb-1\">\n            <h5 class=\"file-name\">{{ file_path.split('/')[-1] }}</h5>\n        </div>\n\n        <div class=\"card mb-2\">\n            <div class=\"card-body py-2\">\n                <div class=\"d-flex align-items-center flex-wrap small\">\n                    <div class=\"file-info-item\">\n                        <strong>路径:</strong> <span class=\"file-path\">{{ file_path }}</span>\n                    </div>\n                    <div class=\"file-info-item\">\n                        <strong>大小:</strong> <span class=\"file-size\" id=\"{{ container_id }}-size\">加载中...</span>\n                    </div>\n                    <div class=\"file-info-item\">\n                        <strong>修改时间:</strong> <span class=\"file-modified\"\n                                                         id=\"{{ container_id }}-modified\">加载中...</span>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <div class=\"file-content-container\">\n        <div class=\"card\">\n            <div class=\"card-header bg-light\">\n                <div class=\"d-flex align-items-center\">\n                    <div class=\"editor-controls\">\n                        <div class=\"control-item\">\n                            <div class=\"input-group input-group-sm\">\n                                <span class=\"input-group-text\">语言</span>\n                                <select class=\"form-select form-select-sm\" id=\"{{ container_id }}-editor-language\">\n                                    <option value=\"plaintext\">纯文本</option>\n                                    <option value=\"python\">Python</option>\n                                    <option value=\"javascript\">JavaScript</option>\n                                    <option value=\"html\">HTML</option>\n                                    <option value=\"css\">CSS</option>\n                                    <option value=\"json\">JSON</option>\n                                    <option value=\"markdown\">Markdown</option>\n                                    <option value=\"yaml\">YAML</option>\n                                    <option value=\"toml\">TOML</option>\n                                    <option value=\"shell\">Shell</option>\n                                </select>\n                            </div>\n                        </div>\n\n                        <div class=\"control-item\">\n                            <div class=\"input-group input-group-sm\">\n                                <span class=\"input-group-text\">字体大小</span>\n                                <select class=\"form-select form-select-sm\" id=\"{{ container_id }}-font-size\">\n                                    <option value=\"12\">12px</option>\n                                    <option selected value=\"14\">14px</option>\n                                    <option value=\"16\">16px</option>\n                                    <option value=\"18\">18px</option>\n                                    <option value=\"20\">20px</option>\n                                    <option value=\"24\">24px</option>\n                                </select>\n                            </div>\n                        </div>\n\n                        <div class=\"control-item\">\n                            <div class=\"input-group input-group-sm\">\n                                <span class=\"input-group-text\">自动换行</span>\n                                <div class=\"input-group-text\">\n                                    <input class=\"form-check-input mt-0\" id=\"{{ container_id }}-line-wrap\"\n                                           type=\"checkbox\">\n                                </div>\n                            </div>\n                        </div>\n                    </div>\n\n                    <div class=\"editor-action-buttons\">\n                        <button class=\"btn btn-sm btn-outline-secondary\" id=\"{{ container_id }}-reload\" type=\"button\">\n                            <i class=\"fas fa-sync-alt\"></i> 刷新\n                        </button>\n                        <button class=\"btn btn-sm btn-outline-secondary\" id=\"{{ container_id }}-save\" type=\"button\">\n                            <i class=\"fas fa-save\"></i> 保存\n                        </button>\n                    </div>\n                </div>\n            </div>\n            <div class=\"card-body p-0\">\n                <div class=\"monaco-editor-container\" id=\"{{ container_id }}-monaco-editor\">\n                    <div class=\"loading-container\">\n                        <i class=\"fas fa-file-alt\"></i>\n                        <p>正在加载文件内容...</p>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n\n<script src=\"https://cdn.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs/loader.js\"></script>\n<script src=\"{{ url_for('static', filename='js/components/file_viewer.js') }}\"></script>\n<script>\n    document.addEventListener('DOMContentLoaded', function () {\n        initFileViewer('{{ container_id }}', '{{ file_path }}');\n    });\n</script>\n{% endmacro %} "
  },
  {
    "path": "WebUI/templates/components/loading.html",
    "content": "{% macro spinner(size=\"md\", color=\"primary\", text=\"加载中...\") %}\n{% set sizes = {\"sm\": \"spinner-border-sm\", \"md\": \"\", \"lg\": \"spinner-border spinner-border-lg\"} %}\n<div class=\"d-flex justify-content-center align-items-center loading-container\">\n    <div class=\"spinner-border text-{{ color }} {{ sizes[size] }}\" role=\"status\">\n        <span class=\"visually-hidden\">{{ text }}</span>\n    </div>\n    {% if text %}\n    <span class=\"ms-2\">{{ text }}</span>\n    {% endif %}\n</div>\n{% endmacro %}\n\n{% macro progress(value=25, color=\"primary\", striped=True, animated=True, label=True) %}\n{% set progress_classes = \"progress-bar bg-\" ~ color %}\n{% if striped %}\n{% set progress_classes = progress_classes ~ \" progress-bar-striped\" %}\n{% endif %}\n{% if animated %}\n{% set progress_classes = progress_classes ~ \" progress-bar-animated\" %}\n{% endif %}\n<div class=\"progress\" style=\"height: 20px;\">\n    <div aria-valuemax=\"100\"\n         aria-valuemin=\"0\"\n         aria-valuenow=\"{{ value }}\"\n         class=\"{{ progress_classes }}\"\n         role=\"progressbar\"\n         style=\"width: {{ value }}%\">\n        {% if label %}{{ value }}%{% endif %}\n    </div>\n</div>\n{% endmacro %}\n\n{% macro full_page_loader(message=\"页面加载中...\") %}\n<div class=\"full-page-loader\" id=\"fullPageLoader\">\n    <div class=\"loader-content\">\n        <div class=\"spinner-grow text-primary\" role=\"status\">\n            <span class=\"visually-hidden\">加载中...</span>\n        </div>\n        {% if message %}\n        <p class=\"mt-3\">{{ message }}</p>\n        {% endif %}\n    </div>\n</div>\n{% endmacro %}\n\n{% macro content_loader(size=\"md\", container_class=\"\") %}\n<div class=\"content-loader {{ container_class }}\">\n    <div class=\"loader-skeleton\">\n        {% if size == \"sm\" %}\n        <div class=\"skeleton-line\" style=\"width: 30%; height: 15px;\"></div>\n        <div class=\"skeleton-line\" style=\"width: 80%; height: 15px;\"></div>\n        {% elif size == \"lg\" %}\n        <div class=\"skeleton-line\" style=\"width: 40%; height: 25px;\"></div>\n        <div class=\"skeleton-line\" style=\"width: 90%; height: 15px;\"></div>\n        <div class=\"skeleton-line\" style=\"width: 60%; height: 15px;\"></div>\n        <div class=\"skeleton-line\" style=\"width: 75%; height: 15px;\"></div>\n        {% else %}\n        <div class=\"skeleton-line\" style=\"width: 40%; height: 20px;\"></div>\n        <div class=\"skeleton-line\" style=\"width: 90%; height: 15px;\"></div>\n        <div class=\"skeleton-line\" style=\"width: 60%; height: 15px;\"></div>\n        {% endif %}\n    </div>\n</div>\n{% endmacro %} "
  },
  {
    "path": "WebUI/templates/components/modals.html",
    "content": "{% macro confirm_modal(id=\"confirmModal\", title=\"确认\", message=\"您确定要执行此操作吗？\", confirm_text=\"确认\", cancel_text=\"取消\", size=\"md\") %}\n<div aria-hidden=\"true\" aria-labelledby=\"{{ id }}Label\" class=\"modal fade\" id=\"{{ id }}\" tabindex=\"-1\">\n    <div class=\"modal-dialog modal-{{ size }}\">\n        <div class=\"modal-content\">\n            <div class=\"modal-header\">\n                <h5 class=\"modal-title\" id=\"{{ id }}Label\">{{ title }}</h5>\n                <button aria-label=\"关闭\" class=\"btn-close\" data-bs-dismiss=\"modal\" type=\"button\"></button>\n            </div>\n            <div class=\"modal-body\">\n                {{ message }}\n            </div>\n            <div class=\"modal-footer\">\n                <button class=\"btn btn-secondary\" data-bs-dismiss=\"modal\" type=\"button\">{{ cancel_text }}</button>\n                <button class=\"btn btn-primary\" id=\"{{ id }}-confirm\" type=\"button\">{{ confirm_text }}</button>\n            </div>\n        </div>\n    </div>\n</div>\n{% endmacro %}\n\n{% macro form_modal(id=\"formModal\", title=\"表单\", save_text=\"保存\", cancel_text=\"取消\", size=\"md\") %}\n<div aria-hidden=\"true\" aria-labelledby=\"{{ id }}Label\" class=\"modal fade\" id=\"{{ id }}\" tabindex=\"-1\">\n    <div class=\"modal-dialog modal-{{ size }}\">\n        <div class=\"modal-content\">\n            <div class=\"modal-header\">\n                <h5 class=\"modal-title\" id=\"{{ id }}Label\">{{ title }}</h5>\n                <button aria-label=\"关闭\" class=\"btn-close\" data-bs-dismiss=\"modal\" type=\"button\"></button>\n            </div>\n            <div class=\"modal-body\">\n                {% if caller %}{{ caller() }}{% endif %}\n            </div>\n            <div class=\"modal-footer\">\n                <button class=\"btn btn-secondary\" data-bs-dismiss=\"modal\" type=\"button\">{{ cancel_text }}</button>\n                <button class=\"btn btn-primary\" id=\"{{ id }}-save\" type=\"button\">{{ save_text }}</button>\n            </div>\n        </div>\n    </div>\n</div>\n{% endmacro %}\n\n{% macro info_modal(id=\"infoModal\", title=\"信息\", ok_text=\"确定\", size=\"md\") %}\n<div aria-hidden=\"true\" aria-labelledby=\"{{ id }}Label\" class=\"modal fade\" id=\"{{ id }}\" tabindex=\"-1\">\n    <div class=\"modal-dialog modal-{{ size }}\">\n        <div class=\"modal-content\">\n            <div class=\"modal-header\">\n                <h5 class=\"modal-title\" id=\"{{ id }}Label\">{{ title }}</h5>\n                <button aria-label=\"关闭\" class=\"btn-close\" data-bs-dismiss=\"modal\" type=\"button\"></button>\n            </div>\n            <div class=\"modal-body\">\n                {% if caller %}{{ caller() }}{% endif %}\n            </div>\n            <div class=\"modal-footer\">\n                <button class=\"btn btn-primary\" data-bs-dismiss=\"modal\" type=\"button\">{{ ok_text }}</button>\n            </div>\n        </div>\n    </div>\n</div>\n{% endmacro %}\n\n{% macro ajax_modal(id=\"ajaxModal\", title=\"加载内容\", size=\"lg\") %}\n<div aria-hidden=\"true\" aria-labelledby=\"{{ id }}Label\" class=\"modal fade\" id=\"{{ id }}\" tabindex=\"-1\">\n    <div class=\"modal-dialog modal-{{ size }}\">\n        <div class=\"modal-content\">\n            <div class=\"modal-header\">\n                <h5 class=\"modal-title\" id=\"{{ id }}Label\">{{ title }}</h5>\n                <button aria-label=\"关闭\" class=\"btn-close\" data-bs-dismiss=\"modal\" type=\"button\"></button>\n            </div>\n            <div class=\"modal-body\">\n                <div class=\"text-center p-5\">\n                    <div class=\"spinner-border text-primary\" role=\"status\">\n                        <span class=\"visually-hidden\">加载中...</span>\n                    </div>\n                    <p class=\"mt-3\">正在加载内容，请稍候...</p>\n                </div>\n                <div class=\"modal-content-container d-none\"></div>\n            </div>\n        </div>\n    </div>\n</div>\n{% endmacro %} "
  },
  {
    "path": "WebUI/templates/config/index.html",
    "content": "{% extends 'base.html' %}\n\n{% block title %}配置管理 - {{ app_name }}{% endblock %}\n\n{% block styles %}\n<link href=\"{{ url_for('static', filename='css/pages/config.css') }}\" rel=\"stylesheet\">\n{% endblock %}\n\n{% block content %}\n<div class=\"container-fluid\">\n    <div class=\"row\">\n        <div class=\"col-12\">\n            <div class=\"card\">\n                <div class=\"card-header\">\n                    <h1 class=\"h3 mb-0\">配置管理</h1>\n                    <div class=\"card-tools\">\n                        <!-- 已移除备份管理相关按钮 -->\n                    </div>\n                </div>\n                <div class=\"card-body\">\n                    <form id=\"configForm\">\n                        <div id=\"configSections\">\n                            <!-- 配置部分将通过JavaScript动态加载 -->\n                            <div class=\"text-center py-5\">\n                                <div class=\"spinner-border text-primary\" role=\"status\">\n                                    <span class=\"sr-only\">加载中...</span>\n                                </div>\n                                <p class=\"mt-2\">正在加载配置...</p>\n                            </div>\n                        </div>\n                    </form>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n\n<!-- 保存按钮 -->\n<button class=\"btn btn-success btn-lg save-btn-fixed\" id=\"saveConfig\" type=\"button\">\n    <i class=\"fas fa-save\"></i> 保存配置\n</button>\n\n<!-- 确认模态框 -->\n<div aria-hidden=\"true\" aria-labelledby=\"confirmModalLabel\" class=\"modal fade\" id=\"confirmModal\" tabindex=\"-1\">\n    <div class=\"modal-dialog\">\n        <div class=\"modal-content\">\n            <div class=\"modal-header\">\n                <h5 class=\"modal-title\" id=\"confirmModalLabel\">确认操作</h5>\n                <button aria-label=\"Close\" class=\"close\" data-dismiss=\"modal\" type=\"button\">\n                    <span aria-hidden=\"true\">&times;</span>\n                </button>\n            </div>\n            <div class=\"modal-body\" id=\"confirmModalBody\">\n                确定要执行此操作吗？\n            </div>\n            <div class=\"modal-footer\">\n                <button class=\"btn btn-secondary\" data-dismiss=\"modal\" type=\"button\">取消</button>\n                <button class=\"btn btn-primary\" id=\"confirmModalConfirm\" type=\"button\">确认</button>\n            </div>\n        </div>\n    </div>\n</div>\n{% endblock %}\n\n{% block scripts %}\n<script src=\"{{ url_for('static', filename='js/pages/config.js') }}\"></script>\n{% endblock %} "
  },
  {
    "path": "WebUI/templates/explorer/index.html",
    "content": "{% extends \"base.html\" %}\n{% from \"components/file_browser.html\" import file_browser %}\n\n{% block title %}{{ page_title }}{% endblock %}\n\n{% block styles %}\n<link href=\"{{ url_for('static', filename='css/pages/explorer.css') }}\" rel=\"stylesheet\">\n{% endblock %}\n\n{% block content %}\n<div class=\"container-fluid py-3\">\n    <div class=\"d-flex justify-content-between align-items-center mb-3\">\n        <h1 class=\"h3 mb-0\">文件浏览器</h1>\n        <div>\n            <a class=\"btn btn-sm btn-outline-secondary\" href=\"javascript:history.back()\">\n                <i class=\"fas fa-arrow-left\"></i> 返回\n            </a>\n        </div>\n    </div>\n\n    <div class=\"bg-light p-3 rounded-3 shadow-sm\">\n        {{ file_browser(container_id='file-explorer', initial_path=initial_path) }}\n    </div>\n</div>\n{% endblock %}\n\n{% block scripts %}\n<script src=\"{{ url_for('static', filename='js/pages/explorer.js') }}\"></script>\n{% endblock %}  "
  },
  {
    "path": "WebUI/templates/explorer/view.html",
    "content": "{% extends \"base.html\" %}\n{% from \"components/file_viewer.html\" import file_viewer %}\n\n{% block title %}文件查看 - {{ file_path }}{% endblock %}\n\n{% block styles %}\n{{ super() }}\n{% endblock %}\n\n{% block content %}\n<div class=\"container-fluid py-3\">\n    <div class=\"row\">\n        <div class=\"col-12\">\n            <div class=\"card mb-4\">\n                <div class=\"card-header d-flex justify-content-between align-items-center\">\n                    <div>\n                        <i class=\"fas fa-file-code me-1\"></i>\n                        文件查看器\n                    </div>\n                    <a class=\"btn btn-sm btn-outline-secondary\" href=\"javascript:history.back()\">\n                        <i class=\"fas fa-arrow-left\"></i> 返回\n                    </a>\n                </div>\n                <div class=\"card-body\">\n                    {{ file_viewer(file_path=file_path, container_id='file-viewer') }}\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n{% endblock %}\n\n{% block scripts %}\n<script src=\"{{ url_for('static', filename='js/pages/explorer.js') }}\"></script>\n{% endblock %} "
  },
  {
    "path": "WebUI/templates/logs/index.html",
    "content": "{% extends \"base.html\" %}\n{% from \"components/file_browser.html\" import file_browser %}\n\n{% block title %}{{ page_title }}{% endblock %}\n\n{% block styles %}\n<link href=\"{{ url_for('static', filename='css/pages/logs.css') }}\" rel=\"stylesheet\">\n{% endblock %}\n\n{% block content %}\n<div class=\"container-fluid py-3\">\n    <div class=\"d-flex justify-content-between align-items-center mb-3\">\n        <h1 class=\"h3 mb-0\">日志管理</h1>\n    </div>\n\n    <div class=\"bg-light p-3 rounded-3 shadow-sm\">\n        {{ file_browser(container_id='logs-browser', initial_path='logs') }}\n    </div>\n</div>\n{% endblock %}\n\n{% block scripts %}\n<script src=\"{{ url_for('static', filename='js/pages/logs.js') }}\"></script>\n{% endblock %} "
  },
  {
    "path": "WebUI/templates/overview/index.html",
    "content": "{% extends \"base.html\" %}\n{% from \"components/cards.html\" import status_card, control_card, log_card, metric_card %}\n{% from \"components/loading.html\" import spinner %}\n\n{% block title %}{{ page_title }} - {{ app_name }}{% endblock %}\n\n{% block styles %}\n{{ super() }}\n<link href=\"{{ url_for('static', filename='css/pages/overview.css') }}\" rel=\"stylesheet\">\n{% endblock %}\n\n{% block content %}\n<div class=\"container-fluid main-content\">\n    <!-- 页面标题 -->\n    <div class=\"d-flex justify-content-between align-items-center mb-4\">\n        <h1 class=\"h3 mb-0\">概览</h1>\n    </div>\n\n    <!-- 状态卡片 -->\n    <div class=\"row\" id=\"metricsRow\">\n        <div class=\"col-md-4\">\n            {{ status_card(\n            title=\"收到消息数\",\n            value=metrics.messages,\n            icon=\"fa-comment\",\n            color=\"primary\",\n            metric_type=\"messages\"\n            ) }}\n        </div>\n        <div class=\"col-md-4\">\n            {{ status_card(\n            title=\"运行时长\",\n            value=metrics.uptime,\n            icon=\"fa-clock\",\n            color=\"success\",\n            metric_type=\"uptime\"\n            ) }}\n        </div>\n        <div class=\"col-md-4\">\n            {{ status_card(\n            title=\"数据库用户数\",\n            value=metrics.users,\n            icon=\"fa-users\",\n            color=\"info\",\n            metric_type=\"users\"\n            ) }}\n        </div>\n    </div>\n\n    <div class=\"row mt-4\">\n        <!-- 账号信息卡片 -->\n        <div class=\"col-md-6\">\n            <div class=\"card mb-3 account-info-card\">\n                <div class=\"card-header\">\n                    <h5 class=\"card-title mb-0\">微信账号</h5>\n                </div>\n                <div class=\"card-body py-4\">\n                    <div class=\"row\">\n                        <div class=\"col-md-4 col-sm-5 d-flex justify-content-center align-items-center\">\n                            <div class=\"profile-picture-container\">\n                                <div class=\"profile-picture\" id=\"profilePicture\">\n                                    <i class=\"fas fa-user-circle\"></i>\n                                </div>\n                            </div>\n                        </div>\n                        <div class=\"col-md-8 col-sm-7 ps-md-4 pt-3 pt-sm-0\">\n                            <h5 class=\"mb-3\" id=\"botNickname\">加载中...</h5>\n                            <p class=\"mb-3\"><strong>wxid:</strong> <span id=\"botWxid\">加载中...</span></p>\n                            <p class=\"mb-0\"><strong>微信号:</strong> <span id=\"botAlias\">加载中...</span></p>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n\n        <!-- 状态信息卡片 -->\n        <div class=\"col-md-6\">\n            <div class=\"card mb-3 h-100\">\n                <div class=\"card-header\">\n                    <h5 class=\"card-title mb-0\">状态信息</h5>\n                </div>\n                <div class=\"card-body py-4\">\n                    <table class=\"table table-sm\">\n                        <tbody>\n                        <tr>\n                            <th scope=\"row\">运行状态</th>\n                            <td>\n                                {% if bot_status.running %}\n                                <span class=\"badge bg-success\">运行中</span>\n                                {% else %}\n                                <span class=\"badge bg-danger\">已停止</span>\n                                {% endif %}\n                            </td>\n                        </tr>\n                        <tr>\n                            <th scope=\"row\">进程ID</th>\n                            <td>{{ bot_status.pid or '无' }}</td>\n                        </tr>\n                        <tr>\n                            <th scope=\"row\">启动时间</th>\n                            <td>\n                                {% if bot_status.start_time %}\n                                {{ bot_status.start_time|timestamp_to_datetime }}\n                                {% else %}\n                                未启动\n                                {% endif %}\n                            </td>\n                        </tr>\n                        </tbody>\n                    </table>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <!-- 控制栏和日志标题 -->\n    <div class=\"d-flex justify-content-between align-items-center mt-4 mb-3\">\n        <div class=\"xybot-title\">\n            <i class=\"fas fa-robot me-2\"></i>实时日志\n        </div>\n        <div>\n            <button class=\"btn btn-success me-2\" id=\"startBtn\" style=\"display: none;\">\n                <i class=\"fas fa-play\"></i> 启动\n            </button>\n            <button class=\"btn btn-danger me-2\" id=\"stopBtn\" style=\"display: none;\">\n                <i class=\"fas fa-stop\"></i> 停止\n            </button>\n            <button class=\"btn btn-warning me-2\" id=\"restartBtn\" style=\"display: none;\">\n                <i class=\"fas fa-redo\"></i> 重启\n            </button>\n        </div>\n    </div>\n\n    <!-- 日志查看器 -->\n    <div class=\"card\">\n        <div class=\"card-body p-0\">\n            <div class=\"log-viewer\" id=\"logViewer\">\n                <div class=\"text-center p-3\">\n                    <div class=\"spinner-border text-primary\" role=\"status\">\n                        <span class=\"visually-hidden\">加载中...</span>\n                    </div>\n                    <p class=\"mt-2 text-muted\">正在加载日志...</p>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <!-- 页面底部空间 -->\n    <div class=\"page-footer-space\"></div>\n</div>\n{% endblock %}\n\n{% block scripts %}\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.4.1/socket.io.min.js\"></script>\n<script src=\"{{ url_for('static', filename='js/pages/overview.js') }}?v=202403113\"></script>\n{% endblock %} "
  },
  {
    "path": "WebUI/templates/plugin/index.html",
    "content": "{% extends 'base.html' %}\n\n{% from 'components/file_browser.html' import file_browser %}\n\n{% block title %}插件管理 - {{ app_name }}{% endblock %}\n\n{% block styles %}\n{{ super() }}\n<link href=\"https://cdn.jsdelivr.net/npm/jsoneditor@9.10.0/dist/jsoneditor.min.css\" rel=\"stylesheet\">\n<link href=\"{{ url_for('static', filename='css/pages/plugin.css') }}\" rel=\"stylesheet\">\n{% endblock %}\n\n{% block content %}\n<div class=\"container-fluid plugin-container\">\n    <!-- 标题和操作区域 -->\n    <div class=\"d-flex justify-content-between align-items-center plugin-header\">\n        <h1 class=\"h3 mb-0\">插件管理</h1>\n        <div class=\"d-flex align-items-center\">\n            <!-- 刷新按钮 -->\n            <button class=\"btn btn-sm btn-outline-secondary me-2\" id=\"refreshPlugins\" type=\"button\">\n                <i class=\"fas fa-sync-alt\"></i> 刷新列表\n            </button>\n\n            <!-- 搜索框 -->\n            <div class=\"input-group input-group-sm plugin-search\">\n                <input class=\"form-control\" id=\"pluginSearch\" placeholder=\"搜索插件...\" type=\"text\">\n                <div class=\"input-group-append\">\n                    <span class=\"input-group-text\">\n                        <i class=\"fas fa-search\"></i>\n                    </span>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <!-- 插件列表容器 -->\n    <div id=\"pluginListContainer\">\n        <div class=\"text-center p-4 text-gray-500\">加载中...</div>\n    </div>\n</div>\n\n<!-- 插件详情模态框 -->\n<div aria-hidden=\"true\" aria-labelledby=\"pluginDetailModalLabel\" class=\"modal fade\" id=\"pluginDetailModal\"\n     tabindex=\"-1\">\n    <div class=\"modal-dialog modal-lg\">\n        <div class=\"modal-content\">\n            <div class=\"modal-header\">\n                <h5 class=\"modal-title\" id=\"pluginDetailModalLabel\">插件详情</h5>\n                <button aria-label=\"Close\" class=\"close\" data-dismiss=\"modal\" type=\"button\">\n                    <span aria-hidden=\"true\">&times;</span>\n                </button>\n            </div>\n            <div class=\"modal-body\">\n                <ul class=\"nav nav-tabs\" id=\"pluginDetailTabs\" role=\"tablist\">\n                    <li class=\"nav-item\">\n                        <a aria-controls=\"info\" aria-selected=\"true\" class=\"nav-link active\" data-toggle=\"tab\"\n                           href=\"#info\"\n                           id=\"info-tab\" role=\"tab\">基本信息</a>\n                    </li>\n                    <li class=\"nav-item\" id=\"readme-tab-item\">\n                        <a aria-controls=\"readme\" aria-selected=\"false\" class=\"nav-link\" data-toggle=\"tab\"\n                           href=\"#readme\"\n                           id=\"readme-tab\" role=\"tab\">说明文档</a>\n                    </li>\n                    <li class=\"nav-item\" id=\"config-tab-item\">\n                        <a aria-controls=\"config\" aria-selected=\"false\" class=\"nav-link\" data-toggle=\"tab\"\n                           href=\"#config\"\n                           id=\"config-tab\" role=\"tab\">配置</a>\n                    </li>\n                </ul>\n                <div class=\"tab-content p-3\" id=\"pluginDetailTabsContent\">\n                    <!-- 基本信息 -->\n                    <div aria-labelledby=\"info-tab\" class=\"tab-pane fade show active\" id=\"info\" role=\"tabpanel\">\n                        <div class=\"plugin-info\">\n                            <h4 id=\"plugin-name\">加载中...</h4>\n                            <p id=\"plugin-description\"></p>\n                            <div class=\"row\">\n                                <div class=\"col-md-6\">\n                                    <dl class=\"row\">\n                                        <dt class=\"col-sm-4\">作者:</dt>\n                                        <dd class=\"col-sm-8\" id=\"plugin-author\"></dd>\n                                        <dt class=\"col-sm-4\">版本:</dt>\n                                        <dd class=\"col-sm-8\" id=\"plugin-version\"></dd>\n                                        <dt class=\"col-sm-4\">状态:</dt>\n                                        <dd class=\"col-sm-8\" id=\"plugin-status\"></dd>\n                                    </dl>\n                                </div>\n                                <div class=\"col-md-6\">\n                                    <dl class=\"row\">\n                                        <dt class=\"col-sm-4\">目录:</dt>\n                                        <dd class=\"col-sm-8\" id=\"plugin-directory\"></dd>\n                                    </dl>\n                                </div>\n                            </div>\n                        </div>\n                    </div>\n\n                    <!-- 说明文档 -->\n                    <div aria-labelledby=\"readme-tab\" class=\"tab-pane fade\" id=\"readme\" role=\"tabpanel\">\n                        <div class=\"markdown-content\" id=\"plugin-readme\">\n                            <!-- README内容将通过JavaScript加载 -->\n                        </div>\n                    </div>\n\n                    <!-- 配置 -->\n                    <div aria-labelledby=\"config-tab\" class=\"tab-pane fade\" id=\"config\" role=\"tabpanel\">\n                        <form id=\"plugin-config-form\">\n                            <div id=\"plugin-config-editor\">\n                                <!-- 配置编辑器将通过JavaScript加载 -->\n                            </div>\n                            <div class=\"text-right mt-3\">\n                                <button class=\"btn btn-primary\" id=\"savePluginConfig\" type=\"button\">保存配置</button>\n                            </div>\n                        </form>\n                    </div>\n                </div>\n            </div>\n            <div class=\"modal-footer\">\n                <button class=\"btn btn-secondary\" data-dismiss=\"modal\" type=\"button\">关闭</button>\n                <button class=\"btn btn-warning\" id=\"reloadPlugin\" type=\"button\">重新加载</button>\n                <button class=\"btn btn-success\" id=\"enableDisablePlugin\" type=\"button\">加载</button>\n            </div>\n        </div>\n    </div>\n</div>\n\n<!-- 文件浏览器模态框 -->\n<div aria-hidden=\"true\" class=\"modal fade\" id=\"fileBrowserModal\" tabindex=\"-1\">\n    <div class=\"modal-dialog modal-xl\">\n        <div class=\"modal-content\">\n            <div class=\"modal-header\">\n                <h5 class=\"modal-title\">文件浏览器 - <span id=\"currentPluginName\"></span></h5>\n                <button aria-label=\"Close\" class=\"btn-close\" data-bs-dismiss=\"modal\" type=\"button\"></button>\n            </div>\n            <div class=\"modal-body\">\n                {{ file_browser(container_id='plugin-file-browser') }}\n            </div>\n        </div>\n    </div>\n</div>\n{% endblock %}\n\n{% block scripts %}\n<script src=\"https://cdn.jsdelivr.net/npm/marked/marked.min.js\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/jsoneditor@9.10.0/dist/jsoneditor.min.js\"></script>\n<script src=\"{{ url_for('static', filename='js/pages/plugin.js') }}\"></script>\n{% endblock %} "
  },
  {
    "path": "WebUI/templates/tools/index.html",
    "content": "{% extends 'base.html' %}\n\n{% block title %}工具箱 - {{ app_name }}{% endblock %}\n\n{% block styles %}\n{{ super() }}\n<link href=\"{{ url_for('static', filename='css/pages/tools.css') }}\" rel=\"stylesheet\">\n{% endblock %}\n\n{% block content %}\n<div class=\"container-fluid mt-4\">\n    <div class=\"row mb-4\">\n        <div class=\"col-12\">\n            <div class=\"card\">\n                <div class=\"card-header\">\n                    <h1 class=\"h3 mb-0\">工具箱</h1>\n                </div>\n                <div class=\"card-body\">\n                    <div class=\"row\" id=\"toolsContainer\">\n                        <div class=\"col-12\">\n                            <div class=\"loading-container\">\n                                <div class=\"spinner-border text-primary\" role=\"status\">\n                                    <span class=\"sr-only\">加载中...</span>\n                                </div>\n                                <p>正在加载工具...</p>\n                            </div>\n                        </div>\n                    </div>\n                </div>\n            </div>\n        </div>\n    </div>\n</div>\n\n<!-- 确认模态框 -->\n<div aria-hidden=\"true\" aria-labelledby=\"confirmModalLabel\" class=\"modal fade\" id=\"confirmModal\" tabindex=\"-1\">\n    <div class=\"modal-dialog\">\n        <div class=\"modal-content\">\n            <div class=\"modal-header\">\n                <h5 class=\"modal-title\" id=\"confirmModalLabel\">确认执行</h5>\n                <button aria-label=\"Close\" class=\"close\" data-dismiss=\"modal\" type=\"button\">\n                    <span aria-hidden=\"true\">&times;</span>\n                </button>\n            </div>\n            <div class=\"modal-body\" id=\"confirmModalBody\">\n                确定要执行此工具吗？\n            </div>\n            <div class=\"modal-footer\">\n                <button class=\"btn btn-secondary\" data-dismiss=\"modal\" type=\"button\">取消</button>\n                <button class=\"btn btn-primary\" id=\"confirmModalConfirm\" type=\"button\">确认</button>\n            </div>\n        </div>\n    </div>\n</div>\n\n<!-- 执行详情模态框 -->\n<div aria-hidden=\"true\" aria-labelledby=\"executeDetailModalLabel\" class=\"modal fade\" id=\"executeDetailModal\"\n     tabindex=\"-1\">\n    <div class=\"modal-dialog modal-lg\">\n        <div class=\"modal-content\">\n            <div class=\"modal-header\">\n                <h5 class=\"modal-title\" id=\"executeDetailModalLabel\">执行详情</h5>\n                <button aria-label=\"Close\" class=\"close\" data-dismiss=\"modal\" type=\"button\">\n                    <span aria-hidden=\"true\">&times;</span>\n                </button>\n            </div>\n            <div class=\"modal-body\">\n                <div class=\"execution-status\" id=\"detailExecutionStatus\">\n                    <span id=\"detailStatusText\"></span>\n                </div>\n                <div class=\"execution-log mt-3\" id=\"executionLog\"></div>\n            </div>\n            <div class=\"modal-footer\">\n                <button class=\"btn btn-secondary\" data-dismiss=\"modal\" type=\"button\">关闭</button>\n            </div>\n        </div>\n    </div>\n</div>\n{% endblock %}\n\n{% block scripts %}\n{{ super() }}\n<script src=\"{{ url_for('static', filename='js/pages/tools.js') }}\"></script>\n{% endblock %} "
  },
  {
    "path": "WebUI/utils/async_to_sync.py",
    "content": "import asyncio\nimport threading\nfrom functools import wraps\n\n_thread_local = threading.local()\n\n\ndef async_to_sync(func):\n    \"\"\"将异步函数转换为同步函数的装饰器，支持多线程环境\"\"\"\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        # 获取或创建当前线程的事件循环\n        try:\n            loop = asyncio.get_event_loop()\n        except RuntimeError:\n            # 如果当前线程没有事件循环，创建一个新的\n            if not hasattr(_thread_local, 'loop'):\n                _thread_local.loop = asyncio.new_event_loop()\n                asyncio.set_event_loop(_thread_local.loop)\n            loop = _thread_local.loop\n\n        # 运行协程并返回结果\n        coroutine = func(*args, **kwargs)\n        if loop.is_running():\n            # 如果循环已经运行，创建任务\n            return asyncio.create_task(coroutine)\n        else:\n            # 如果循环未运行，运行直到完成\n            return loop.run_until_complete(coroutine)\n\n    return wrapper\n"
  },
  {
    "path": "WebUI/utils/auth_utils.py",
    "content": "import functools\n\nfrom flask import session, redirect, url_for, request, flash\n\nfrom WebUI.config import ADMIN_USERNAME, ADMIN_PASSWORD\n\n\ndef login_required(view):\n    \"\"\"\n    用于装饰需要登录才能访问的视图函数\n    \n    参数:\n        view: 视图函数\n    \n    返回:\n        装饰后的视图函数\n    \"\"\"\n\n    @functools.wraps(view)\n    def wrapped_view(**kwargs):\n        # 检查会话中是否有 authenticated 标志\n        if not session.get('authenticated', False):\n            # 保存请求的URL用于登录后重定向\n            session['redirect_url'] = request.url\n            flash('请先登录后访问此页面', 'warning')\n            return redirect(url_for('auth.login'))\n\n        return view(**kwargs)\n\n    return wrapped_view\n\n\ndef verify_credentials(username, password):\n    \"\"\"\n    验证用户名和密码\n    \n    参数:\n        username: 用户名\n        password: 密码\n    \n    返回:\n        bool: 验证是否成功\n    \"\"\"\n    return username == ADMIN_USERNAME and password == ADMIN_PASSWORD\n"
  },
  {
    "path": "WebUI/utils/singleton.py",
    "content": "class Singleton(type):\n    _instances = {}\n\n    def __call__(cls, *args, **kwargs):\n        if cls not in cls._instances:\n            cls._instances[cls] = super().__call__(*args, **kwargs)\n        return cls._instances[cls]\n\n    @classmethod\n    def reset_instance(mcs, cls):\n        \"\"\"重置指定类的单例实例\"\"\"\n        if cls in mcs._instances:\n            del mcs._instances[cls]\n\n    @classmethod\n    def reset_all(mcs):\n        \"\"\"重置所有单例实例\"\"\"\n        mcs._instances.clear()\n"
  },
  {
    "path": "WebUI/utils/template_filters.py",
    "content": "from datetime import datetime\n\n\ndef timestamp_to_datetime(timestamp):\n    \"\"\"\n    将时间戳转换为格式化日期时间字符串\n    \n    参数:\n        timestamp (float): UNIX时间戳\n        \n    返回:\n        str: 格式化的日期时间字符串\n    \"\"\"\n    if not timestamp:\n        return \"未知\"\n\n    try:\n        dt = datetime.fromtimestamp(float(timestamp))\n        return dt.strftime('%Y-%m-%d %H:%M:%S')\n    except (ValueError, TypeError):\n        return \"无效时间戳\"\n\n\ndef format_file_size(size_bytes):\n    \"\"\"\n    格式化文件大小\n    \n    参数:\n        size_bytes (int): 文件大小（字节）\n        \n    返回:\n        str: 格式化的文件大小字符串\n    \"\"\"\n    if size_bytes < 1024:\n        return f\"{size_bytes} B\"\n    elif size_bytes < 1024 * 1024:\n        return f\"{size_bytes / 1024:.1f} KB\"\n    elif size_bytes < 1024 * 1024 * 1024:\n        return f\"{size_bytes / (1024 * 1024):.1f} MB\"\n    else:\n        return f\"{size_bytes / (1024 * 1024 * 1024):.2f} GB\"\n\n\ndef register_template_filters(app):\n    \"\"\"\n    注册所有模板过滤器到Flask应用\n    \n    参数:\n        app: Flask应用实例\n    \"\"\"\n    app.jinja_env.filters['timestamp_to_datetime'] = timestamp_to_datetime\n    app.jinja_env.filters['format_file_size'] = format_file_size\n"
  },
  {
    "path": "WechatAPI/Client/__init__.py",
    "content": "from WechatAPI.errors import *\nfrom .base import WechatAPIClientBase, Proxy, Section\nfrom .chatroom import ChatroomMixin\nfrom .friend import FriendMixin\nfrom .hongbao import HongBaoMixin\nfrom .login import LoginMixin\nfrom .message import MessageMixin\nfrom .protect import protector\nfrom .protect import protector\nfrom .tool import ToolMixin\nfrom .user import UserMixin\n\n\nclass WechatAPIClient(LoginMixin, MessageMixin, FriendMixin, ChatroomMixin, UserMixin,\n                      ToolMixin, HongBaoMixin):\n\n    # 这里都是需要结合多个功能的方法\n\n    async def send_at_message(self, wxid: str, content: str, at: list[str]) -> tuple[int, int, int]:\n        \"\"\"发送@消息\n\n        Args:\n            wxid (str): 接收人\n            content (str): 消息内容\n            at (list[str]): 要@的用户ID列表\n\n        Returns:\n            tuple[int, int, int]: 包含以下三个值的元组:\n                - ClientMsgid (int): 客户端消息ID\n                - CreateTime (int): 创建时间\n                - NewMsgId (int): 新消息ID\n\n        Raises:\n            UserLoggedOut: 用户未登录时抛出\n            BanProtection: 新设备登录4小时内操作时抛出\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(14400):\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        output = \"\"\n        for id in at:\n            nickname = await self.get_nickname(id)\n            output += f\"@{nickname}\\u2005\"\n\n        output += content\n\n        return await self.send_text_message(wxid, output, at)\n"
  },
  {
    "path": "WechatAPI/Client/base.py",
    "content": "from dataclasses import dataclass\n\nfrom WechatAPI.errors import *\n\n\n@dataclass\nclass Proxy:\n    \"\"\"代理(无效果，别用！)\n\n    Args:\n        ip (str): 代理服务器IP地址\n        port (int): 代理服务器端口\n        username (str, optional): 代理认证用户名. 默认为空字符串\n        password (str, optional): 代理认证密码. 默认为空字符串\n    \"\"\"\n    ip: str\n    port: int\n    username: str = \"\"\n    password: str = \"\"\n\n\n@dataclass\nclass Section:\n    \"\"\"数据段配置类\n\n    Args:\n        data_len (int): 数据长度\n        start_pos (int): 起始位置\n    \"\"\"\n    data_len: int\n    start_pos: int\n\n\nclass WechatAPIClientBase:\n    \"\"\"微信API客户端基类\n\n    Args:\n        ip (str): 服务器IP地址\n        port (int): 服务器端口\n\n    Attributes:\n        wxid (str): 微信ID\n        nickname (str): 昵称\n        alias (str): 别名\n        phone (str): 手机号\n        ignore_protect (bool): 是否忽略保护机制\n    \"\"\"\n    def __init__(self, ip: str, port: int):\n        self.ip = ip\n        self.port = port\n\n        self.wxid = \"\"\n        self.nickname = \"\"\n        self.alias = \"\"\n        self.phone = \"\"\n\n        self.ignore_protect = False\n\n        # 调用所有 Mixin 的初始化方法\n        super().__init__()\n\n    @staticmethod\n    def error_handler(json_resp):\n        \"\"\"处理API响应中的错误码\n\n        Args:\n            json_resp (dict): API响应的JSON数据\n\n        Raises:\n            ValueError: 参数错误时抛出\n            MarshallingError: 序列化错误时抛出\n            UnmarshallingError: 反序列化错误时抛出\n            MMTLSError: MMTLS初始化错误时抛出\n            PacketError: 数据包长度错误时抛出\n            UserLoggedOut: 用户已退出登录时抛出\n            ParsePacketError: 解析数据包错误时抛出\n            DatabaseError: 数据库错误时抛出\n            Exception: 其他类型错误时抛出\n        \"\"\"\n        code = json_resp.get(\"Code\")\n        if code == -1:  # 参数错误\n            raise ValueError(json_resp.get(\"Message\"))\n        elif code == -2:  # 其他错误\n            raise Exception(json_resp.get(\"Message\"))\n        elif code == -3:  # 序列化错误\n            raise MarshallingError(json_resp.get(\"Message\"))\n        elif code == -4:  # 反序列化错误\n            raise UnmarshallingError(json_resp.get(\"Message\"))\n        elif code == -5:  # MMTLS初始化错误\n            raise MMTLSError(json_resp.get(\"Message\"))\n        elif code == -6:  # 收到的数据包长度错误\n            raise PacketError(json_resp.get(\"Message\"))\n        elif code == -7:  # 已退出登录\n            raise UserLoggedOut(\"Already logged out\")\n        elif code == -8:  # 链接过期\n            raise Exception(json_resp.get(\"Message\"))\n        elif code == -9:  # 解析数据包错误\n            raise ParsePacketError(json_resp.get(\"Message\"))\n        elif code == -10:  # 数据库错误\n            raise DatabaseError(json_resp.get(\"Message\"))\n        elif code == -11:  # 登陆异常\n            raise UserLoggedOut(json_resp.get(\"Message\"))\n        elif code == -12:  # 操作过于频繁\n            raise Exception(json_resp.get(\"Message\"))\n        elif code == -13:  # 上传失败\n            raise Exception(json_resp.get(\"Message\"))\n"
  },
  {
    "path": "WechatAPI/Client/chatroom.py",
    "content": "from typing import Union, Any\n\nimport aiohttp\n\nfrom .base import *\nfrom .protect import protector\nfrom ..errors import *\n\n\nclass ChatroomMixin(WechatAPIClientBase):\n    async def add_chatroom_member(self, chatroom: str, wxid: str) -> bool:\n        \"\"\"添加群成员(群聊最多40人)\n\n        Args:\n            chatroom: 群聊wxid\n            wxid: 要添加的wxid\n\n        Returns:\n            bool: 成功返回True, 失败False或者报错\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(14400):\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"Chatroom\": chatroom, \"InviteWxids\": wxid}\n            response = await session.post(f'http://{self.ip}:{self.port}/AddChatroomMember', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return True\n            else:\n                self.error_handler(json_resp)\n\n    async def get_chatroom_announce(self, chatroom: str) -> dict:\n        \"\"\"获取群聊公告\n\n        Args:\n            chatroom: 群聊id\n\n        Returns:\n            dict: 群聊信息字典\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"Chatroom\": chatroom}\n            response = await session.post(f'http://{self.ip}:{self.port}/GetChatroomInfo', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                data = dict(json_resp.get(\"Data\"))\n                data.pop(\"BaseResponse\")\n                return data\n            else:\n                self.error_handler(json_resp)\n\n    async def get_chatroom_info(self, chatroom: str) -> dict:\n        \"\"\"获取群聊信息\n\n        Args:\n            chatroom: 群聊id\n\n        Returns:\n            dict: 群聊信息字典\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(14400):\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"Chatroom\": chatroom}\n            response = await session.post(f'http://{self.ip}:{self.port}/GetChatroomInfoNoAnnounce', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return json_resp.get(\"Data\").get(\"ContactList\")[0]\n            else:\n                self.error_handler(json_resp)\n\n    async def get_chatroom_member_list(self, chatroom: str) -> list[dict]:\n        \"\"\"获取群聊成员列表\n\n        Args:\n            chatroom: 群聊id\n\n        Returns:\n            list[dict]: 群聊成员列表\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"Chatroom\": chatroom}\n            response = await session.post(f'http://{self.ip}:{self.port}/GetChatroomMemberDetail', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return json_resp.get(\"Data\").get(\"NewChatroomData\").get(\"ChatRoomMember\")\n            else:\n                self.error_handler(json_resp)\n\n    async def get_chatroom_qrcode(self, chatroom: str) -> dict[str, Any]:\n        \"\"\"获取群聊二维码\n\n        Args:\n            chatroom: 群聊id\n\n        Returns:\n            dict: {\"base64\": 二维码的base64, \"description\": 二维码描述}\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(86400):\n            raise BanProtection(\"获取二维码需要在登录后24小时才可使用\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"Chatroom\": chatroom}\n            response = await session.post(f'http://{self.ip}:{self.port}/GetChatroomQRCode', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                data = json_resp.get(\"Data\")\n                return {\"base64\": data.get(\"qrcode\").get(\"buffer\"), \"description\": data.get(\"revokeQrcodeWording\")}\n            else:\n                self.error_handler(json_resp)\n\n    async def invite_chatroom_member(self, wxid: Union[str, list], chatroom: str) -> bool:\n        \"\"\"邀请群聊成员(群聊大于40人)\n\n        Args:\n            wxid: 要邀请的用户wxid或wxid列表\n            chatroom: 群聊id\n\n        Returns:\n            bool: 成功返回True, 失败False或者报错\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(14400):\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        if isinstance(wxid, list):\n            wxid = \",\".join(wxid)\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"Chatroom\": chatroom, \"InviteWxids\": wxid}\n            response = await session.post(f'http://{self.ip}:{self.port}/InviteChatroomMember', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return True\n            else:\n                self.error_handler(json_resp)\n"
  },
  {
    "path": "WechatAPI/Client/friend.py",
    "content": "from typing import Union\n\nimport aiohttp\n\nfrom .base import *\nfrom .protect import protector\nfrom ..errors import *\n\n\nclass FriendMixin(WechatAPIClientBase):\n    async def accept_friend(self, scene: int, v1: str, v2: str) -> bool:\n        \"\"\"接受好友请求\n\n        主动添加好友单天上限如下所示：1小时内上限为 5个，超过上限时，无法发出好友请求，也收不到好友请求。\n\n        - 新账号：5/天\n        - 注册超过7天：10个/天\n        - 注册满3个月&&近期登录过该电脑：15/天\n        - 注册满6个月&&近期经常登录过该电脑：20/天\n        - 注册满6个月&&近期频繁登陆过该电脑：30/天\n        - 注册1年以上&&一直登录：50/天\n        - 上一次通过好友到下一次通过间隔20-40s\n        - 收到加人申请，到通过好友申请（每天最多通过300个好友申请），间隔30s+（随机时间）\n\n        Args:\n            scene: 来源 在消息的xml获取\n            v1: v1key\n            v2: v2key\n\n        Returns:\n            bool: 操作是否成功\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(14400):\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"Scene\": scene, \"V1\": v1, \"V2\": v2}\n            response = await session.post(f'http://{self.ip}:{self.port}/AcceptFriend', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return True\n            else:\n                self.error_handler(json_resp)\n\n    async def get_contact(self, wxid: Union[str, list[str]]) -> Union[dict, list[dict]]:\n        \"\"\"获取联系人信息\n\n        Args:\n            wxid: 联系人wxid, 可以是多个wxid在list里，也可查询chatroom\n\n        Returns:\n            Union[dict, list[dict]]: 单个联系人返回dict，多个联系人返回list[dict]\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        if isinstance(wxid, list):\n            wxid = \",\".join(wxid)\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"RequestWxids\": wxid}\n            response = await session.post(f'http://{self.ip}:{self.port}/GetContact', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                contact_list = json_resp.get(\"Data\").get(\"ContactList\")\n                if len(contact_list) == 1:\n                    return contact_list[0]\n                else:\n                    return contact_list\n            else:\n                self.error_handler(json_resp)\n\n    async def get_contract_detail(self, wxid: Union[str, list[str]], chatroom: str = \"\") -> list:\n        \"\"\"获取联系人详情\n\n        Args:\n            wxid: 联系人wxid\n            chatroom: 群聊wxid\n\n        Returns:\n            list: 联系人详情列表\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        if isinstance(wxid, list):\n            if len(wxid) > 20:\n                raise ValueError(\"一次最多查询20个联系人\")\n            wxid = \",\".join(wxid)\n\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"RequestWxids\": wxid, \"Chatroom\": chatroom}\n            response = await session.post(f'http://{self.ip}:{self.port}/GetContractDetail', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return json_resp.get(\"Data\").get(\"ContactList\")\n            else:\n                self.error_handler(json_resp)\n\n    async def get_contract_list(self, wx_seq: int = 0, chatroom_seq: int = 0) -> dict:\n        \"\"\"获取联系人列表\n\n        Args:\n            wx_seq: 联系人序列\n            chatroom_seq: 群聊序列\n\n        Returns:\n            dict: 联系人列表数据\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"CurrentWxcontactSeq\": wx_seq, \"CurrentChatroomContactSeq\": chatroom_seq}\n            response = await session.post(f'http://{self.ip}:{self.port}/GetContractList', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return json_resp.get(\"Data\")\n            else:\n                self.error_handler(json_resp)\n\n    async def get_nickname(self, wxid: Union[str, list[str]]) -> Union[str, list[str]]:\n        \"\"\"获取用户昵称\n\n        Args:\n            wxid: 用户wxid，可以是单个wxid或最多20个wxid的列表\n\n        Returns:\n            Union[str, list[str]]: 如果输入单个wxid返回str，如果输入wxid列表则返回对应的昵称列表\n        \"\"\"\n        data = await self.get_contract_detail(wxid)\n\n        if isinstance(wxid, str):\n            try:\n                return data[0].get(\"NickName\").get(\"string\")\n            except:\n                return \"\"\n        else:\n            result = []\n            for contact in data:\n                try:\n                    result.append(contact.get(\"NickName\").get(\"string\"))\n                except:\n                    result.append(\"\")\n            return result\n"
  },
  {
    "path": "WechatAPI/Client/hongbao.py",
    "content": "import aiohttp\n\nfrom .base import *\nfrom ..errors import *\n\n\nclass HongBaoMixin(WechatAPIClientBase):\n    async def get_hongbao_detail(self, xml: str, encrypt_key: str, encrypt_userinfo: str) -> dict:\n        \"\"\"获取红包详情\n\n        Args:\n            xml: 红包 XML 数据\n            encrypt_key: 加密密钥\n            encrypt_userinfo: 加密的用户信息\n\n        Returns:\n            dict: 红包详情数据\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"Xml\": xml, \"EncryptKey\": encrypt_key, \"EncryptUserinfo\": encrypt_userinfo}\n            response = await session.post(f'http://{self.ip}:{self.port}/GetHongBaoDetail', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return json_resp.get(\"Data\")\n            else:\n                self.error_handler(json_resp)\n"
  },
  {
    "path": "WechatAPI/Client/login.py",
    "content": "import hashlib\nimport io\nimport string\nfrom random import choice\nfrom typing import Union\n\nimport aiohttp\nimport qrcode\n\nfrom .base import *\nfrom .protect import protector\nfrom ..errors import *\n\n\nclass LoginMixin(WechatAPIClientBase):\n    async def is_running(self) -> bool:\n        \"\"\"检查WechatAPI是否在运行。\n\n        Returns:\n            bool: 如果WechatAPI正在运行返回True，否则返回False。\n        \"\"\"\n        try:\n            async with aiohttp.ClientSession() as session:\n                response = await session.get(f'http://{self.ip}:{self.port}/IsRunning')\n                return await response.text() == 'OK'\n        except aiohttp.client_exceptions.ClientConnectorError:\n            return False\n\n    async def get_qr_code(self, device_name: str, device_id: str = \"\", proxy: Proxy = None) -> (\n            str, str):\n        \"\"\"获取登录二维码。\n\n        Args:\n            device_name (str): 设备名称\n            device_id (str, optional): 设备ID. Defaults to \"\".\n            proxy (Proxy, optional): 代理信息. Defaults to None.\n\n        Returns:\n            tuple[str, str, str]: 返回UUID，URL，登录二维码\n\n        Raises:\n            根据error_handler处理错误\n        \"\"\"\n        async with aiohttp.ClientSession() as session:\n            json_param = {'DeviceName': device_name, 'DeviceID': device_id}\n            if proxy:\n                json_param['ProxyInfo'] = {'ProxyIp': f'{proxy.ip}:{proxy.port}',\n                                           'ProxyPassword': proxy.password,\n                                           'ProxyUser': proxy.username}\n\n            response = await session.post(f'http://{self.ip}:{self.port}/GetQRCode', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                qr = qrcode.QRCode(\n                    version=1,\n                    box_size=10,\n                    border=4,\n                )\n                qr.add_data(f'http://weixin.qq.com/x/{json_resp.get(\"Data\").get(\"Uuid\")}')\n                qr.make(fit=True)\n                f = io.StringIO()\n                qr.print_ascii(out=f)\n                f.seek(0)\n\n                return json_resp.get(\"Data\").get(\"Uuid\"), json_resp.get(\"Data\").get(\"QRCodeURL\"), f.read()\n            else:\n                self.error_handler(json_resp)\n\n    async def check_login_uuid(self, uuid: str, device_id: str = \"\") -> tuple[bool, Union[dict, int]]:\n        \"\"\"检查登录的UUID状态。\n\n        Args:\n            uuid (str): 登录的UUID\n            device_id (str, optional): 设备ID. Defaults to \"\".\n\n        Returns:\n            tuple[bool, Union[dict, int]]: 如果登录成功返回(True, 用户信息)，否则返回(False, 过期时间)\n\n        Raises:\n            根据error_handler处理错误\n        \"\"\"\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Uuid\": uuid}\n            response = await session.post(f'http://{self.ip}:{self.port}/CheckUuid', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                if json_resp.get(\"Data\").get(\"acctSectResp\", \"\"):\n                    self.wxid = json_resp.get(\"Data\").get(\"acctSectResp\").get(\"userName\")\n                    self.nickname = json_resp.get(\"Data\").get(\"acctSectResp\").get(\"nickName\")\n                    protector.update_login_status(device_id=device_id)\n                    return True, json_resp.get(\"Data\")\n                else:\n                    return False, json_resp.get(\"Data\").get(\"expiredTime\")\n            else:\n                self.error_handler(json_resp)\n\n    async def log_out(self) -> bool:\n        \"\"\"登出当前账号。\n\n        Returns:\n            bool: 登出成功返回True，否则返回False\n\n        Raises:\n            UserLoggedOut: 如果未登录时调用\n            根据error_handler处理错误\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid}\n            response = await session.post(f'http://{self.ip}:{self.port}/Logout', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return True\n            elif json_resp.get(\"Success\"):\n                return False\n            else:\n                self.error_handler(json_resp)\n\n    async def awaken_login(self, wxid: str = \"\") -> str:\n        \"\"\"唤醒登录。\n\n        Args:\n            wxid (str, optional): 要唤醒的微信ID. Defaults to \"\".\n\n        Returns:\n            str: 返回新的登录UUID\n\n        Raises:\n            Exception: 如果未提供wxid且未登录\n            LoginError: 如果无法获取UUID\n            根据error_handler处理错误\n        \"\"\"\n        if not wxid and not self.wxid:\n            raise Exception(\"Please login using QRCode first\")\n\n        if not wxid and self.wxid:\n            wxid = self.wxid\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": wxid}\n            response = await session.post(f'http://{self.ip}:{self.port}/AwakenLogin', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\") and json_resp.get(\"Data\").get(\"QrCodeResponse\").get(\"Uuid\"):\n                return json_resp.get(\"Data\").get(\"QrCodeResponse\").get(\"Uuid\")\n            elif not json_resp.get(\"Data\").get(\"QrCodeResponse\").get(\"Uuid\"):\n                raise LoginError(\"Please login using QRCode first\")\n            else:\n                self.error_handler(json_resp)\n\n    async def get_cached_info(self, wxid: str = None) -> dict:\n        \"\"\"获取登录缓存信息。\n\n        Args:\n            wxid (str, optional): 要查询的微信ID. Defaults to None.\n\n        Returns:\n            dict: 返回缓存信息，如果未提供wxid且未登录返回空字典\n        \"\"\"\n        if not wxid:\n            wxid = self.wxid\n\n        if not wxid:\n            return {}\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": wxid}\n            response = await session.post(f'http://{self.ip}:{self.port}/GetCachedInfo', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return json_resp.get(\"Data\")\n            else:\n                return {}\n\n    async def heartbeat(self) -> bool:\n        \"\"\"发送心跳包。\n\n        Returns:\n            bool: 成功返回True，否则返回False\n\n        Raises:\n            UserLoggedOut: 如果未登录时调用\n            根据error_handler处理错误\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid}\n            response = await session.post(f'http://{self.ip}:{self.port}/Heartbeat', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return True\n            else:\n                self.error_handler(json_resp)\n\n    async def start_auto_heartbeat(self) -> bool:\n        \"\"\"开始自动心跳。\n\n        Returns:\n            bool: 成功返回True，否则返回False\n\n        Raises:\n            UserLoggedOut: 如果未登录时调用\n            根据error_handler处理错误\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid}\n            response = await session.post(f'http://{self.ip}:{self.port}/AutoHeartbeatStart', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return True\n            else:\n                self.error_handler(json_resp)\n\n    async def stop_auto_heartbeat(self) -> bool:\n        \"\"\"停止自动心跳。\n\n        Returns:\n            bool: 成功返回True，否则返回False\n\n        Raises:\n            UserLoggedOut: 如果未登录时调用\n            根据error_handler处理错误\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid}\n            response = await session.post(f'http://{self.ip}:{self.port}/AutoHeartbeatStop', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return True\n            else:\n                self.error_handler(json_resp)\n\n    async def get_auto_heartbeat_status(self) -> bool:\n        \"\"\"获取自动心跳状态。\n\n        Returns:\n            bool: 如果正在运行返回True，否则返回False\n\n        Raises:\n            UserLoggedOut: 如果未登录时调用\n            根据error_handler处理错误\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid}\n            response = await session.post(f'http://{self.ip}:{self.port}/AutoHeartbeatStatus', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return json_resp.get(\"Data\").get(\"Running\")\n            else:\n                return self.error_handler(json_resp)\n\n    @staticmethod\n    def create_device_name() -> str:\n        \"\"\"生成一个随机的设备名。\n\n        Returns:\n            str: 返回生成的设备名\n        \"\"\"\n        first_names = [\n            \"Oliver\", \"Emma\", \"Liam\", \"Ava\", \"Noah\", \"Sophia\", \"Elijah\", \"Isabella\",\n            \"James\", \"Mia\", \"William\", \"Amelia\", \"Benjamin\", \"Harper\", \"Lucas\", \"Evelyn\",\n            \"Henry\", \"Abigail\", \"Alexander\", \"Ella\", \"Jackson\", \"Scarlett\", \"Sebastian\",\n            \"Grace\", \"Aiden\", \"Chloe\", \"Matthew\", \"Zoey\", \"Samuel\", \"Lily\", \"David\",\n            \"Aria\", \"Joseph\", \"Riley\", \"Carter\", \"Nora\", \"Owen\", \"Luna\", \"Daniel\",\n            \"Sofia\", \"Gabriel\", \"Ellie\", \"Matthew\", \"Avery\", \"Isaac\", \"Mila\", \"Leo\",\n            \"Julian\", \"Layla\"\n        ]\n\n        last_names = [\n            \"Smith\", \"Johnson\", \"Williams\", \"Brown\", \"Jones\", \"Garcia\", \"Miller\", \"Davis\",\n            \"Rodriguez\", \"Martinez\", \"Hernandez\", \"Lopez\", \"Gonzalez\", \"Wilson\", \"Anderson\",\n            \"Thomas\", \"Taylor\", \"Moore\", \"Jackson\", \"Martin\", \"Lee\", \"Perez\", \"Thompson\",\n            \"White\", \"Harris\", \"Sanchez\", \"Clark\", \"Ramirez\", \"Lewis\", \"Robinson\", \"Walker\",\n            \"Young\", \"Allen\", \"King\", \"Wright\", \"Scott\", \"Torres\", \"Nguyen\", \"Hill\",\n            \"Flores\", \"Green\", \"Adams\", \"Nelson\", \"Baker\", \"Hall\", \"Rivera\", \"Campbell\",\n            \"Mitchell\", \"Carter\", \"Roberts\", \"Gomez\", \"Phillips\", \"Evans\"\n        ]\n\n        return choice(first_names) + \" \" + choice(last_names) + \"'s Pad\"\n\n    @staticmethod\n    def create_device_id(s: str = \"\") -> str:\n        \"\"\"生成设备ID。\n\n        Args:\n            s (str, optional): 用于生成ID的字符串. Defaults to \"\".\n\n        Returns:\n            str: 返回生成的设备ID\n        \"\"\"\n        if s == \"\" or s == \"string\":\n            s = ''.join(choice(string.ascii_letters) for _ in range(15))\n        md5_hash = hashlib.md5(s.encode()).hexdigest()\n        return \"49\" + md5_hash[2:]\n"
  },
  {
    "path": "WechatAPI/Client/message.py",
    "content": "import asyncio\nimport base64\nimport os\nfrom asyncio import Future\nfrom asyncio import Queue, sleep\nfrom io import BytesIO\nfrom pathlib import Path\nfrom typing import Union\n\nimport aiohttp\nimport pysilk\nfrom loguru import logger\nfrom pydub import AudioSegment\nfrom pymediainfo import MediaInfo\n\nfrom .base import *\nfrom .protect import protector\nfrom ..errors import *\n\n\nclass MessageMixin(WechatAPIClientBase):\n    def __init__(self, ip: str, port: int):\n        # 初始化消息队列\n        super().__init__(ip, port)\n        self._message_queue = Queue()\n        self._is_processing = False\n\n    async def _process_message_queue(self):\n        \"\"\"\n        处理消息队列的异步方法\n        \"\"\"\n        if self._is_processing:\n            return\n\n        self._is_processing = True\n        while True:\n            if self._message_queue.empty():\n                self._is_processing = False\n                break\n\n            func, args, kwargs, future = await self._message_queue.get()\n            try:\n                result = await func(*args, **kwargs)\n                future.set_result(result)\n            except Exception as e:\n                future.set_exception(e)\n            finally:\n                self._message_queue.task_done()\n                await sleep(1)  # 消息发送间隔1秒\n\n    async def _queue_message(self, func, *args, **kwargs):\n        \"\"\"\n        将消息添加到队列\n        \"\"\"\n        future = Future()\n        await self._message_queue.put((func, args, kwargs, future))\n\n        if not self._is_processing:\n            asyncio.create_task(self._process_message_queue())\n\n        return await future\n\n    async def revoke_message(self, wxid: str, client_msg_id: int, create_time: int, new_msg_id: int) -> bool:\n        \"\"\"撤回消息。\n\n        Args:\n            wxid (str): 接收人wxid\n            client_msg_id (int): 发送消息的返回值\n            create_time (int): 发送消息的返回值\n            new_msg_id (int): 发送消息的返回值\n\n        Returns:\n            bool: 成功返回True，失败返回False\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            BanProtection: 登录新设备后4小时内操作\n            根据error_handler处理错误\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(14400):\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"ToWxid\": wxid, \"ClientMsgId\": client_msg_id, \"CreateTime\": create_time,\n                          \"NewMsgId\": new_msg_id}\n            response = await session.post(f'http://{self.ip}:{self.port}/RevokeMsg', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                logger.info(\"消息撤回成功: 对方wxid:{} ClientMsgId:{} CreateTime:{} NewMsgId:{}\",\n                            wxid,\n                            client_msg_id,\n                            new_msg_id)\n                return True\n            else:\n                self.error_handler(json_resp)\n\n    async def send_text_message(self, wxid: str, content: str, at: Union[list, str] = \"\") -> tuple[int, int, int]:\n        \"\"\"发送文本消息。\n\n        Args:\n            wxid (str): 接收人wxid\n            content (str): 消息内容\n            at (list, str, optional): 要@的用户\n\n        Returns:\n            tuple[int, int, int]: 返回(ClientMsgid, CreateTime, NewMsgId)\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            BanProtection: 登录新设备后4小时内操作\n            根据error_handler处理错误\n        \"\"\"\n        return await self._queue_message(self._send_text_message, wxid, content, at)\n\n    async def _send_text_message(self, wxid: str, content: str, at: list[str] = None) -> tuple[int, int, int]:\n        \"\"\"\n        实际发送文本消息的方法\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(14400):\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        if isinstance(at, str):\n            at_str = at\n        elif isinstance(at, list):\n            if at is None:\n                at = []\n            at_str = \",\".join(at)\n        else:\n            raise ValueError(\"Argument 'at' should be str or list\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"ToWxid\": wxid, \"Content\": content, \"Type\": 1, \"At\": at_str}\n            response = await session.post(f'http://{self.ip}:{self.port}/SendTextMsg', json=json_param)\n            json_resp = await response.json()\n            if json_resp.get(\"Success\"):\n                logger.info(\"发送文字消息: 对方wxid:{} at:{} 内容:{}\", wxid, at, content)\n                data = json_resp.get(\"Data\")\n                return data.get(\"List\")[0].get(\"ClientMsgid\"), data.get(\"List\")[0].get(\"Createtime\"), data.get(\"List\")[\n                    0].get(\"NewMsgId\")\n            else:\n                self.error_handler(json_resp)\n\n    async def send_image_message(self, wxid: str, image: Union[str, bytes, os.PathLike]) -> tuple[int, int, int]:\n        \"\"\"发送图片消息。\n\n        Args:\n            wxid (str): 接收人wxid\n            image (str, byte, os.PathLike): 图片，支持base64字符串，图片byte，图片路径\n\n        Returns:\n            tuple[int, int, int]: 返回(ClientImgId, CreateTime, NewMsgId)\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            BanProtection: 登录新设备后4小时内操作\n            ValueError: image_path和image_base64都为空或都不为空时\n            根据error_handler处理错误\n        \"\"\"\n        return await self._queue_message(self._send_image_message, wxid, image)\n\n    async def _send_image_message(self, wxid: str, image: Union[str, bytes, os.PathLike]) -> tuple[\n        int, int, int]:\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(14400):\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        if isinstance(image, str):\n            pass\n        elif isinstance(image, bytes):\n            image = base64.b64encode(image).decode()\n        elif isinstance(image, os.PathLike):\n            with open(image, 'rb') as f:\n                image = base64.b64encode(f.read()).decode()\n        else:\n            raise ValueError(\"Argument 'image' can only be str, bytes, or os.PathLike\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"ToWxid\": wxid, \"Base64\": image}\n            response = await session.post(f'http://{self.ip}:{self.port}/SendImageMsg', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                json_param.pop('Base64')\n                logger.info(\"发送图片消息: 对方wxid:{} 图片base64略\", wxid)\n                data = json_resp.get(\"Data\")\n                return data.get(\"ClientImgId\").get(\"string\"), data.get(\"CreateTime\"), data.get(\"Newmsgid\")\n            else:\n                self.error_handler(json_resp)\n\n    async def send_video_message(self, wxid: str, video: Union[str, bytes, os.PathLike],\n                                 image: [str, bytes, os.PathLike] = None):\n        \"\"\"发送视频消息。不推荐使用，上传速度很慢300KB/s。如要使用，可压缩视频，或者发送链接卡片而不是视频。\n\n                Args:\n                    wxid (str): 接收人wxid\n                    video (str, bytes, os.PathLike): 视频 接受base64字符串，字节，文件路径\n                    image (str, bytes, os.PathLike): 视频封面图片 接受base64字符串，字节，文件路径\n\n                Returns:\n                    tuple[int, int]: 返回(ClientMsgid, NewMsgId)\n\n                Raises:\n                    UserLoggedOut: 未登录时调用\n                    BanProtection: 登录新设备后4小时内操作\n                    ValueError: 视频或图片参数都为空或都不为空时\n                    根据error_handler处理错误\n                \"\"\"\n        if not image:\n            image = Path(os.path.join(Path(__file__).resolve().parent, \"fallback.png\"))\n        # get video base64 and duration\n        if isinstance(video, str):\n            vid_base64 = video\n            video = base64.b64decode(video)\n            file_len = len(video)\n            media_info = MediaInfo.parse(BytesIO(video))\n        elif isinstance(video, bytes):\n            vid_base64 = base64.b64encode(video).decode()\n            file_len = len(video)\n            media_info = MediaInfo.parse(BytesIO(video))\n        elif isinstance(video, os.PathLike):\n            with open(video, \"rb\") as f:\n                file_len = len(f.read())\n                vid_base64 = base64.b64encode(f.read()).decode()\n            media_info = MediaInfo.parse(video)\n        else:\n            raise ValueError(\"video should be str, bytes, or path\")\n        duration = media_info.tracks[0].duration\n\n        # get image base64\n        if isinstance(image, str):\n            image_base64 = image\n        elif isinstance(image, bytes):\n            image_base64 = base64.b64encode(image).decode()\n        elif isinstance(image, os.PathLike):\n            with open(image, \"rb\") as f:\n                image_base64 = base64.b64encode(f.read()).decode()\n        else:\n            raise ValueError(\"image should be str, bytes, or path\")\n\n        # 打印预估时间，300KB/s\n        predict_time = int(file_len / 1024 / 300)\n        logger.info(\"开始发送视频: 对方wxid:{} 视频base64略 图片base64略 预计耗时:{}秒\", wxid, predict_time)\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"ToWxid\": wxid, \"Base64\": vid_base64, \"ImageBase64\": image_base64,\n                          \"PlayLength\": duration}\n            async with session.post(f'http://{self.ip}:{self.port}/SendVideoMsg', json=json_param) as resp:\n                json_resp = await resp.json()\n\n        if json_resp.get(\"Success\"):\n            json_param.pop('Base64')\n            json_param.pop('ImageBase64')\n            logger.info(\"发送视频成功: 对方wxid:{} 时长:{} 视频base64略 图片base64略\", wxid, duration)\n            data = json_resp.get(\"Data\")\n            return data.get(\"clientMsgId\"), data.get(\"newMsgId\")\n        else:\n            self.error_handler(json_resp)\n\n    async def send_voice_message(self, wxid: str, voice: Union[str, bytes, os.PathLike], format: str = \"amr\") -> \\\n            tuple[int, int, int]:\n        \"\"\"发送语音消息。\n\n        Args:\n            wxid (str): 接收人wxid\n            voice (str, bytes, os.PathLike): 语音 接受base64字符串，字节，文件路径\n            format (str, optional): 语音格式，支持amr/wav/mp3. Defaults to \"amr\".\n\n        Returns:\n            tuple[int, int, int]: 返回(ClientMsgid, CreateTime, NewMsgId)\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            BanProtection: 登录新设备后4小时内操作\n            ValueError: voice_path和voice_base64都为空或都不为空时，或format不支持时\n            根据error_handler处理错误\n        \"\"\"\n        return await self._queue_message(self._send_voice_message, wxid, voice, format)\n\n    async def _send_voice_message(self, wxid: str, voice: Union[str, bytes, os.PathLike], format: str = \"amr\") -> \\\n            tuple[int, int, int]:\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(14400):\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n        elif format not in [\"amr\", \"wav\", \"mp3\"]:\n            raise ValueError(\"format must be one of amr, wav, mp3\")\n\n        # read voice to byte\n        if isinstance(voice, str):\n            voice_byte = base64.b64decode(voice)\n        elif isinstance(voice, bytes):\n            voice_byte = voice\n        elif isinstance(voice, os.PathLike):\n            with open(voice, \"rb\") as f:\n                voice_byte = f.read()\n        else:\n            raise ValueError(\"voice should be str, bytes, or path\")\n\n        # get voice duration and b64\n        if format.lower() == \"amr\":\n            audio = AudioSegment.from_file(BytesIO(voice_byte), format=\"amr\")\n            voice_base64 = base64.b64encode(voice_byte).decode()\n        elif format.lower() == \"wav\":\n            audio = AudioSegment.from_file(BytesIO(voice_byte), format=\"wav\").set_channels(1)\n            audio = audio.set_frame_rate(self._get_closest_frame_rate(audio.frame_rate))\n            voice_base64 = base64.b64encode(\n                await pysilk.async_encode(audio.raw_data, sample_rate=audio.frame_rate)).decode()\n        elif format.lower() == \"mp3\":\n            audio = AudioSegment.from_file(BytesIO(voice_byte), format=\"mp3\").set_channels(1)\n            audio = audio.set_frame_rate(self._get_closest_frame_rate(audio.frame_rate))\n            voice_base64 = base64.b64encode(\n                await pysilk.async_encode(audio.raw_data, sample_rate=audio.frame_rate)).decode()\n        else:\n            raise ValueError(\"format must be one of amr, wav, mp3\")\n\n        duration = len(audio)\n\n        format_dict = {\"amr\": 0, \"wav\": 4, \"mp3\": 4}\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"ToWxid\": wxid, \"Base64\": voice_base64, \"VoiceTime\": duration,\n                          \"Type\": format_dict[format]}\n            response = await session.post(f'http://{self.ip}:{self.port}/SendVoiceMsg', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                json_param.pop('Base64')\n                logger.info(\"发送语音消息: 对方wxid:{} 时长:{} 格式:{} 音频base64略\", wxid, duration, format)\n                data = json_resp.get(\"Data\")\n                return int(data.get(\"ClientMsgId\")), data.get(\"CreateTime\"), data.get(\"NewMsgId\")\n            else:\n                self.error_handler(json_resp)\n\n    @staticmethod\n    def _get_closest_frame_rate(frame_rate: int) -> int:\n        supported = [8000, 12000, 16000, 24000]\n        closest_rate = None\n        smallest_diff = float('inf')\n        for num in supported:\n            diff = abs(frame_rate - num)\n            if diff < smallest_diff:\n                smallest_diff = diff\n                closest_rate = num\n\n        return closest_rate\n\n    async def send_link_message(self, wxid: str, url: str, title: str = \"\", description: str = \"\",\n                                thumb_url: str = \"\") -> tuple[str, int, int]:\n        \"\"\"发送链接消息。\n\n        Args:\n            wxid (str): 接收人wxid\n            url (str): 跳转链接\n            title (str, optional): 标题. Defaults to \"\".\n            description (str, optional): 描述. Defaults to \"\".\n            thumb_url (str, optional): 缩略图链接. Defaults to \"\".\n\n        Returns:\n            tuple[str, int, int]: 返回(ClientMsgid, CreateTime, NewMsgId)\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            BanProtection: 登录新设备后4小时内操作\n            根据error_handler处理错误\n        \"\"\"\n        return await self._queue_message(self._send_link_message, wxid, url, title, description, thumb_url)\n\n    async def _send_link_message(self, wxid: str, url: str, title: str = \"\", description: str = \"\",\n                                 thumb_url: str = \"\") -> tuple[int, int, int]:\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(14400):\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"ToWxid\": wxid, \"Url\": url, \"Title\": title, \"Desc\": description,\n                          \"ThumbUrl\": thumb_url}\n            response = await session.post(f'http://{self.ip}:{self.port}/SendShareLink', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                logger.info(\"发送链接消息: 对方wxid:{} 链接:{} 标题:{} 描述:{} 缩略图链接:{}\",\n                            wxid,\n                            url,\n                            title,\n                            description,\n                            thumb_url)\n                data = json_resp.get(\"Data\")\n                return data.get(\"clientMsgId\"), data.get(\"createTime\"), data.get(\"newMsgId\")\n            else:\n                self.error_handler(json_resp)\n\n    async def send_emoji_message(self, wxid: str, md5: str, total_length: int) -> list[dict]:\n        \"\"\"发送表情消息。\n\n        Args:\n            wxid (str): 接收人wxid\n            md5 (str): 表情md5值\n            total_length (int): 表情总长度\n\n        Returns:\n            list[dict]: 返回表情项列表(list of emojiItem)\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            BanProtection: 登录新设备后4小时内操作\n            根据error_handler处理错误\n        \"\"\"\n        return await self._queue_message(self._send_emoji_message, wxid, md5, total_length)\n\n    async def _send_emoji_message(self, wxid: str, md5: str, total_length: int) -> tuple[int, int, int]:\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(14400):\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"ToWxid\": wxid, \"Md5\": md5, \"TotalLen\": total_length}\n            response = await session.post(f'http://{self.ip}:{self.port}/SendEmojiMsg', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                logger.info(\"发送表情消息: 对方wxid:{} md5:{} 总长度:{}\", wxid, md5, total_length)\n                return json_resp.get(\"Data\").get(\"emojiItem\")\n            else:\n                self.error_handler(json_resp)\n\n    async def send_card_message(self, wxid: str, card_wxid: str, card_nickname: str, card_alias: str = \"\") -> tuple[\n        int, int, int]:\n        \"\"\"发送名片消息。\n\n        Args:\n            wxid (str): 接收人wxid\n            card_wxid (str): 名片用户的wxid\n            card_nickname (str): 名片用户的昵称\n            card_alias (str, optional): 名片用户的备注. Defaults to \"\".\n\n        Returns:\n            tuple[int, int, int]: 返回(ClientMsgid, CreateTime, NewMsgId)\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            BanProtection: 登录新设备后4小时内操作\n            根据error_handler处理错误\n        \"\"\"\n        return await self._queue_message(self._send_card_message, wxid, card_wxid, card_nickname, card_alias)\n\n    async def _send_card_message(self, wxid: str, card_wxid: str, card_nickname: str, card_alias: str = \"\") -> tuple[\n        int, int, int]:\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(14400):\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"ToWxid\": wxid, \"CardWxid\": card_wxid, \"CardAlias\": card_alias,\n                          \"CardNickname\": card_nickname}\n            response = await session.post(f'http://{self.ip}:{self.port}/SendCardMsg', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                logger.info(\"发送名片消息: 对方wxid:{} 名片wxid:{} 名片备注:{} 名片昵称:{}\", wxid,\n                            card_wxid,\n                            card_alias,\n                            card_nickname)\n                data = json_resp.get(\"Data\")\n                return data.get(\"List\")[0].get(\"ClientMsgid\"), data.get(\"List\")[0].get(\"Createtime\"), data.get(\"List\")[\n                    0].get(\"NewMsgId\")\n            else:\n                self.error_handler(json_resp)\n\n    async def send_app_message(self, wxid: str, xml: str, type: int) -> tuple[str, int, int]:\n        \"\"\"发送应用消息。\n\n        Args:\n            wxid (str): 接收人wxid\n            xml (str): 应用消息的xml内容\n            type (int): 应用消息类型\n\n        Returns:\n            tuple[str, int, int]: 返回(ClientMsgid, CreateTime, NewMsgId)\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            BanProtection: 登录新设备后4小时内操作\n            根据error_handler处理错误\n        \"\"\"\n        return await self._queue_message(self._send_app_message, wxid, xml, type)\n\n    async def _send_app_message(self, wxid: str, xml: str, type: int) -> tuple[int, int, int]:\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(14400):\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"ToWxid\": wxid, \"Xml\": xml, \"Type\": type}\n            response = await session.post(f'http://{self.ip}:{self.port}/SendAppMsg', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                json_param[\"Xml\"] = json_param[\"Xml\"].replace(\"\\n\", \"\")\n                logger.info(\"发送app消息: 对方wxid:{} 类型:{} xml:{}\", wxid, type, json_param[\"Xml\"])\n                return json_resp.get(\"Data\").get(\"clientMsgId\"), json_resp.get(\"Data\").get(\n                    \"createTime\"), json_resp.get(\"Data\").get(\"newMsgId\")\n            else:\n                self.error_handler(json_resp)\n\n    async def send_cdn_file_msg(self, wxid: str, xml: str) -> tuple[str, int, int]:\n        \"\"\"转发文件消息。\n\n        Args:\n            wxid (str): 接收人wxid\n            xml (str): 要转发的文件消息xml内容\n\n        Returns:\n            tuple[str, int, int]: 返回(ClientMsgid, CreateTime, NewMsgId)\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            BanProtection: 登录新设备后4小时内操作\n            根据error_handler处理错误\n        \"\"\"\n        return await self._queue_message(self._send_cdn_file_msg, wxid, xml)\n\n    async def _send_cdn_file_msg(self, wxid: str, xml: str) -> tuple[int, int, int]:\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(14400):\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"ToWxid\": wxid, \"Content\": xml}\n            response = await session.post(f'http://{self.ip}:{self.port}/SendCDNFileMsg', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                logger.info(\"转发文件消息: 对方wxid:{} xml:{}\", wxid, xml)\n                data = json_resp.get(\"Data\")\n                return data.get(\"clientMsgId\"), data.get(\"createTime\"), data.get(\"newMsgId\")\n            else:\n                self.error_handler(json_resp)\n\n    async def send_cdn_img_msg(self, wxid: str, xml: str) -> tuple[str, int, int]:\n        \"\"\"转发图片消息。\n\n        Args:\n            wxid (str): 接收人wxid\n            xml (str): 要转发的图片消息xml内容\n\n        Returns:\n            tuple[str, int, int]: 返回(ClientImgId, CreateTime, NewMsgId)\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            BanProtection: 登录新设备后4小时内操作\n            根据error_handler处理错误\n        \"\"\"\n        return await self._queue_message(self._send_cdn_img_msg, wxid, xml)\n\n    async def _send_cdn_img_msg(self, wxid: str, xml: str) -> tuple[int, int, int]:\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(14400):\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"ToWxid\": wxid, \"Content\": xml}\n            response = await session.post(f'http://{self.ip}:{self.port}/SendCDNImgMsg', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                logger.info(\"转发图片消息: 对方wxid:{} xml:{}\", wxid, xml)\n                data = json_resp.get(\"Data\")\n                return data.get(\"ClientImgId\").get(\"string\"), data.get(\"CreateTime\"), data.get(\"Newmsgid\")\n            else:\n                self.error_handler(json_resp)\n\n    async def send_cdn_video_msg(self, wxid: str, xml: str) -> tuple[str, int]:\n        \"\"\"转发视频消息。\n\n        Args:\n            wxid (str): 接收人wxid\n            xml (str): 要转发的视频消息xml内容\n\n        Returns:\n            tuple[str, int]: 返回(ClientMsgid, NewMsgId)\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            BanProtection: 登录新设备后4小时内操作\n            根据error_handler处理错误\n        \"\"\"\n        return await self._queue_message(self._send_cdn_video_msg, wxid, xml)\n\n    async def _send_cdn_video_msg(self, wxid: str, xml: str) -> tuple[int, int]:\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(14400):\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"ToWxid\": wxid, \"Content\": xml}\n            response = await session.post(f'http://{self.ip}:{self.port}/SendCDNVideoMsg', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                logger.info(\"转发视频消息: 对方wxid:{} xml:{}\", wxid, xml)\n                data = json_resp.get(\"Data\")\n                return data.get(\"clientMsgId\"), data.get(\"newMsgId\")\n            else:\n                self.error_handler(json_resp)\n\n    async def sync_message(self) -> dict:\n        \"\"\"同步消息。\n\n        Returns:\n            dict: 返回同步到的消息数据\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            根据error_handler处理错误\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) as session:\n            json_param = {\"Wxid\": self.wxid, \"Scene\": 0, \"Synckey\": \"\"}\n            response = await session.post(f'http://{self.ip}:{self.port}/Sync', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return json_resp.get(\"Data\")\n            else:\n                self.error_handler(json_resp)\n"
  },
  {
    "path": "WechatAPI/Client/protect.py",
    "content": "import json\nimport os\nfrom datetime import datetime\n\n\nclass Singleton(type):\n    \"\"\"单例模式的元类。\n\n    用于确保一个类只有一个实例。\n\n    Attributes:\n        _instances (dict): 存储类的实例的字典\n    \"\"\"\n    _instances = {}\n\n    def __call__(cls, *args, **kwargs):\n        \"\"\"创建或返回类的单例实例。\n\n        Args:\n            *args: 位置参数\n            **kwargs: 关键字参数\n\n        Returns:\n            object: 类的单例实例\n        \"\"\"\n        if cls not in cls._instances:\n            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)\n        return cls._instances[cls]\n\n\nclass Protect(metaclass=Singleton):\n    \"\"\"保护类，风控保护机制。\n\n    使用单例模式确保全局只有一个实例。\n\n    Attributes:\n        login_stat_path (str): 登录状态文件的路径\n        login_stat (dict): 登录状态信息\n        login_time (int): 最后登录时间戳\n        login_device_id (str): 最后登录的设备ID\n    \"\"\"\n\n    def __init__(self):\n        \"\"\"初始化保护类实例。\n\n        创建或加载登录状态文件，初始化登录时间和设备ID。\n        \"\"\"\n        self.login_stat_path = os.path.join(os.path.dirname(__file__), \"login_stat.json\")\n\n        if not os.path.exists(self.login_stat_path):\n            default_config = {\n                \"login_time\": 0,\n                \"device_id\": \"\"\n            }\n            with open(self.login_stat_path, \"w\", encoding=\"utf-8\") as f:\n                f.write(json.dumps(default_config, indent=4, ensure_ascii=False))\n            self.login_stat = default_config\n        else:\n            with open(self.login_stat_path, \"r\", encoding=\"utf-8\") as f:\n                self.login_stat = json.loads(f.read())\n\n        self.login_time = self.login_stat.get(\"login_time\", 0)\n        self.login_device_id = self.login_stat.get(\"device_id\", \"\")\n\n    def check(self, second: int) -> bool:\n        \"\"\"检查是否在指定时间内，风控保护。\n\n        Args:\n            second (int): 指定的秒数\n\n        Returns:\n            bool: 如果当前时间与上次登录时间的差小于指定秒数，返回True；否则返回False\n        \"\"\"\n        now = datetime.now().timestamp()\n        return now - self.login_time < second\n\n    def update_login_status(self, device_id: str = \"\"):\n        \"\"\"更新登录状态。\n\n        如果设备ID发生变化，更新登录时间和设备ID，并保存到文件。\n\n        Args:\n            device_id (str, optional): 设备ID. Defaults to \"\".\n        \"\"\"\n        if device_id == self.login_device_id:\n            return\n        self.login_time = int(datetime.now().timestamp())\n        self.login_stat[\"login_time\"] = self.login_time\n        self.login_stat[\"device_id\"] = device_id\n        with open(self.login_stat_path, \"w\", encoding=\"utf-8\") as f:\n            f.write(json.dumps(self.login_stat, indent=4, ensure_ascii=False))\n\n\nprotector = Protect()\n"
  },
  {
    "path": "WechatAPI/Client/tool.py",
    "content": "import base64\nimport io\nimport os\n\nimport aiohttp\nimport pysilk\nfrom pydub import AudioSegment\n\nfrom .base import *\nfrom .protect import protector\nfrom ..errors import *\n\n\nclass ToolMixin(WechatAPIClientBase):\n    async def download_image(self, aeskey: str, cdnmidimgurl: str) -> str:\n        \"\"\"CDN下载高清图片。\n\n        Args:\n            aeskey (str): 图片的AES密钥\n            cdnmidimgurl (str): 图片的CDN URL\n\n        Returns:\n            str: 图片的base64编码字符串\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            根据error_handler处理错误\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"AesKey\": aeskey, \"Cdnmidimgurl\": cdnmidimgurl}\n            response = await session.post(f'http://{self.ip}:{self.port}/CdnDownloadImg', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return json_resp.get(\"Data\")\n            else:\n                self.error_handler(json_resp)\n\n    async def download_voice(self, msg_id: str, voiceurl: str, length: int) -> str:\n        \"\"\"下载语音文件。\n\n        Args:\n            msg_id (str): 消息的msgid\n            voiceurl (str): 语音的url，从xml获取\n            length (int): 语音长度，从xml获取\n\n        Returns:\n            str: 语音的base64编码字符串\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            根据error_handler处理错误\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"MsgId\": msg_id, \"Voiceurl\": voiceurl, \"Length\": length}\n            response = await session.post(f'http://{self.ip}:{self.port}/DownloadVoice', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return json_resp.get(\"Data\").get(\"data\").get(\"buffer\")\n            else:\n                self.error_handler(json_resp)\n\n    async def download_attach(self, attach_id: str) -> dict:\n        \"\"\"下载附件。\n\n        Args:\n            attach_id (str): 附件ID\n\n        Returns:\n            dict: 附件数据\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            根据error_handler处理错误\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"AttachId\": attach_id}\n            response = await session.post(f'http://{self.ip}:{self.port}/DownloadAttach', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return json_resp.get(\"Data\").get(\"data\").get(\"buffer\")\n            else:\n                self.error_handler(json_resp)\n\n    async def download_video(self, msg_id) -> str:\n        \"\"\"下载视频。\n\n        Args:\n            msg_id (str): 消息的msg_id\n\n        Returns:\n            str: 视频的base64编码字符串\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            根据error_handler处理错误\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"MsgId\": msg_id}\n            response = await session.post(f'http://{self.ip}:{self.port}/DownloadVideo', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return json_resp.get(\"Data\").get(\"data\").get(\"buffer\")\n            else:\n                self.error_handler(json_resp)\n\n    async def set_step(self, count: int) -> bool:\n        \"\"\"设置步数。\n\n        Args:\n            count (int): 要设置的步数\n\n        Returns:\n            bool: 成功返回True，失败返回False\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            BanProtection: 风控保护: 新设备登录后4小时内请挂机\n            根据error_handler处理错误\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif not self.ignore_protect and protector.check(14400):\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"StepCount\": count}\n            response = await session.post(f'http://{self.ip}:{self.port}/SetStep', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return True\n            else:\n                self.error_handler(json_resp)\n\n    async def set_proxy(self, proxy: Proxy) -> bool:\n        \"\"\"设置代理。\n\n        Args:\n            proxy (Proxy): 代理配置对象\n\n        Returns:\n            bool: 成功返回True，失败返回False\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            根据error_handler处理错误\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid,\n                          \"Proxy\": {\"ProxyIp\": f\"{proxy.ip}:{proxy.port}\",\n                                    \"ProxyUser\": proxy.username,\n                                    \"ProxyPassword\": proxy.password}}\n            response = await session.post(f'http://{self.ip}:{self.port}/SetProxy', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return True\n            else:\n                self.error_handler(json_resp)\n\n    async def check_database(self) -> bool:\n        \"\"\"检查数据库状态。\n\n        Returns:\n            bool: 数据库正常返回True，否则返回False\n        \"\"\"\n        async with aiohttp.ClientSession() as session:\n            response = await session.get(f'http://{self.ip}:{self.port}/CheckDatabaseOK')\n            json_resp = await response.json()\n\n            if json_resp.get(\"Running\"):\n                return True\n            else:\n                return False\n\n    @staticmethod\n    def base64_to_file(base64_str: str, file_name: str, file_path: str) -> bool:\n        \"\"\"将base64字符串转换为文件并保存。\n\n        Args:\n            base64_str (str): base64编码的字符串\n            file_name (str): 要保存的文件名\n            file_path (str): 文件保存路径\n\n        Returns:\n            bool: 转换成功返回True，失败返回False\n        \"\"\"\n        try:\n            os.makedirs(file_path, exist_ok=True)\n\n            # 拼接完整的文件路径\n            full_path = os.path.join(file_path, file_name)\n\n            # 移除可能存在的 base64 头部信息\n            if ',' in base64_str:\n                base64_str = base64_str.split(',')[1]\n\n            # 解码 base64 并写入文件\n            with open(full_path, 'wb') as f:\n                f.write(base64.b64decode(base64_str))\n\n            return True\n\n        except Exception as e:\n            return False\n\n    @staticmethod\n    def file_to_base64(file_path: str) -> str:\n        \"\"\"将文件转换为base64字符串。\n\n        Args:\n            file_path (str): 文件路径\n\n        Returns:\n            str: base64编码的字符串\n        \"\"\"\n        with open(file_path, 'rb') as f:\n            return base64.b64encode(f.read()).decode()\n\n    @staticmethod\n    def base64_to_byte(base64_str: str) -> bytes:\n        \"\"\"将base64字符串转换为bytes。\n\n        Args:\n            base64_str (str): base64编码的字符串\n\n        Returns:\n            bytes: 解码后的字节数据\n        \"\"\"\n        # 移除可能存在的 base64 头部信息\n        if ',' in base64_str:\n            base64_str = base64_str.split(',')[1]\n\n        return base64.b64decode(base64_str)\n\n    @staticmethod\n    def byte_to_base64(byte: bytes) -> str:\n        \"\"\"将bytes转换为base64字符串。\n\n        Args:\n            byte (bytes): 字节数据\n\n        Returns:\n            str: base64编码的字符串\n        \"\"\"\n        return base64.b64encode(byte).decode(\"utf-8\")\n\n    @staticmethod\n    async def silk_byte_to_byte_wav_byte(silk_byte: bytes) -> bytes:\n        \"\"\"将silk字节转换为wav字节。\n\n        Args:\n            silk_byte (bytes): silk格式的字节数据\n\n        Returns:\n            bytes: wav格式的字节数据\n        \"\"\"\n        return await pysilk.async_decode(silk_byte, to_wav=True)\n\n    @staticmethod\n    def wav_byte_to_amr_byte(wav_byte: bytes) -> bytes:\n        \"\"\"将WAV字节数据转换为AMR格式。\n\n        Args:\n            wav_byte (bytes): WAV格式的字节数据\n\n        Returns:\n            bytes: AMR格式的字节数据\n\n        Raises:\n            Exception: 转换失败时抛出异常\n        \"\"\"\n        try:\n            # 从字节数据创建 AudioSegment 对象\n            audio = AudioSegment.from_wav(io.BytesIO(wav_byte))\n\n            # 设置 AMR 编码的标准参数\n            audio = audio.set_frame_rate(8000).set_channels(1)\n\n            # 创建一个字节缓冲区来存储 AMR 数据\n            output = io.BytesIO()\n\n            # 导出为 AMR 格式\n            audio.export(output, format=\"amr\")\n\n            # 获取字节数据\n            return output.getvalue()\n\n        except Exception as e:\n            raise Exception(f\"转换WAV到AMR失败: {str(e)}\")\n\n    @staticmethod\n    def wav_byte_to_amr_base64(wav_byte: bytes) -> str:\n        \"\"\"将WAV字节数据转换为AMR格式的base64字符串。\n\n        Args:\n            wav_byte (bytes): WAV格式的字节数据\n\n        Returns:\n            str: AMR格式的base64编码字符串\n        \"\"\"\n        return base64.b64encode(ToolMixin.wav_byte_to_amr_byte(wav_byte)).decode()\n\n    @staticmethod\n    async def wav_byte_to_silk_byte(wav_byte: bytes) -> bytes:\n        \"\"\"将WAV字节数据转换为silk格式。\n\n        Args:\n            wav_byte (bytes): WAV格式的字节数据\n\n        Returns:\n            bytes: silk格式的字节数据\n        \"\"\"\n        # get pcm data\n        audio = AudioSegment.from_wav(io.BytesIO(wav_byte))\n        pcm = audio.raw_data\n        return await pysilk.async_encode(pcm, data_rate=audio.frame_rate, sample_rate=audio.frame_rate)\n\n    @staticmethod\n    async def wav_byte_to_silk_base64(wav_byte: bytes) -> str:\n        \"\"\"将WAV字节数据转换为silk格式的base64字符串。\n\n        Args:\n            wav_byte (bytes): WAV格式的字节数据\n\n        Returns:\n            str: silk格式的base64编码字符串\n        \"\"\"\n        return base64.b64encode(await ToolMixin.wav_byte_to_silk_byte(wav_byte)).decode()\n\n    @staticmethod\n    async def silk_base64_to_wav_byte(silk_base64: str) -> bytes:\n        \"\"\"将silk格式的base64字符串转换为WAV字节数据。\n\n        Args:\n            silk_base64 (str): silk格式的base64编码字符串\n\n        Returns:\n            bytes: WAV格式的字节数据\n        \"\"\"\n        return await ToolMixin.silk_byte_to_byte_wav_byte(base64.b64decode(silk_base64))"
  },
  {
    "path": "WechatAPI/Client/user.py",
    "content": "import aiohttp\n\nfrom .base import *\nfrom .protect import protector\nfrom ..errors import *\n\n\nclass UserMixin(WechatAPIClientBase):\n    async def get_profile(self, wxid: str = None) -> dict:\n        \"\"\"获取用户信息。\n\n        Args:\n            wxid (str, optional): 用户wxid. Defaults to None.\n\n        Returns:\n            dict: 用户信息字典\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            根据error_handler处理错误\n        \"\"\"\n        if not self.wxid and not wxid:\n            raise UserLoggedOut(\"请先登录\")\n\n        if not wxid:\n            wxid = self.wxid\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": wxid}\n            response = await session.post(f'http://{self.ip}:{self.port}/GetProfile', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return json_resp.get(\"Data\").get(\"userInfo\")\n            else:\n                self.error_handler(json_resp)\n\n    async def get_my_qrcode(self, style: int = 0) -> str:\n        \"\"\"获取个人二维码。\n\n        Args:\n            style (int, optional): 二维码样式. Defaults to 0.\n\n        Returns:\n            str: 图片的base64编码字符串\n\n        Raises:\n            UserLoggedOut: 未登录时调用\n            BanProtection: 风控保护: 新设备登录后4小时内请挂机\n            根据error_handler处理错误\n        \"\"\"\n        if not self.wxid:\n            raise UserLoggedOut(\"请先登录\")\n        elif protector.check(14400) and not self.ignore_protect:\n            raise BanProtection(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        async with aiohttp.ClientSession() as session:\n            json_param = {\"Wxid\": self.wxid, \"Style\": style}\n            response = await session.post(f'http://{self.ip}:{self.port}/GetMyQRCode', json=json_param)\n            json_resp = await response.json()\n\n            if json_resp.get(\"Success\"):\n                return json_resp.get(\"Data\").get(\"qrcode\").get(\"buffer\")\n            else:\n                self.error_handler(json_resp)\n\n    async def is_logged_in(self, wxid: str = None) -> bool:\n        \"\"\"检查是否登录。\n\n        Args:\n            wxid (str, optional): 用户wxid. Defaults to None.\n\n        Returns:\n            bool: 已登录返回True，未登录返回False\n        \"\"\"\n        if not wxid:\n            wxid = self.wxid\n        try:\n            await self.get_profile(wxid)\n            return True\n        except:\n            return False"
  },
  {
    "path": "WechatAPI/Server/WechatAPIServer.py",
    "content": "import asyncio\nimport os\nimport pathlib\n\nimport xywechatpad_binary\nfrom loguru import logger\n\n\nclass WechatAPIServer:\n    def __init__(self):\n        self.executable_path = xywechatpad_binary.copy_binary(pathlib.Path(__file__).parent.parent / \"core\")\n        self.executable_path = self.executable_path.absolute()\n\n        self.server_task = None\n        self.log_task = None\n        self.process = None\n\n    async def start(self, port=9000, mode=\"release\", redis_host=\"127.0.0.1\",\n                    redis_port=6379, redis_password=\"\", redis_db=0):\n        \"\"\"异步启动服务\"\"\"\n        command = [\n            self.executable_path,\n            \"-p\", str(port),\n            \"-m\", mode,\n            \"-rh\", redis_host,\n            \"-rp\", str(redis_port),\n            \"-rpwd\", redis_password,\n            \"-rdb\", str(redis_db)\n        ]\n\n        # 使用异步创建子进程\n        self.process = await asyncio.create_subprocess_exec(\n            *command,\n            cwd=os.path.dirname(os.path.abspath(__file__)),\n            stdout=asyncio.subprocess.PIPE,\n            stderr=asyncio.subprocess.PIPE\n        )\n\n        # 启动日志监控任务\n        self.log_task = asyncio.create_task(self.process_log())\n\n    async def stop(self):\n        \"\"\"异步停止服务\"\"\"\n        if hasattr(self, 'process'):\n            try:\n                if not self.log_task.done():\n                    self.log_task.cancel()\n                await asyncio.gather(self.log_task, return_exceptions=True)\n                self.log_task = None\n\n                self.process.terminate()\n                await self.process.wait()\n            except ProcessLookupError:\n                logger.warning(\"尝试终止已退出的进程\")\n\n    async def process_log(self):\n        \"\"\"处理子进程的日志输出\"\"\"\n        try:\n            # 创建两个独立的任务来分别处理stdout和stderr\n            stdout_task = asyncio.create_task(self._read_stream(self.process.stdout, \"info\"))\n            stderr_task = asyncio.create_task(self._read_stream(self.process.stderr, \"error\"))\n\n            # 等待两个任务中的任何一个完成\n            codes = await asyncio.gather(stdout_task, stderr_task, return_exceptions=True)\n            logger.info(f\"WechatAPI已退出，返回码: {codes[0]}\")\n\n        except asyncio.CancelledError:\n            pass\n        except Exception as e:\n            logger.error(f\"处理日志时发生错误: {e}\")\n\n    async def _read_stream(self, stream, log_level):\n        \"\"\"读取流并记录日志\"\"\"\n        while True:\n            line = await stream.readline()\n            if not line:  # EOF\n                if self.process.returncode is not None:\n                    return self.process.returncode\n                await asyncio.sleep(0.1)\n                continue\n\n            text = line.decode('utf-8', errors='replace').strip()\n            if text:\n                if log_level == \"info\":\n                    logger.log(\"API\", text)\n                else:\n                    logger.log(\"API\", text)\n        return self.process.returncode\n\n\nwechat_api_server = WechatAPIServer()\n"
  },
  {
    "path": "WechatAPI/Server/__init__.py",
    "content": ""
  },
  {
    "path": "WechatAPI/__init__.py",
    "content": "from WechatAPI.Server.WechatAPIServer import *\nfrom WechatAPI.Client import *\nfrom WechatAPI.errors import *\n\n__name__ = \"WechatAPI\"\n__version__ = \"1.0.0\"\n__description__ = \"Wechat API for XYBot\"\n__author__ = \"HenryXiaoYang\""
  },
  {
    "path": "WechatAPI/core/placeholder",
    "content": ""
  },
  {
    "path": "WechatAPI/errors.py",
    "content": "class MarshallingError(Exception):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass UnmarshallingError(Exception):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass MMTLSError(Exception):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass PacketError(Exception):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass ParsePacketError(Exception):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass DatabaseError(Exception):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass LoginError(Exception):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass UserLoggedOut(Exception):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n\nclass BanProtection(Exception):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n"
  },
  {
    "path": "WechatAPIDocs/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line, and also\n# from the environment for the first two.\nSPHINXOPTS    ?=\nSPHINXBUILD   ?= sphinx-build\nSOURCEDIR     = .\nBUILDDIR      = _build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\n.PHONY: help Makefile\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n"
  },
  {
    "path": "WechatAPIDocs/_templates/custom-toc.html",
    "content": "<div class=\"sidebar-tree\">\n    <p class=\"caption\"><span class=\"caption-text\">重要函数导航</span></p>\n    <ul>\n        {% for category, functions in important_functions.items() %}\n        <li class=\"toctree-l1\">\n            <p class=\"caption\"><span class=\"caption-text\">{{ category }}</span></p>\n            <ul>\n                {% for title, ref in functions %}\n                <li class=\"toctree-l2\">\n                    <a class=\"reference internal\" href=\"{{ ref | safe }}\">{{ title }}</a>\n                </li>\n                {% endfor %}\n            </ul>\n        </li>\n        {% endfor %}\n    </ul>\n</div> "
  },
  {
    "path": "WechatAPIDocs/build.sh",
    "content": "#!/bin/bash\ncd \"$(dirname \"$0\")\" || exit\nmake clean\nmake html\n\nrm -rf ../docs/WechatAPIClient/*\ncp -r _build/html/* ../docs/WechatAPIClient || exit"
  },
  {
    "path": "WechatAPIDocs/conf.py",
    "content": "# Configuration file for the Sphinx documentation builder.\n#\n# For the full list of built-in configuration values, see the documentation:\n# https://www.sphinx-doc.org/en/master/usage/configuration.html\n\n# -- Project information -----------------------------------------------------\n# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information\n\nimport os\nimport sys\nimport sphinx_rtd_theme\n\nsys.path.insert(0, os.path.abspath('..'))\n\nhtml_theme = 'furo'\n\nproject = 'WechatAPI'\ncopyright = '2025, HenryXiaoYang'\nauthor = 'HenryXiaoYang'\n\n# -- General configuration ---------------------------------------------------\n# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration\n\nextensions = [\n    'sphinx.ext.autodoc',\n    'sphinx.ext.napoleon',  # 支持Google风格的文档字符串\n    'sphinx.ext.viewcode'\n]\n\ntemplates_path = ['_templates']\nexclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']\n\nlanguage = 'zh_CN'\n\n# Napoleon设置\nnapoleon_google_docstring = True\nnapoleon_numpy_docstring = False\nnapoleon_include_init_with_doc = True\nnapoleon_include_private_with_doc = True\nnapoleon_include_special_with_doc = True\nnapoleon_use_admonition_for_examples = True\nnapoleon_use_admonition_for_notes = True\nnapoleon_use_admonition_for_references = True\nnapoleon_use_ivar = True\nnapoleon_use_param = True\nnapoleon_use_rtype = True\nnapoleon_type_aliases = None\n\nhtml_title = \"XYBotV2\"\nhtml_sidebars = {\n    \"**\": [\n        \"sidebar/scroll-start.html\",\n        \"sidebar/brand.html\",\n        \"sidebar/search.html\",\n        \"sidebar/navigation.html\",\n        \"custom-toc.html\",\n        \"sidebar/scroll-end.html\"\n    ]\n}\n\n# furo主题配置\nhtml_theme_options = {\n    \"sidebar_hide_name\": False,\n    \"light_css_variables\": {\n        \"color-brand-primary\": \"#2962ff\",\n        \"color-brand-content\": \"#2962ff\",\n    },\n    \"navigation_with_keys\": True,\n}\n\n# 重要函数导航列表\nimportant_functions = {\n    \"登录\": [\n        (\"检查WechatAPI是否在运行\", \"index.html#WechatAPI.Client.login.LoginMixin.is_running\"),\n        (\"获取登录二维码\", \"index.html#WechatAPI.Client.login.LoginMixin.get_qr_code\"),\n        (\"二次登录(唤醒登录)\", \"index.html#WechatAPI.Client.login.LoginMixin.awaken_login\"),\n        (\"检查登录的UUID状态\", \"index.html#WechatAPI.Client.login.LoginMixin.check_login_uuid\"),\n        (\"获取登录缓存信息\", \"index.html#WechatAPI.Client.login.LoginMixin.get_cached_info\"),\n        (\"登出当前账号\", \"index.html#WechatAPI.Client.login.LoginMixin.log_out\"),\n        (\"发送心跳包\", \"index.html#WechatAPI.Client.login.LoginMixin.heartbeat\"),\n        (\"开始自动心跳\", \"index.html#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\"),\n        (\"停止自动心跳\", \"index.html#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\"),\n        (\"获取自动心跳状态\", \"index.html#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\")\n    ],\n    \"消息\": [\n        (\"同步消息\", \"index.html#WechatAPI.Client.message.MessageMixin.sync_message\"),\n        (\"发送文本消息\", \"index.html#WechatAPI.Client.message.MessageMixin.send_text_message\"),\n        (\"发送图片消息\", \"index.html#WechatAPI.Client.message.MessageMixin.send_image_message\"),\n        (\"发送语音消息\", \"index.html#WechatAPI.Client.message.MessageMixin.send_voice_message\"),\n        (\"发送视频消息\", \"index.html#WechatAPI.Client.message.MessageMixin.send_video_message\"),\n        (\"发送链接消息\", \"index.html#WechatAPI.Client.message.MessageMixin.send_link_message\"),\n        (\"发送名片消息\", \"index.html#WechatAPI.Client.message.MessageMixin.send_card_message\"),\n        (\"发送应用(xml)消息\", \"index.html#WechatAPI.Client.message.MessageMixin.send_app_message\"),\n        (\"发送表情消息\", \"index.html#WechatAPI.Client.message.MessageMixin.send_emoji_message\"),\n        (\"转发图片消息\", \"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\"),\n        (\"转发视频消息\", \"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\"),\n        (\"转发文件消息\", \"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\"),\n        (\"撤回消息\", \"index.html#WechatAPI.Client.message.MessageMixin.revoke_message\")\n    ],\n    \"用户\": [\n        (\"获取个人二维码\", \"index.html#WechatAPI.Client.user.UserMixin.get_my_qrcode\"),\n        (\"获取用户信息\", \"index.html#WechatAPI.Client.user.UserMixin.get_profile\"),\n        (\"检查是否登录\", \"index.html#WechatAPI.Client.user.UserMixin.is_logged_in\")\n    ],\n    \"群聊\": [\n        (\"获取群聊信息\", \"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\"),\n        (\"获取群聊公告\", \"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\"),\n        (\"获取群聊成员列表\", \"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\"),\n        (\"获取群聊二维码\", \"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\"),\n        (\"添加群成员(群聊最多40人)\", \"index.html#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\"),\n        (\"邀请群聊成员(群聊大于40人)\", \"index.html#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\")\n    ],\n    \"好友\": [\n        (\"获取联系人信息\", \"index.html#WechatAPI.Client.friend.FriendMixin.get_contact\"),\n        (\"获取联系人详情\", \"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_detail\"),\n        (\"获取联系人列表\", \"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_list\"),\n        (\"获取用户昵称\", \"index.html#WechatAPI.Client.friend.FriendMixin.get_nickname\"),\n        (\"接受好友请求\", \"index.html#WechatAPI.Client.friend.FriendMixin.accept_friend\"),\n    ],\n    \"红包\": [\n        (\"获取红包详情\", \"index.html#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\")\n    ],\n    \"工具\": [\n        (\"检查数据库状态\", \"index.html#WechatAPI.Client.tool.ToolMixin.check_database\"),\n        (\"设置步数\", \"index.html#WechatAPI.Client.tool.ToolMixin.set_step\"),\n        (\"下载高清图片\", \"index.html#WechatAPI.Client.tool.ToolMixin.download_image\"),\n        (\"下载视频\", \"index.html#WechatAPI.Client.tool.ToolMixin.download_video\"),\n        (\"下载语音文件\", \"index.html#WechatAPI.Client.tool.ToolMixin.download_voice\"),\n        (\"下载附件\", \"index.html#WechatAPI.Client.tool.ToolMixin.download_attach\"),\n        (\"base64转字节\", \"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_byte\"),\n        (\"base64转文件\", \"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_file\"),\n        (\"字节转base64\", \"index.html#WechatAPI.Client.tool.ToolMixin.byte_to_base64\"),\n        (\"文件转base64\", \"index.html#WechatAPI.Client.tool.ToolMixin.file_to_base64\"),\n        (\"silk的base64转wav字节\", \"index.html#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\"),\n        (\"silk字节转wav字节\", \"index.html#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\"),\n        (\"WAV字节转AMR的base64\", \"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\"),\n        (\"WAV字节转AMR字节\", \"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\"),\n        (\"WAV字节转silk的base64\", \"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\"),\n        (\"WAV字节转silk字节\", \"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\"),\n    ]\n}\n\n# 添加 HTML 上下文\nhtml_context = {\n    'important_functions': important_functions\n}\n"
  },
  {
    "path": "WechatAPIDocs/index.rst",
    "content": "WechatAPIClient\n------------------------------\n\n基础\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.base\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n登录\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.login\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n消息\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.message\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n用户\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.user\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n群聊\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.chatroom\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n好友\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.friend\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n红包\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.hongbao\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n保护\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.protect\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n工具\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.tool\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n索引\n====\n\n* :ref:`search`\n\n\n"
  },
  {
    "path": "WechatAPIDocs/make.bat",
    "content": "@ECHO OFF\n\npushd %~dp0\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset SOURCEDIR=.\nset BUILDDIR=_build\n\n%SPHINXBUILD% >NUL 2>NUL\nif errorlevel 9009 (\n\techo.\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\n\techo.installed, then set the SPHINXBUILD environment variable to point\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\n\techo.may add the Sphinx directory to PATH.\n\techo.\n\techo.If you don't have Sphinx installed, grab it from\n\techo.https://www.sphinx-doc.org/\n\texit /b 1\n)\n\nif \"%1\" == \"\" goto help\n\n%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\ngoto end\n\n:help\n%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%\n\n:end\npopd\n"
  },
  {
    "path": "WechatAPIDocs/requirements.txt",
    "content": "sphinx~=8.1.3\nsphinx-rtd-theme~=3.0.2\nfuro~=2024.8.6"
  },
  {
    "path": "XYBot_Dify_Template.yml",
    "content": "app:\n  description: XYBot微信机器人Dify插件模版\n  icon: 🤖\n  icon_background: '#FFEAD5'\n  mode: advanced-chat\n  name: XYBot\n  use_icon_as_answer_icon: false\nkind: app\nversion: 0.1.5\nworkflow:\n  conversation_variables: [ ]\n  environment_variables: [ ]\n  features:\n    file_upload:\n      allowed_file_extensions: [ ]\n      allowed_file_types:\n        - image\n      allowed_file_upload_methods:\n        - remote_url\n        - local_file\n      enabled: true\n      fileUploadConfig:\n        audio_file_size_limit: 50\n        batch_count_limit: 5\n        file_size_limit: 15\n        image_file_size_limit: 10\n        video_file_size_limit: 100\n        workflow_file_upload_limit: 10\n      image:\n        enabled: false\n        number_limits: 3\n        transfer_methods:\n          - local_file\n          - remote_url\n      number_limits: 1\n    opening_statement: ''\n    retriever_resource:\n      enabled: true\n    sensitive_word_avoidance:\n      enabled: false\n    speech_to_text:\n      enabled: true\n    suggested_questions: [ ]\n    suggested_questions_after_answer:\n      enabled: false\n    text_to_speech:\n      enabled: true\n      language: zh-Hans\n      voice: ''\n  graph:\n    edges:\n      - data:\n          isInIteration: false\n          sourceType: llm\n          targetType: answer\n        id: 1738917745853-source-1738918123165-target\n        source: '1738917745853'\n        sourceHandle: source\n        target: '1738918123165'\n        targetHandle: target\n        type: custom\n        zIndex: 0\n      - data:\n          isInIteration: false\n          sourceType: start\n          targetType: if-else\n        id: 1738915019767-source-1739252680159-target\n        source: '1738915019767'\n        sourceHandle: source\n        target: '1739252680159'\n        targetHandle: target\n        type: custom\n        zIndex: 0\n      - data:\n          isInIteration: false\n          sourceType: if-else\n          targetType: llm\n        id: 1739252680159-3a9541e0-b608-4bb5-a027-def256809e7a-1739252840296-target\n        source: '1739252680159'\n        sourceHandle: 3a9541e0-b608-4bb5-a027-def256809e7a\n        target: '1739252840296'\n        targetHandle: target\n        type: custom\n        zIndex: 0\n      - data:\n          isInIteration: false\n          sourceType: llm\n          targetType: answer\n        id: 1739252840296-source-1739252888579-target\n        source: '1739252840296'\n        sourceHandle: source\n        target: '1739252888579'\n        targetHandle: target\n        type: custom\n        zIndex: 0\n      - data:\n          isInIteration: false\n          sourceType: list-operator\n          targetType: tool\n        id: 1739254087701-source-1739252789163-target\n        source: '1739254087701'\n        sourceHandle: source\n        target: '1739252789163'\n        targetHandle: target\n        type: custom\n        zIndex: 0\n      - data:\n          isInIteration: false\n          sourceType: if-else\n          targetType: list-operator\n        id: 1739252680159-458858e2-e643-442d-9bfd-2ef164189378-1739254087701-target\n        source: '1739252680159'\n        sourceHandle: 458858e2-e643-442d-9bfd-2ef164189378\n        target: '1739254087701'\n        targetHandle: target\n        type: custom\n        zIndex: 0\n      - data:\n          isInIteration: false\n          sourceType: if-else\n          targetType: llm\n        id: 1739252680159-true-1738917745853-target\n        source: '1739252680159'\n        sourceHandle: 'true'\n        target: '1738917745853'\n        targetHandle: target\n        type: custom\n        zIndex: 0\n      - data:\n          isInIteration: false\n          sourceType: tool\n          targetType: llm\n        id: 1739252789163-source-1739255059499-target\n        source: '1739252789163'\n        sourceHandle: source\n        target: '1739255059499'\n        targetHandle: target\n        type: custom\n        zIndex: 0\n      - data:\n          isInIteration: false\n          sourceType: llm\n          targetType: tool\n        id: 1739255059499-source-1739257224364-target\n        source: '1739255059499'\n        sourceHandle: source\n        target: '1739257224364'\n        targetHandle: target\n        type: custom\n        zIndex: 0\n      - data:\n          isInIteration: false\n          sourceType: tool\n          targetType: answer\n        id: 1739257224364-source-1739288133732-target\n        source: '1739257224364'\n        sourceHandle: source\n        target: '1739288133732'\n        targetHandle: target\n        type: custom\n        zIndex: 0\n    nodes:\n      - data:\n          desc: ''\n          selected: false\n          title: 开始\n          type: start\n          variables: [ ]\n        height: 54\n        id: '1738915019767'\n        position:\n          x: -262.276922445997\n          y: 406.16303131970574\n        positionAbsolute:\n          x: -262.276922445997\n          y: 406.16303131970574\n        selected: false\n        sourcePosition: right\n        targetPosition: left\n        type: custom\n        width: 244\n      - data:\n          context:\n            enabled: false\n            variable_selector: [ ]\n          desc: ''\n          memory:\n            query_prompt_template: '{{#sys.query#}}\n\n            '\n            role_prefix:\n              assistant: ''\n              user: ''\n            window:\n              enabled: false\n              size: 50\n          model:\n            completion_params:\n              max_tokens: 4096\n              temperature: 0.7\n            mode: chat\n            name: gpt-4o-mini\n            provider: openai\n          prompt_template:\n            - edition_type: basic\n              id: 6de35274-53f5-4e34-89c5-a9c30ffb5f64\n              role: system\n              text: '你是一个友好有用的助理，你的名字叫XYBot。你会用简洁、专业的方式回答问题。\n\n            - 用户用什么语言问，就用什么语言回答\n\n            - 对于代码相关问题，请提供清晰的示例和解释'\n          selected: false\n          title: LLM1\n          type: llm\n          variables: [ ]\n          vision:\n            configs:\n              detail: low\n              variable_selector:\n                - sys\n                - files\n            enabled: false\n        height: 98\n        id: '1738917745853'\n        position:\n          x: 500.2723150319407\n          y: 356.991212440781\n        positionAbsolute:\n          x: 500.2723150319407\n          y: 356.991212440781\n        selected: false\n        sourcePosition: right\n        targetPosition: left\n        type: custom\n        width: 244\n      - data:\n          answer: '{{#1738917745853.text#}}'\n          desc: ''\n          selected: false\n          title: 直接回复\n          type: answer\n          variables: [ ]\n        height: 103\n        id: '1738918123165'\n        position:\n          x: 829.4029907903282\n          y: 356.991212440781\n        positionAbsolute:\n          x: 829.4029907903282\n          y: 356.991212440781\n        selected: false\n        sourcePosition: right\n        targetPosition: left\n        type: custom\n        width: 244\n      - data:\n          cases:\n            - case_id: 'true'\n              conditions:\n                - comparison_operator: empty\n                  id: a6c51713-13ff-4c59-92ad-df1af61aa759\n                  value: ''\n                  varType: array[file]\n                  variable_selector:\n                    - sys\n                    - files\n              id: 'true'\n              logical_operator: or\n            - case_id: 3a9541e0-b608-4bb5-a027-def256809e7a\n              conditions:\n                - comparison_operator: contains\n                  id: dda26e42-905b-4503-9e7e-aefe52b20e7e\n                  sub_variable_condition:\n                    case_id: a3472907-6671-4ff6-a40f-420943e154f3\n                    conditions:\n                      - comparison_operator: in\n                        id: f2c55058-eabf-4fde-a2da-e6c07e64246c\n                        key: type\n                        value:\n                          - image\n                        varType: string\n                    logical_operator: and\n                  value: ''\n                  varType: array[file]\n                  variable_selector:\n                    - sys\n                    - files\n              id: 3a9541e0-b608-4bb5-a027-def256809e7a\n              logical_operator: and\n            - case_id: 458858e2-e643-442d-9bfd-2ef164189378\n              conditions:\n                - comparison_operator: contains\n                  id: d5b09e7e-a8ea-4018-8650-947d6d9b0ca1\n                  sub_variable_condition:\n                    case_id: 1e38440a-5660-41d5-9be3-7cfafa0a8b6a\n                    conditions:\n                      - comparison_operator: in\n                        id: 864efcf6-b80a-4c3b-8842-b3c7c66074cb\n                        key: type\n                        value:\n                          - audio\n                        varType: string\n                    logical_operator: and\n                  value: ''\n                  varType: array[file]\n                  variable_selector:\n                    - sys\n                    - files\n              id: 458858e2-e643-442d-9bfd-2ef164189378\n              logical_operator: and\n            - case_id: c47afde9-f052-4d4d-9efe-f498b3f94a80\n              conditions:\n                - comparison_operator: contains\n                  id: 1a5eab00-9ed2-488c-8eb3-99d902c33ac3\n                  sub_variable_condition:\n                    case_id: b4eb1a99-7840-4d62-bffe-e40a073db948\n                    conditions:\n                      - comparison_operator: in\n                        id: 280c0d9a-7e77-4c69-902c-36e9ccc466be\n                        key: type\n                        value:\n                          - document\n                        varType: string\n                    logical_operator: and\n                  value: ''\n                  varType: array[file]\n                  variable_selector:\n                    - sys\n                    - files\n              logical_operator: and\n            - case_id: 5e77a038-54bc-45a9-b99f-1606c8d13d6a\n              conditions:\n                - comparison_operator: contains\n                  id: 33cb4ff7-f570-46b8-a358-2dc44e735754\n                  sub_variable_condition:\n                    case_id: 84bf66f6-5445-46ce-b036-90207abb4a98\n                    conditions:\n                      - comparison_operator: in\n                        id: dc9f8cb7-668b-4545-b924-f58e6bbd4436\n                        key: type\n                        value:\n                          - video\n                        varType: string\n                    logical_operator: and\n                  value: ''\n                  varType: array[file]\n                  variable_selector:\n                    - sys\n                    - files\n              logical_operator: and\n          desc: ''\n          selected: false\n          title: 条件分支\n          type: if-else\n        height: 414\n        id: '1739252680159'\n        position:\n          x: 92.72226561832565\n          y: 406.16303131970574\n        positionAbsolute:\n          x: 92.72226561832565\n          y: 406.16303131970574\n        selected: false\n        sourcePosition: right\n        targetPosition: left\n        type: custom\n        width: 244\n      - data:\n          desc: ''\n          provider_id: audio\n          provider_name: audio\n          provider_type: builtin\n          selected: false\n          title: Speech To Text\n          tool_configurations:\n            model: openai_api_compatible#step-asr\n          tool_label: Speech To Text\n          tool_name: asr\n          tool_parameters:\n            audio_file:\n              type: variable\n              value:\n                - '1739254087701'\n                - first_record\n          type: tool\n        height: 90\n        id: '1739252789163'\n        position:\n          x: 818.0854081792563\n          y: 637.9065614866665\n        positionAbsolute:\n          x: 818.0854081792563\n          y: 637.9065614866665\n        selected: false\n        sourcePosition: right\n        targetPosition: left\n        type: custom\n        width: 244\n      - data:\n          context:\n            enabled: false\n            variable_selector: [ ]\n          desc: ''\n          memory:\n            query_prompt_template: '{{#sys.query#}}'\n            role_prefix:\n              assistant: ''\n              user: ''\n            window:\n              enabled: false\n              size: 50\n          model:\n            completion_params:\n              temperature: 0.7\n            mode: chat\n            name: gpt-4o-mini\n            provider: openai\n          prompt_template:\n            - id: 230c6990-e5b0-41d7-9dc8-70782d28bd8d\n              role: system\n              text: 你是一个乐于助人的助手。请将图片里的内容完整无缺的复述出来。\n          selected: true\n          title: 支持图片输入的LLM\n          type: llm\n          variables: [ ]\n          vision:\n            configs:\n              detail: high\n              variable_selector:\n                - sys\n                - files\n            enabled: true\n        height: 98\n        id: '1739252840296'\n        position:\n          x: 507.5756009542859\n          y: 498.0040880918219\n        positionAbsolute:\n          x: 507.5756009542859\n          y: 498.0040880918219\n        selected: true\n        sourcePosition: right\n        targetPosition: left\n        type: custom\n        width: 244\n      - data:\n          answer: '{{#1739252840296.text#}}'\n          desc: ''\n          selected: false\n          title: 直接回复 3\n          type: answer\n          variables: [ ]\n        height: 103\n        id: '1739252888579'\n        position:\n          x: 818.0854081792563\n          y: 498.0040880918219\n        positionAbsolute:\n          x: 818.0854081792563\n          y: 498.0040880918219\n        selected: false\n        sourcePosition: right\n        targetPosition: left\n        type: custom\n        width: 244\n      - data:\n          desc: ''\n          extract_by:\n            enabled: true\n            serial: '1'\n          filter_by:\n            conditions:\n              - comparison_operator: contains\n                key: name\n                value: ''\n            enabled: false\n          item_var_type: file\n          limit:\n            enabled: false\n            size: 1\n          order_by:\n            enabled: false\n            key: ''\n            value: asc\n          selected: false\n          title: 列表操作\n          type: list-operator\n          var_type: array[file]\n          variable:\n            - sys\n            - files\n        height: 92\n        id: '1739254087701'\n        position:\n          x: 507.5756009542859\n          y: 630.2109361546167\n        positionAbsolute:\n          x: 507.5756009542859\n          y: 630.2109361546167\n        selected: false\n        sourcePosition: right\n        targetPosition: left\n        type: custom\n        width: 244\n      - data:\n          context:\n            enabled: false\n            variable_selector: [ ]\n          desc: ''\n          memory:\n            query_prompt_template: '{{#sys.query#}}\n\n            {{#1739252789163.text#}}'\n            role_prefix:\n              assistant: ''\n              user: ''\n            window:\n              enabled: false\n              size: 50\n          model:\n            completion_params:\n              temperature: 0.7\n            mode: chat\n            name: gpt-4o-mini\n            provider: openai\n          prompt_template:\n            - id: 6291138d-3a76-46e1-a7e0-d2670d6352f5\n              role: system\n              text: 你是一个友好的助理，你的名字叫XYBot。你会用简洁方式回答问题。\n          selected: false\n          title: LLM\n          type: llm\n          variables: [ ]\n          vision:\n            enabled: false\n        height: 98\n        id: '1739255059499'\n        position:\n          x: 1115.57540385159\n          y: 637.9065614866665\n        positionAbsolute:\n          x: 1115.57540385159\n          y: 637.9065614866665\n        selected: false\n        sourcePosition: right\n        targetPosition: left\n        type: custom\n        width: 244\n      - data:\n          desc: ''\n          provider_id: audio\n          provider_name: audio\n          provider_type: builtin\n          selected: false\n          title: Text To Speech\n          tool_configurations:\n            model: openai_api_compatible#step-tts-mini\n            voice#openai#tts-1: null\n            voice#openai#tts-1-hd: null\n            voice#openai_api_compatible#step-tts-mini: qingniandaxuesheng\n            voice#siliconflow#fishaudio/fish-speech-1.4: null\n            voice#siliconflow#fishaudio/fish-speech-1.5: null\n          tool_label: Text To Speech\n          tool_name: tts\n          tool_parameters:\n            text:\n              type: mixed\n              value: '{{#1739255059499.text#}}'\n          type: tool\n        height: 220\n        id: '1739257224364'\n        position:\n          x: 1397.2265350019834\n          y: 637.9065614866665\n        positionAbsolute:\n          x: 1397.2265350019834\n          y: 637.9065614866665\n        selected: false\n        sourcePosition: right\n        targetPosition: left\n        type: custom\n        width: 244\n      - data:\n          answer: '{{#1739257224364.files#}}'\n          desc: ''\n          selected: false\n          title: 直接回复 3\n          type: answer\n          variables: [ ]\n        height: 103\n        id: '1739288133732'\n        position:\n          x: 1701.2265350019834\n          y: 637.9065614866665\n        positionAbsolute:\n          x: 1701.2265350019834\n          y: 637.9065614866665\n        selected: false\n        sourcePosition: right\n        targetPosition: left\n        type: custom\n        width: 244\n    viewport:\n      x: 166.25140302119496\n      y: -208.44690452784948\n      zoom: 0.6997797002524726\n"
  },
  {
    "path": "app.py",
    "content": "import asyncio\nimport os\nimport signal\nimport sys\n\nfrom loguru import logger\n\n# 在任何导入之前设置日志级别\nlogger.remove()\nlogger.level(\"WEBUI\", no=20, color=\"<blue>\")\nlogger.level(\"API\", no=1, color=\"<blue>\")\nlogger.add(sys.stdout, level=\"INFO\", colorize=True,\n           format=\"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level}</level> | {message}\")\nlogger.add(\"logs/xybot.log\", rotation=\"10mb\", level=\"DEBUG\", encoding=\"utf-8\")\nlogger.add(\"logs/wechatapi.log\", level=\"DEBUG\", filter=lambda r: r[\"level\"].name == \"API\")\nlogger.add(\"logs/webui.log\", level=\"WEBUI\", filter=lambda r: r[\"level\"].name == \"WEBUI\")\n\n# 导入eventlet并应用猴子补丁\nimport eventlet\n\neventlet.monkey_patch()\n\n# 现在可以安全地导入其他模块\nfrom WebUI import create_app\nfrom WebUI.services.websocket_service import shutdown_websocket\nfrom database.XYBotDB import XYBotDB\nfrom database.keyvalDB import KeyvalDB\nfrom database.messsagDB import MessageDB\n\n# 全局变量\nmessage_db = None\nkeyval_db = None\n_is_shutting_down = False\n\n\nasync def init_system():\n    \"\"\"初始化系统数据库连接\"\"\"\n    global message_db, keyval_db\n    try:\n        logger.info(\"正在初始化数据库连接...\")\n        XYBotDB()\n        message_db = MessageDB()\n        await message_db.initialize()\n        keyval_db = KeyvalDB()\n        await keyval_db.initialize()\n        await keyval_db.delete(\"start_time\")\n        logger.success(\"数据库初始化成功\")\n    except Exception as e:\n        logger.error(f\"数据库初始化失败: {str(e)}\")\n        raise\n\n\nasync def shutdown_system():\n    \"\"\"关闭系统连接和资源\"\"\"\n    global _is_shutting_down, message_db, keyval_db\n    if _is_shutting_down:\n        return\n    _is_shutting_down = True\n\n    logger.info(\"正在关闭系统资源...\")\n\n    # 关闭WebSocket服务\n    try:\n        shutdown_websocket()\n    except Exception as e:\n        logger.error(f\"关闭WebSocket服务时出错: {str(e)}\")\n\n    # 关闭数据库连接\n    logger.info(\"正在关闭数据库连接...\")\n    for db, name in [(message_db, \"消息数据库\"), (keyval_db, \"键值数据库\")]:\n        if db:\n            try:\n                await db.close()\n                logger.info(f\"{name}连接已关闭\")\n            except Exception as e:\n                logger.error(f\"关闭{name}连接时出错: {str(e)}\")\n\n    message_db = keyval_db = None\n    logger.success(\"所有系统资源已关闭\")\n\n\ndef run_async_safely(coro):\n    \"\"\"安全运行异步协程，处理事件循环问题\n    \n    Args:\n        coro: 要运行的异步协程\n    \"\"\"\n    try:\n        try:\n            loop = asyncio.get_event_loop()\n        except RuntimeError:\n            loop = asyncio.new_event_loop()\n            asyncio.set_event_loop(loop)\n\n        if loop.is_running():\n            asyncio.run_coroutine_threadsafe(coro, loop).result(timeout=5)\n        else:\n            loop.run_until_complete(coro)\n    except Exception as e:\n        logger.error(f\"异步任务执行失败: {str(e)}\")\n\n\ndef signal_handler(signum, _):\n    \"\"\"处理终止信号\n    \n    Args:\n        signum: 信号编号\n        _: 信号帧（未使用）\n    \"\"\"\n    logger.info(f\"收到 {signum} 信号, 退出中...\")\n    run_async_safely(shutdown_system())\n    sys.exit(0)\n\n\n# 注册信号处理器\nsignal.signal(signal.SIGINT, signal_handler)\nsignal.signal(signal.SIGTERM, signal_handler)\n\n\ndef main():\n    \"\"\"应用主入口函数\"\"\"\n    try:\n        # 初始化系统\n        asyncio.run(init_system())\n\n        # 创建Flask应用和socketio\n        app, socketio = create_app()\n\n        # 运行Web服务器\n        host = os.environ.get('FLASK_HOST', '0.0.0.0')\n        port = int(os.environ.get('FLASK_PORT', 9999))\n        debug = False  # 禁用debug模式以防止双重初始化\n\n        logger.info(f\"WebUI服务启动于 http://{host}:{port}/\")\n        socketio.run(app, host=host, port=port, debug=debug)\n\n    except KeyboardInterrupt:\n        logger.info(\"接收到中断信号，开始优雅关闭...\")\n    except Exception as e:\n        logger.error(f\"应用运行时发生错误: {str(e)}\")\n    finally:\n        run_async_safely(shutdown_system())\n\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "bot.py",
    "content": "import asyncio\nimport json\nimport os\nimport sys\nimport time\nimport tomllib\nimport traceback\nfrom pathlib import Path\n\nfrom loguru import logger\n\nimport WechatAPI\nfrom WebUI.common.bot_bridge import bot_bridge\nfrom WechatAPI.Server.WechatAPIServer import wechat_api_server\nfrom database.XYBotDB import XYBotDB\nfrom database.keyvalDB import KeyvalDB\nfrom database.messsagDB import MessageDB\nfrom utils.decorators import scheduler\nfrom utils.plugin_manager import PluginManager\nfrom utils.xybot import XYBot\n\n\nasync def run_bot():\n    \"\"\"\n    机器人主要运行逻辑\n    \"\"\"\n\n    try:\n        # 设置工作目录\n        script_dir = Path(__file__).resolve().parent\n\n        # 读取主设置\n        config_path = script_dir / \"main_config.toml\"\n        with open(config_path, \"rb\") as f:\n            main_config = tomllib.load(f)\n\n        logger.success(\"读取主设置成功\")\n\n        # 启动WechatAPI服务\n        server = wechat_api_server\n        api_config = main_config.get(\"WechatAPIServer\", {})\n        redis_host = api_config.get(\"redis-host\", \"127.0.0.1\")\n        redis_port = api_config.get(\"redis-port\", 6379)\n        logger.debug(\"Redis 主机地址: {}:{}\", redis_host, redis_port)\n        await server.start(port=api_config.get(\"port\", 9000),\n                           mode=api_config.get(\"mode\", \"release\"),\n                           redis_host=redis_host,\n                           redis_port=redis_port,\n                           redis_password=api_config.get(\"redis-password\", \"\"),\n                           redis_db=api_config.get(\"redis-db\", 0))\n\n        # 实例化WechatAPI客户端\n        bot = WechatAPI.WechatAPIClient(\"127.0.0.1\", api_config.get(\"port\", 9000))\n        bot.ignore_protect = main_config.get(\"XYBot\", {}).get(\"ignore-protection\", False)\n\n        # 等待WechatAPI服务启动\n        time_out = 10\n        while not await bot.is_running() and time_out > 0:\n            logger.info(\"等待WechatAPI启动中\")\n            await asyncio.sleep(2)\n            time_out -= 2\n\n        if time_out <= 0:\n            logger.error(\"WechatAPI服务启动超时\")\n            return\n\n        if not await bot.check_database():\n            logger.error(\"Redis连接失败，请检查Redis是否在运行中，Redis的配置\")\n            return\n\n        logger.success(\"WechatAPI服务已启动\")\n\n        # 加载插件目录下的所有插件\n        plugin_manager = PluginManager()\n        plugin_manager.set_bot(bot)\n        loaded_plugins = await plugin_manager.load_plugins(load_disabled=False)\n        logger.success(f\"已加载插件: {loaded_plugins}\")\n\n        # ==========登陆==========\n\n        # 检查并创建robot_stat.json文件\n        robot_stat_path = script_dir / \"resource\" / \"robot_stat.json\"\n        if not os.path.exists(robot_stat_path):\n            default_config = {\n                \"wxid\": \"\",\n                \"device_name\": \"\",\n                \"device_id\": \"\"\n            }\n            os.makedirs(os.path.dirname(robot_stat_path), exist_ok=True)\n            with open(robot_stat_path, \"w\") as f:\n                json.dump(default_config, f)\n            robot_stat = default_config\n        else:\n            with open(robot_stat_path, \"r\") as f:\n                robot_stat = json.load(f)\n\n        wxid = robot_stat.get(\"wxid\", None)\n        device_name = robot_stat.get(\"device_name\", None)\n        device_id = robot_stat.get(\"device_id\", None)\n\n        if not await bot.is_logged_in(wxid):\n            while not await bot.is_logged_in(wxid):\n                # 需要登录\n                try:\n                    if await bot.get_cached_info(wxid):\n                        # 尝试唤醒登录\n                        uuid = await bot.awaken_login(wxid)\n                        logger.success(\"获取到登录uuid: {}\", uuid)\n                    else:\n                        # 二维码登录\n                        if not device_name:\n                            device_name = bot.create_device_name()\n                        if not device_id:\n                            device_id = bot.create_device_id()\n                        uuid, url, qr = await bot.get_qr_code(device_id=device_id, device_name=device_name)\n                        logger.success(\"获取到登录uuid: {}\", uuid)\n                        logger.success(\"获取到登录二维码: {}\", url)\n                        bot_bridge.save_profile(avatar_url=url)\n                        logger.info(\"\\n\" + qr)\n                except:\n                    # 二维码登录\n                    if not device_name:\n                        device_name = bot.create_device_name()\n                    if not device_id:\n                        device_id = bot.create_device_id()\n                    uuid, url, qr = await bot.get_qr_code(device_id=device_id, device_name=device_name)\n                    logger.success(\"获取到登录uuid: {}\", uuid)\n                    logger.success(\"获取到登录二维码: {}\", url)\n                    bot_bridge.save_profile(avatar_url=url)\n                    logger.info(\"\\n\" + qr)\n\n                while True:\n                    stat, data = await bot.check_login_uuid(uuid, device_id=device_id)\n                    if stat:\n                        break\n                    logger.info(\"等待登录中，过期倒计时：{}\", data)\n                    await asyncio.sleep(5)\n\n            # 保存登录信息\n            robot_stat[\"wxid\"] = bot.wxid\n            robot_stat[\"device_name\"] = device_name\n            robot_stat[\"device_id\"] = device_id\n            with open(\"resource/robot_stat.json\", \"w\") as f:\n                json.dump(robot_stat, f)\n\n            # 获取登录账号信息\n            bot.wxid = data.get(\"acctSectResp\", {}).get(\"userName\", \"\")\n            bot.nickname = data.get(\"acctSectResp\", {}).get(\"nickName\", \"\")\n            bot.alias = data.get(\"acctSectResp\", {}).get(\"alias\", \"\")\n            bot.phone = data.get(\"acctSectResp\", {}).get(\"bindMobile\", \"\")\n\n            logger.info(\"登录账号信息: wxid: {}  昵称: {}  微信号: {}  手机号: {}\", bot.wxid, bot.nickname, bot.alias,\n                        bot.phone)\n\n            bot_bridge.save_profile(avatar_url=data.get(\"userInfoExt\", {}).get(\"BigHeadImgUrl\", \"\"),\n                                    nickname=data.get(\"acctSectResp\", {}).get(\"nickName\", \"\"),\n                                    wxid=data.get(\"acctSectResp\", {}).get(\"userName\", \"\"),\n                                    alias=data.get(\"acctSectResp\", {}).get(\"alias\", \"\"))\n\n\n        else:  # 已登录\n            bot.wxid = wxid\n            profile = await bot.get_profile()\n\n            bot.nickname = profile.get(\"NickName\", {}).get(\"string\", \"\")\n            bot.alias = profile.get(\"Alias\", \"\")\n            bot.phone = profile.get(\"BindMobile\", {}).get(\"string\", \"\")\n\n            logger.info(\"登录账号信息: wxid: {}  昵称: {}  微信号: {}  手机号: {}\", bot.wxid, bot.nickname, bot.alias,\n                        bot.phone)\n\n            bot_bridge.save_profile(nickname=profile.get(\"NickName\", {}).get(\"string\", \"\"),\n                                    wxid=wxid,\n                                    alias=profile.get(\"Alias\", \"\"))\n\n        logger.info(\"登录设备信息: device_name: {}  device_id: {}\", device_name, device_id)\n\n        logger.success(\"登录成功\")\n\n        # ========== 登录完毕 开始初始化 ========== #\n\n        # 开启自动心跳\n        try:\n            success = await bot.start_auto_heartbeat()\n            if success:\n                logger.success(\"已开启自动心跳\")\n            else:\n                logger.warning(\"开启自动心跳失败\")\n        except ValueError:\n            logger.warning(\"自动心跳已在运行\")\n        except Exception as e:\n            if \"在运行\" not in str(e):\n                logger.warning(\"自动心跳已在运行\")\n\n        # 初始化机器人\n        xybot = XYBot(bot)\n        xybot.update_profile(bot.wxid, bot.nickname, bot.alias, bot.phone)\n\n        # 启动调度器\n        if scheduler.state == 0:\n            scheduler.start()\n        else:\n            scheduler.remove_all_jobs()\n        logger.success(\"定时任务已启动\")\n\n        # ========== 开始接受消息 ========== #\n\n        # 开始接受消息说明机器人开始正常运行\n\n        keyval_db = KeyvalDB()\n        await keyval_db.set(\"start_time\", str(int(time.time())))\n\n        # 先接受堆积消息\n        logger.info(\"处理堆积消息中\")\n        count = 0\n        while True:\n            data = await bot.sync_message()\n            data = data.get(\"AddMsgs\")\n            if not data:\n                if count > 2:\n                    break\n                else:\n                    count += 1\n                    continue\n\n            logger.debug(\"接受到 {} 条消息\", len(data))\n            await asyncio.sleep(1)\n        logger.success(\"处理堆积消息完毕\")\n\n        logger.success(\"开始处理消息\")\n        while True:\n            try:\n                data = await bot.sync_message()\n            except Exception as e:\n                logger.warning(\"获取新消息失败 {}\", e)\n                await asyncio.sleep(5)\n                continue\n\n            data = data.get(\"AddMsgs\")\n            if data:\n                for message in data:\n                    asyncio.create_task(xybot.process_message(message))\n            await asyncio.sleep(0.5)\n\n    except asyncio.CancelledError:\n        await wechat_api_server.stop()\n        logger.info(\"机器人关闭\")\n    except Exception as e:\n        logger.error(f\"机器人运行出错: {e}\")\n        logger.error(traceback.format_exc())\n\n\nasync def init_system():\n    \"\"\"系统初始化\"\"\"\n    print(\n        \"░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓███████▓▒░ ░▒▓██████▓▒░▒▓████████▓▒░      ░▒▓█▓▒░░▒▓█▓▒░▒▓███████▓▒░  \\n\"\n        \"░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░          ░▒▓█▓▒░░▒▓█▓▒░      ░▒▓█▓▒░ \\n\"\n        \"░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░           ░▒▓█▓▒▒▓█▓▒░       ░▒▓█▓▒░ \\n\"\n        \" ░▒▓██████▓▒░ ░▒▓██████▓▒░░▒▓███████▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░           ░▒▓█▓▒▒▓█▓▒░ ░▒▓██████▓▒░  \\n\"\n        \"░▒▓█▓▒░░▒▓█▓▒░  ░▒▓█▓▒░   ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░            ░▒▓█▓▓█▓▒░ ░▒▓█▓▒░        \\n\"\n        \"░▒▓█▓▒░░▒▓█▓▒░  ░▒▓█▓▒░   ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░            ░▒▓█▓▓█▓▒░ ░▒▓█▓▒░        \\n\"\n        \"░▒▓█▓▒░░▒▓█▓▒░  ░▒▓█▓▒░   ░▒▓███████▓▒░ ░▒▓██████▓▒░  ░▒▓█▓▒░             ░▒▓██▓▒░  ░▒▓████████▓▒░\\n\")\n\n    # 配置日志\n    logger.remove()\n\n    logger.level(\"API\", no=1, color=\"<cyan>\")\n\n    logger.add(\n        \"logs/xybot.log\",\n        format=\"{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}\",\n        encoding=\"utf-8\",\n        enqueue=True,\n        rotation=\"10mb\",\n        retention=\"2 weeks\",\n        backtrace=True,\n        diagnose=True,\n        level=\"DEBUG\",\n    )\n    logger.add(\n        sys.stdout,\n        colorize=True,\n        format=\"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level}</level> | {message}\",\n        level=\"TRACE\",\n        enqueue=True,\n        backtrace=True,\n        diagnose=True,\n    )\n    logger.add(\n        \"logs/wechatapi.log\",\n        format=\"{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}\",\n        level=\"API\",\n        encoding=\"utf-8\",\n        enqueue=True,\n        rotation=\"10mb\",\n        retention=\"2 weeks\",\n        backtrace=True,\n        diagnose=True,\n        filter=lambda record: record[\"level\"].name == \"API\",\n    )\n\n    # 初始化数据库\n    XYBotDB()\n\n    message_db = MessageDB()\n    await message_db.initialize()\n\n    keyval_db = KeyvalDB()\n    await keyval_db.initialize()\n    await keyval_db.delete(\"start_time\")\n\n    logger.success(\"数据库初始化成功\")\n\n\nasync def main():\n    \"\"\"主入口函数\"\"\"\n    await init_system()\n\n    await run_bot()\n\n\nif __name__ == \"__main__\":\n    if sys.version_info.major != 3 and sys.version_info.minor != 11:\n        print(\"请使用Python3.11\")\n        sys.exit(1)\n\n    asyncio.run(main())\n"
  },
  {
    "path": "database/XYBotDB.py",
    "content": "import datetime\nimport tomllib\nfrom concurrent.futures import ThreadPoolExecutor\nfrom typing import Union\n\nfrom loguru import logger\nfrom sqlalchemy import Column, String, Integer, DateTime, create_engine, JSON, Boolean\nfrom sqlalchemy import update\nfrom sqlalchemy.exc import SQLAlchemyError\nfrom sqlalchemy.orm import declarative_base\nfrom sqlalchemy.orm import sessionmaker\n\nfrom utils.singleton import Singleton\n\nBase = declarative_base()\n\n\nclass User(Base):\n    __tablename__ = 'user'\n\n    wxid = Column(String(20), primary_key=True, nullable=False, unique=True, index=True, autoincrement=False,\n                  comment='wxid')\n    points = Column(Integer, nullable=False, default=0, comment='points')\n    signin_stat = Column(DateTime, nullable=False, default=datetime.datetime.fromtimestamp(0), comment='signin_stat')\n    signin_streak = Column(Integer, nullable=False, default=0, comment='signin_streak')\n    whitelist = Column(Boolean, nullable=False, default=False, comment='whitelist')\n    llm_thread_id = Column(JSON, nullable=False, default=lambda: {}, comment='llm_thread_id')\n\n\nclass Chatroom(Base):\n    __tablename__ = 'chatroom'\n\n    chatroom_id = Column(String(20), primary_key=True, nullable=False, unique=True, index=True, autoincrement=False,\n                         comment='chatroom_id')\n    members = Column(JSON, nullable=False, default=list, comment='members')\n    llm_thread_id = Column(JSON, nullable=False, default=lambda: {}, comment='llm_thread_id')\n\n\nclass XYBotDB(metaclass=Singleton):\n    def __init__(self):\n        with open(\"main_config.toml\", \"rb\") as f:\n            main_config = tomllib.load(f)\n\n        self.database_url = main_config[\"XYBot\"][\"XYBotDB-url\"]\n        self.engine = create_engine(self.database_url)\n        self.DBSession = sessionmaker(bind=self.engine)\n\n        # 创建表\n        Base.metadata.create_all(self.engine)\n\n        # 创建线程池执行器\n        self.executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix=\"database\")\n\n    def _execute_in_queue(self, method, *args, **kwargs):\n        \"\"\"在队列中执行数据库操作\"\"\"\n        future = self.executor.submit(method, *args, **kwargs)\n        try:\n            return future.result(timeout=20)  # 20秒超时\n        except Exception as e:\n            logger.error(f\"数据库操作失败: {method.__name__} - {str(e)}\")\n            raise\n\n    # USER\n\n    def add_points(self, wxid: str, num: int) -> bool:\n        \"\"\"Thread-safe point addition\"\"\"\n        return self._execute_in_queue(self._add_points, wxid, num)\n\n    def _add_points(self, wxid: str, num: int) -> bool:\n        \"\"\"Thread-safe point addition\"\"\"\n        session = self.DBSession()\n        try:\n            # Use UPDATE with atomic operation\n            result = session.execute(\n                update(User)\n                .where(User.wxid == wxid)\n                .values(points=User.points + num)\n            )\n            if result.rowcount == 0:\n                # User doesn't exist, create new\n                user = User(wxid=wxid, points=num)\n                session.add(user)\n            logger.info(f\"数据库: 用户{wxid}积分增加{num}\")\n            session.commit()\n            return True\n        except SQLAlchemyError as e:\n            session.rollback()\n            logger.error(f\"数据库: 用户{wxid}积分增加失败, 错误: {e}\")\n            return False\n        finally:\n            session.close()\n\n    def set_points(self, wxid: str, num: int) -> bool:\n        \"\"\"Thread-safe point setting\"\"\"\n        return self._execute_in_queue(self._set_points, wxid, num)\n\n    def _set_points(self, wxid: str, num: int) -> bool:\n        \"\"\"Thread-safe point setting\"\"\"\n        session = self.DBSession()\n        try:\n            result = session.execute(\n                update(User)\n                .where(User.wxid == wxid)\n                .values(points=num)\n            )\n            if result.rowcount == 0:\n                user = User(wxid=wxid, points=num)\n                session.add(user)\n            logger.info(f\"数据库: 用户{wxid}积分设置为{num}\")\n            session.commit()\n            return True\n        except SQLAlchemyError as e:\n            session.rollback()\n            logger.error(f\"数据库: 用户{wxid}积分设置失败, 错误: {e}\")\n            return False\n        finally:\n            session.close()\n\n    def get_points(self, wxid: str) -> int:\n        \"\"\"Get user points\"\"\"\n        return self._execute_in_queue(self._get_points, wxid)\n\n    def _get_points(self, wxid: str) -> int:\n        \"\"\"Get user points\"\"\"\n        session = self.DBSession()\n        try:\n            user = session.query(User).filter_by(wxid=wxid).first()\n            return user.points if user else 0\n        finally:\n            session.close()\n\n    def get_signin_stat(self, wxid: str) -> datetime.datetime:\n        \"\"\"获取用户签到状态\"\"\"\n        return self._execute_in_queue(self._get_signin_stat, wxid)\n\n    def _get_signin_stat(self, wxid: str) -> datetime.datetime:\n        session = self.DBSession()\n        try:\n            user = session.query(User).filter_by(wxid=wxid).first()\n            return user.signin_stat if user else datetime.datetime.fromtimestamp(0)\n        finally:\n            session.close()\n\n    def set_signin_stat(self, wxid: str, signin_time: datetime.datetime) -> bool:\n        \"\"\"Thread-safe set user's signin time\"\"\"\n        return self._execute_in_queue(self._set_signin_stat, wxid, signin_time)\n\n    def _set_signin_stat(self, wxid: str, signin_time: datetime.datetime) -> bool:\n        session = self.DBSession()\n        try:\n            result = session.execute(\n                update(User)\n                .where(User.wxid == wxid)\n                .values(\n                    signin_stat=signin_time,\n                    signin_streak=User.signin_streak\n                )\n            )\n            if result.rowcount == 0:\n                user = User(\n                    wxid=wxid,\n                    signin_stat=signin_time,\n                    signin_streak=0\n                )\n                session.add(user)\n            logger.info(f\"数据库: 用户{wxid}登录时间设置为{signin_time}\")\n            session.commit()\n            return True\n        except SQLAlchemyError as e:\n            session.rollback()\n            logger.error(f\"数据库: 用户{wxid}登录时间设置失败, 错误: {e}\")\n            return False\n        finally:\n            session.close()\n\n    def reset_all_signin_stat(self) -> bool:\n        \"\"\"Reset all users' signin status\"\"\"\n        session = self.DBSession()\n        try:\n            session.query(User).update({User.signin_stat: datetime.datetime.fromtimestamp(0)})\n            session.commit()\n            return True\n        except Exception as e:\n            session.rollback()\n            logger.error(f\"数据库: 重置所有用户登录时间失败, 错误: {e}\")\n            return False\n        finally:\n            session.close()\n\n    def get_leaderboard(self, count: int) -> list:\n        \"\"\"Get points leaderboard\"\"\"\n        session = self.DBSession()\n        try:\n            users = session.query(User).order_by(User.points.desc()).limit(count).all()\n            return [(user.wxid, user.points) for user in users]\n        finally:\n            session.close()\n\n    def set_whitelist(self, wxid: str, stat: bool) -> bool:\n        \"\"\"Set user's whitelist status\"\"\"\n        session = self.DBSession()\n        try:\n            user = session.query(User).filter_by(wxid=wxid).first()\n            if not user:\n                user = User(wxid=wxid)\n                session.add(user)\n            user.whitelist = stat\n            session.commit()\n            logger.info(f\"数据库: 用户{wxid}白名单状态设置为{stat}\")\n            return True\n        except Exception as e:\n            session.rollback()\n            logger.error(f\"数据库: 用户{wxid}白名单状态设置失败, 错误: {e}\")\n            return False\n        finally:\n            session.close()\n\n    def get_whitelist(self, wxid: str) -> bool:\n        \"\"\"Get user's whitelist status\"\"\"\n        session = self.DBSession()\n        try:\n            user = session.query(User).filter_by(wxid=wxid).first()\n            return user.whitelist if user else False\n        finally:\n            session.close()\n\n    def get_whitelist_list(self) -> list:\n        \"\"\"Get list of all whitelisted users\"\"\"\n        session = self.DBSession()\n        try:\n            users = session.query(User).filter_by(whitelist=True).all()\n            return [user.wxid for user in users]\n        finally:\n            session.close()\n\n    def safe_trade_points(self, trader_wxid: str, target_wxid: str, num: int) -> bool:\n        \"\"\"Thread-safe points trading between users\"\"\"\n        return self._execute_in_queue(self._safe_trade_points, trader_wxid, target_wxid, num)\n\n    def _safe_trade_points(self, trader_wxid: str, target_wxid: str, num: int) -> bool:\n        \"\"\"Thread-safe points trading between users\"\"\"\n        session = self.DBSession()\n        try:\n            # Start transaction with row-level locking\n            trader = session.query(User).filter_by(wxid=trader_wxid) \\\n                .with_for_update().first()  # Acquire row lock\n            target = session.query(User).filter_by(wxid=target_wxid) \\\n                .with_for_update().first()  # Acquire row lock\n\n            if not trader:\n                trader = User(wxid=trader_wxid)\n                session.add(trader)\n            if not target:\n                target = User(wxid=target_wxid)\n                session.add(target)\n                session.flush()  # Ensure IDs are generated\n\n            if trader.points >= num:\n                trader.points -= num\n                target.points += num\n                session.commit()\n                logger.info(f\"数据库: 用户{trader_wxid}给用户{target_wxid}转账{num}积分\")\n                return True\n            logger.info(f\"数据库: 转账失败, 用户{trader_wxid}积分不足\")\n            session.rollback()\n            return False\n        except SQLAlchemyError as e:\n            session.rollback()\n            logger.error(f\"数据库: 转账失败, 错误: {e}\")\n            return False\n        finally:\n            session.close()\n\n    def get_user_list(self) -> list:\n        \"\"\"Get list of all users\"\"\"\n        session = self.DBSession()\n        try:\n            users = session.query(User).all()\n            return [user.wxid for user in users]\n        finally:\n            session.close()\n\n    def get_llm_thread_id(self, wxid: str, namespace: str = None) -> Union[dict, str]:\n        \"\"\"Get LLM thread id for user or chatroom\"\"\"\n        session = self.DBSession()\n        try:\n            # Check if it's a chatroom ID\n            if wxid.endswith(\"@chatroom\"):\n                chatroom = session.query(Chatroom).filter_by(chatroom_id=wxid).first()\n                if namespace:\n                    return chatroom.llm_thread_id.get(namespace, \"\") if chatroom else \"\"\n                else:\n                    return chatroom.llm_thread_id if chatroom else {}\n            else:\n                # Regular user\n                user = session.query(User).filter_by(wxid=wxid).first()\n                if namespace:\n                    return user.llm_thread_id.get(namespace, \"\") if user else \"\"\n                else:\n                    return user.llm_thread_id if user else {}\n        finally:\n            session.close()\n\n    def save_llm_thread_id(self, wxid: str, data: str, namespace: str) -> bool:\n        \"\"\"Save LLM thread id for user or chatroom\"\"\"\n        session = self.DBSession()\n        try:\n            if wxid.endswith(\"@chatroom\"):\n                chatroom = session.query(Chatroom).filter_by(chatroom_id=wxid).first()\n                if not chatroom:\n                    chatroom = Chatroom(\n                        chatroom_id=wxid,\n                        llm_thread_id={}\n                    )\n                    session.add(chatroom)\n                # 创建新字典并更新\n                new_thread_ids = dict(chatroom.llm_thread_id or {})\n                new_thread_ids[namespace] = data\n                chatroom.llm_thread_id = new_thread_ids\n            else:\n                user = session.query(User).filter_by(wxid=wxid).first()\n                if not user:\n                    user = User(\n                        wxid=wxid,\n                        llm_thread_id={}\n                    )\n                    session.add(user)\n                # 创建新字典并更新\n                new_thread_ids = dict(user.llm_thread_id or {})\n                new_thread_ids[namespace] = data\n                user.llm_thread_id = new_thread_ids\n\n            session.commit()\n            logger.info(f\"数据库: 成功保存 {wxid} 的 llm thread id\")\n            return True\n        except Exception as e:\n            session.rollback()\n            logger.error(f\"数据库: 保存用户llm thread id失败, 错误: {e}\")\n            return False\n        finally:\n            session.close()\n\n    def delete_all_llm_thread_id(self):\n        \"\"\"Clear llm thread id for everyone\"\"\"\n        session = self.DBSession()\n        try:\n            session.query(User).update({User.llm_thread_id: {}})\n            session.query(Chatroom).update({Chatroom.llm_thread_id: {}})\n            session.commit()\n            return True\n        except Exception as e:\n            session.rollback()\n            logger.error(f\"数据库: 清除所有用户llm thread id失败, 错误: {e}\")\n            return False\n        finally:\n            session.close()\n\n    def get_signin_streak(self, wxid: str) -> int:\n        \"\"\"Thread-safe get user's signin streak\"\"\"\n        return self._execute_in_queue(self._get_signin_streak, wxid)\n\n    def _get_signin_streak(self, wxid: str) -> int:\n        session = self.DBSession()\n        try:\n            user = session.query(User).filter_by(wxid=wxid).first()\n            return user.signin_streak if user else 0\n        finally:\n            session.close()\n\n    def set_signin_streak(self, wxid: str, streak: int) -> bool:\n        \"\"\"Thread-safe set user's signin streak\"\"\"\n        return self._execute_in_queue(self._set_signin_streak, wxid, streak)\n\n    def _set_signin_streak(self, wxid: str, streak: int) -> bool:\n        session = self.DBSession()\n        try:\n            result = session.execute(\n                update(User)\n                .where(User.wxid == wxid)\n                .values(signin_streak=streak)\n            )\n            if result.rowcount == 0:\n                user = User(wxid=wxid, signin_streak=streak)\n                session.add(user)\n            logger.info(f\"数据库: 用户{wxid}连续签到天数设置为{streak}\")\n            session.commit()\n            return True\n        except SQLAlchemyError as e:\n            session.rollback()\n            logger.error(f\"数据库: 用户{wxid}连续签到天数设置失败, 错误: {e}\")\n            return False\n        finally:\n            session.close()\n\n    # CHATROOM\n\n    def get_chatroom_list(self) -> list:\n        \"\"\"Get list of all chatrooms\"\"\"\n        session = self.DBSession()\n        try:\n            chatrooms = session.query(Chatroom).all()\n            return [chatroom.chatroom_id for chatroom in chatrooms]\n        finally:\n            session.close()\n\n    def get_chatroom_members(self, chatroom_id: str) -> set:\n        \"\"\"Get members of a chatroom\"\"\"\n        session = self.DBSession()\n        try:\n            chatroom = session.query(Chatroom).filter_by(chatroom_id=chatroom_id).first()\n            return set(chatroom.members) if chatroom else set()\n        finally:\n            session.close()\n\n    def set_chatroom_members(self, chatroom_id: str, members: set) -> bool:\n        \"\"\"Set members of a chatroom\"\"\"\n        session = self.DBSession()\n        try:\n            chatroom = session.query(Chatroom).filter_by(chatroom_id=chatroom_id).first()\n            if not chatroom:\n                chatroom = Chatroom(chatroom_id=chatroom_id)\n                session.add(chatroom)\n            chatroom.members = list(members)  # Convert set to list for JSON storage\n            logger.info(f\"Database: Set chatroom {chatroom_id} members successfully\")\n            session.commit()\n            return True\n        except Exception as e:\n            session.rollback()\n            logger.error(f\"Database: Set chatroom {chatroom_id} members failed, error: {e}\")\n            return False\n        finally:\n            session.close()\n\n    def get_users_count(self):\n        session = self.DBSession()\n        try:\n            return session.query(User).count()\n        finally:\n            session.close()\n\n    def __del__(self):\n        \"\"\"确保关闭时清理资源\"\"\"\n        if hasattr(self, 'executor'):\n            self.executor.shutdown(wait=True)\n        if hasattr(self, 'engine'):\n            self.engine.dispose()\n"
  },
  {
    "path": "database/keyvalDB.py",
    "content": "import asyncio\nimport logging\nimport tomllib\nfrom datetime import datetime, timedelta\nfrom typing import Optional, Union, List\n\nfrom pydantic import validate_arguments\nfrom sqlalchemy import Column, String, Text, DateTime, delete, select\nfrom sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_scoped_session\nfrom sqlalchemy.orm import declarative_base, sessionmaker\n\nfrom utils.singleton import Singleton\n\nDeclarativeBase = declarative_base()\n\n\nclass KeyValue(DeclarativeBase):\n    __tablename__ = 'key_value_store'\n\n    key = Column(String(255), primary_key=True, unique=True, comment='键名')\n    value = Column(Text, nullable=False, comment='存储值')\n    expire_time = Column(DateTime, index=True, comment='过期时间')\n\n\nclass KeyvalDB(metaclass=Singleton):\n    _instance = None\n\n    def __new__(cls):\n        with open(\"main_config.toml\", \"rb\") as f:\n            main_config = tomllib.load(f)\n        db_url = main_config[\"XYBot\"][\"keyvalDB-url\"]\n\n        if cls._instance is None:\n            cls._instance = super().__new__(cls)\n            cls._instance.engine = create_async_engine(\n                db_url,\n                echo=False,\n                future=True\n            )\n            cls._async_session_factory = async_scoped_session(\n                sessionmaker(\n                    cls._instance.engine,\n                    class_=AsyncSession,\n                    expire_on_commit=False\n                ),\n                scopefunc=asyncio.current_task\n            )\n        return cls._instance\n\n    async def initialize(self):\n        \"\"\"异步初始化数据库\"\"\"\n        async with self.engine.begin() as conn:\n            await conn.run_sync(DeclarativeBase.metadata.create_all)\n        # 启动后台清理任务\n        asyncio.create_task(self._cleanup_expired())\n\n    @validate_arguments\n    async def set(\n            self,\n            key: str,\n            value: Union[str, dict, list],\n            ex: Optional[Union[int, timedelta]] = None\n    ) -> bool:\n        \"\"\"设置键值对，支持过期时间（秒或timedelta）\"\"\"\n        async with self._async_session_factory() as session:\n            try:\n                expire_time = None\n                if ex:\n                    # 统一处理时间类型转换\n                    if isinstance(ex, int):\n                        expire_time = datetime.now() + timedelta(seconds=ex)\n                    else:\n                        expire_time = datetime.now() + ex\n\n                kv = KeyValue(\n                    key=key,\n                    value=str(value),\n                    expire_time=expire_time\n                )\n                await session.merge(kv)\n                await session.commit()\n                return True\n            except Exception as e:\n                logging.error(f\"设置键值失败: {str(e)}\")\n                await session.rollback()\n                return False\n\n    async def get(self, key: str) -> Optional[str]:\n        \"\"\"获取键值，自动处理过期数据\"\"\"\n        async with self._async_session_factory() as session:\n            result = await session.get(KeyValue, key)\n            if not result:\n                return None\n\n            if result.expire_time and result.expire_time < datetime.now():\n                await session.delete(result)\n                await session.commit()\n                return None\n\n            return result.value\n\n    async def delete(self, key: str) -> bool:\n        \"\"\"删除键值\"\"\"\n        async with self._async_session_factory() as session:\n            result = await session.execute(delete(KeyValue).where(KeyValue.key == key))\n            await session.commit()\n            return result.rowcount > 0\n\n    async def exists(self, key: str) -> bool:\n        \"\"\"检查键是否存在\"\"\"\n        async with self._async_session_factory() as session:\n            result = await session.get(KeyValue, key)\n            if result and result.expire_time and result.expire_time < datetime.now():\n                await session.delete(result)\n                await session.commit()\n                return False\n            return result is not None\n\n    async def ttl(self, key: str) -> int:\n        \"\"\"获取剩余生存时间（秒）\"\"\"\n        async with self._async_session_factory() as session:\n            result = await session.get(KeyValue, key)\n            if not result or not result.expire_time:\n                return -1\n\n            remaining = (result.expire_time - datetime.now()).total_seconds()\n            # 明确返回类型处理\n            return int(remaining) if remaining > 0 else -2\n\n    async def expire(self, key: str, ex: Union[int, timedelta]) -> bool:\n        \"\"\"设置过期时间\"\"\"\n        async with self._async_session_factory() as session:\n            result = await session.get(KeyValue, key)\n            if not result:\n                return False\n\n            expire_time = datetime.now() + (ex if isinstance(ex, timedelta) else timedelta(seconds=ex))\n            result.expire_time = expire_time\n            await session.commit()\n            return True\n\n    async def keys(self, pattern: str = \"*\") -> List[str]:\n        \"\"\"查找匹配模式的键\"\"\"\n        async with self._async_session_factory() as session:\n            # 显式指定查询列类型\n            query = select(KeyValue.key).where(KeyValue.key.like(pattern.replace(\"*\", \"%\")))\n            result = await session.execute(query)\n            return [str(row[0]) for row in result.all()]  # 确保返回字符串类型\n\n    async def _cleanup_expired(self, interval: int = 3600):\n        \"\"\"后台定时清理过期数据\"\"\"\n        while True:\n            async with self._async_session_factory() as session:\n                await session.execute(\n                    delete(KeyValue).where(KeyValue.expire_time < datetime.now())\n                )\n                await session.commit()\n            await asyncio.sleep(interval)\n\n    async def close(self):\n        \"\"\"关闭数据库连接\"\"\"\n        try:\n            # 取消清理任务如果正在运行\n            for task in asyncio.all_tasks():\n                if task != asyncio.current_task() and '_cleanup_expired' in str(task):\n                    task.cancel()\n                    try:\n                        await task\n                    except asyncio.CancelledError:\n                        pass\n\n            # 关闭连接\n            await self.engine.dispose()\n            return True\n        except asyncio.CancelledError:\n            logging.warning(\"键值数据库关闭过程被取消，这可能是正常的关闭行为\")\n            return True\n        except Exception as e:\n            logging.error(f\"关闭键值数据库连接时出错: {str(e)}\")\n            return False\n\n    async def __aenter__(self):\n        return self\n\n    async def __aexit__(self, exc_type, exc_val, exc_tb):\n        await self.close()\n"
  },
  {
    "path": "database/messsagDB.py",
    "content": "import asyncio\nimport logging\nimport tomllib\nfrom datetime import datetime, timedelta\nfrom typing import Optional, List\n\nfrom pydantic import validate_arguments\nfrom sqlalchemy import Column, String, Integer, DateTime, Text, Boolean, delete\nfrom sqlalchemy import select\nfrom sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_scoped_session\nfrom sqlalchemy.orm import declarative_base, sessionmaker\n\nfrom utils.singleton import Singleton\n\n# 使用新的声明式基类\nDeclarativeBase = declarative_base()\n\n\nclass Message(DeclarativeBase):\n    __tablename__ = 'messages'\n\n    id = Column(Integer, primary_key=True, autoincrement=True)\n    msg_id = Column(Integer, index=True, comment='消息唯一ID（整型）')\n    sender_wxid = Column(String(40), index=True, comment='消息发送人wxid')\n    from_wxid = Column(String(40), index=True, comment='消息来源wxid')\n    msg_type = Column(Integer, comment='消息类型（整型编码）')\n    content = Column(Text, comment='消息内容')\n    timestamp = Column(DateTime, default=datetime.now, index=True, comment='消息时间戳')\n    is_group = Column(Boolean, default=False, comment='是否群消息')\n\n\nclass MessageDB(metaclass=Singleton):\n    _instance = None\n\n    def __new__(cls):\n        with open(\"main_config.toml\", \"rb\") as f:\n            main_config = tomllib.load(f)\n        db_url = main_config[\"XYBot\"][\"msgDB-url\"]\n\n        if cls._instance is None:\n            cls._instance = super().__new__(cls)\n            cls._instance.engine = create_async_engine(\n                db_url,\n                echo=False,\n                future=True\n            )\n            cls._async_session_factory = async_scoped_session(\n                sessionmaker(\n                    cls._instance.engine,\n                    class_=AsyncSession,\n                    expire_on_commit=False\n                ),\n                scopefunc=asyncio.current_task\n            )\n        return cls._instance\n\n    async def initialize(self):\n        \"\"\"异步初始化数据库\"\"\"\n        async with self.engine.begin() as conn:\n            await conn.run_sync(DeclarativeBase.metadata.create_all)\n\n    @validate_arguments(config=dict(arbitrary_types_allowed=True))\n    async def save_message(self,\n                           msg_id: int,\n                           sender_wxid: str,\n                           from_wxid: str,\n                           msg_type: int,\n                           content: str,\n                           is_group: bool = False) -> bool:\n        \"\"\"异步保存消息到数据库\"\"\"\n        async with self._async_session_factory() as session:\n            try:\n                message = Message(\n                    msg_id=msg_id,\n                    sender_wxid=sender_wxid,\n                    from_wxid=from_wxid,\n                    msg_type=msg_type,\n                    content=content,\n                    is_group=is_group,\n                    timestamp=datetime.now()\n                )\n                session.add(message)\n                await session.commit()\n                return True\n            except Exception as e:\n                logging.error(f\"保存消息失败: {str(e)}\")\n                await session.rollback()\n                return False\n\n    async def get_messages(self,\n                           start_time: Optional[datetime] = None,\n                           end_time: Optional[datetime] = None,\n                           sender_wxid: Optional[str] = None,\n                           from_wxid: Optional[str] = None,\n                           msg_type: Optional[int] = None,\n                           is_group: Optional[bool] = None,\n                           limit: int = 100) -> List[Message]:\n        \"\"\"异步查询消息记录\"\"\"\n        async with self._async_session_factory() as session:\n            try:\n                query = select(Message).order_by(Message.timestamp.desc()).limit(limit)\n\n                if start_time:\n                    query = query.where(Message.timestamp >= start_time)\n                if end_time:\n                    query = query.where(Message.timestamp <= end_time)\n                if sender_wxid:\n                    query = query.where(Message.sender_wxid == sender_wxid)\n                if from_wxid:\n                    query = query.where(Message.from_wxid == from_wxid)\n                if msg_type is not None:\n                    query = query.where(Message.msg_type == msg_type)\n                if is_group is not None:\n                    query = query.where(Message.is_group == is_group)\n\n                result = await session.execute(query)\n                return result.scalars().all()\n            except Exception as e:\n                logging.error(f\"查询消息失败: {str(e)}\")\n                return []\n\n    async def close(self):\n        \"\"\"关闭数据库连接\"\"\"\n        try:\n            # 取消清理任务如果正在运行\n            for task in asyncio.all_tasks():\n                if task != asyncio.current_task() and 'cleanup_messages' in str(task):\n                    task.cancel()\n                    try:\n                        await task\n                    except asyncio.CancelledError:\n                        pass\n\n            # 关闭连接\n            await self.engine.dispose()\n            return True\n        except asyncio.CancelledError:\n            logging.warning(\"数据库关闭过程被取消，这可能是正常的关闭行为\")\n            return True\n        except Exception as e:\n            logging.error(f\"关闭数据库连接时出错: {str(e)}\")\n            return False\n\n    async def cleanup_messages(self):\n        \"\"\"每三天清理旧消息\"\"\"\n        while True:\n            async with self._async_session_factory() as session:\n                try:\n                    # 计算三天前的时间\n                    three_days_ago = datetime.now() - timedelta(days=3)\n                    # 删除三天前的消息\n                    await session.execute(\n                        delete(Message).where(Message.timestamp < three_days_ago)\n                    )\n                    await session.commit()\n                except Exception as e:\n                    logging.error(f\"清理消息失败: {str(e)}\")\n                    await session.rollback()\n            await asyncio.sleep(259200)  # 每三天（259200秒）执行一次\n\n    async def __aenter__(self):\n        # 启动清理消息的定时任务\n        asyncio.create_task(self.cleanup_messages())\n        return self\n\n    async def __aexit__(self, exc_type, exc_val, exc_tb):\n        await self.close()\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "services:\n  xybotv2:\n    image: henryxiaoyang/xybotv2:latest\n    container_name: XYBotV2\n    restart: on-failure:3\n    ports:\n      - \"9999:9999\"  # 映射gunicorn端口\n    volumes:\n      - xybotv2:/app\n      - redis_data:/var/lib/redis\n\nvolumes:\n  redis_data:\n    name: redis_data\n  xybotv2:\n    name: xybotv2"
  },
  {
    "path": "docs/.nojekyll",
    "content": ""
  },
  {
    "path": "docs/README.md",
    "content": "# 🤖 XYBot V2\n\nXYBot V2 是一个功能丰富的微信机器人框架,支持多种互动功能和游戏玩法。\n\n# 免责声明\n\n- 这个项目免费开源，不存在收费。\n- 本工具仅供学习和技术研究使用，不得用于任何商业或非法行为。\n- 本工具的作者不对本工具的安全性、完整性、可靠性、有效性、正确性或适用性做任何明示或暗示的保证，也不对本工具的使用或滥用造成的任何直接或间接的损失、责任、索赔、要求或诉讼承担任何责任。\n- 本工具的作者保留随时修改、更新、删除或终止本工具的权利，无需事先通知或承担任何义务。\n- 本工具的使用者应遵守相关法律法规，尊重微信的版权和隐私，不得侵犯微信或其他第三方的合法权益，不得从事任何违法或不道德的行为。\n- 本工具的使用者在下载、安装、运行或使用本工具时，即表示已阅读并同意本免责声明。如有异议，请立即停止使用本工具，并删除所有相关文件。\n\n# 💬 微信交流群\n\n<div style=\"text-align: center\" align=\"center\">\n    <img alt=\"微信交流群二维码\" src=\"https://qrcode.yangres.com/get_image\" style=\"width: 300px; height: auto;\">\n    <p>微信扫码加入交流群</p>\n    <a href=\"https://qrcode.yangres.com/get_image\">🔗图片会被缓存，点我查看最新二维码</a>\n</div>\n\n# 🙏 赞助\n\n<div style=\"text-align: center\" align=\"center\">\n    <h2>开源不易，请作者喝杯奶茶吧🙏</h2>\n    <img alt=\"微信收款码\" src=\"sponsor1.jpg\" style=\"width: 250px; height: auto;\">\n    <img alt=\"微信收款码\" src=\"sponsor2.jpg\" style=\"width: 250px; height: auto;\">\n</div>\n\n# ✨ 主要功能\n\n## 🛠️ 基础功能\n\n- 🤖 AI聊天 - 支持文字、图片、语音等多模态交互\n- 📰 每日新闻 - 自动推送每日新闻\n- 🎵 点歌系统 - 支持在线点歌\n- 🌤️ 天气查询 - 查询全国各地天气\n- 🎮 游戏功能 - 五子棋、战争雷霆玩家查询等\n\n## 💎 积分系统\n\n- 📝 每日签到 - 支持连续签到奖励\n- 🎲 抽奖系统 - 多种抽奖玩法\n- 🧧 红包系统 - 群内发积分红包\n- 💰 积分交易 - 用户间积分转账\n- 📊 积分排行 - 查看积分排名\n\n## 👮 管理功能\n\n- ⚙️ 插件管理 - 动态加载/卸载插件\n- 👥 白名单管理 - 控制机器人使用权限\n- 📊 积分管理 - 管理员可调整用户积分\n- 🔄 签到重置 - 重置所有用户签到状态\n\n# 🔌 插件系统\n\nXYBot V2 采用插件化设计,所有功能都以插件形式实现。主要插件包括:\n\n- 👨‍💼 AdminPoint - 积分管理\n- 🔄 AdminSignInReset - 签到重置\n- 🛡️ AdminWhitelist - 白名单管理\n- 🤖 Ai - AI聊天\n- 📊 BotStatus - 机器人状态\n- 📱 GetContact - 获取通讯录\n- 🌤️ GetWeather - 天气查询\n- 🎮 Gomoku - 五子棋游戏\n- 🌅 GoodMorning - 早安问候\n- 📈 Leaderboard - 积分排行\n- 🎲 LuckyDraw - 幸运抽奖\n- 📋 Menu - 菜单系统\n- 🎵 Music - 点歌系统\n- 📰 News - 新闻推送\n- 💱 PointTrade - 积分交易\n- 💰 QueryPoint - 积分查询\n- 🎯 RandomMember - 随机群成员\n- 🖼️ RandomPicture - 随机图片\n- 🧧 RedPacket - 红包系统\n- ✍️ SignIn - 每日签到\n- ✈️ Warthunder - 战争雷霆查询\n\n# 💻 代码提交\n\n提交代码时请使用 `feat: something` 作为说明，支持的标识如下:\n\n- `feat` 新功能(feature)\n- `fix` 修复bug\n- `docs` 文档(documentation)\n- `style` 格式(不影响代码运行的变动)\n- `ref` 重构(即不是新增功能，也不是修改bug的代码变动)\n- `perf` 性能优化(performance)\n- `test` 增加测试\n- `chore` 构建过程或辅助工具的变动\n- `revert` 撤销"
  },
  {
    "path": "docs/WechatAPIClient/_modules/WechatAPI/Client/base.html",
    "content": "<!doctype html>\n<html class=\"no-js\" data-content_root=\"../../../\" lang=\"zh-CN\">\n<head>\n    <meta charset=\"utf-8\"/>\n    <meta content=\"width=device-width,initial-scale=1\" name=\"viewport\"/>\n    <meta content=\"light dark\" name=\"color-scheme\">\n    <link href=\"../../../genindex.html\" rel=\"index\" title=\"索引\"/>\n    <link href=\"../../../search.html\" rel=\"search\" title=\"搜索\"/>\n\n    <!-- Generated with Sphinx 8.1.3 and Furo 2024.08.06 -->\n    <title>WechatAPI.Client.base - XYBotV2</title>\n    <link href=\"../../../_static/pygments.css?v=8f2a1f02\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo.css?v=354aac6f\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo-extensions.css?v=302659d7\" rel=\"stylesheet\" type=\"text/css\"/>\n\n\n    <style>\n        body {\n            --color-code-background: #f8f8f8;\n            --color-code-foreground: black;\n            --color-brand-primary: #2962ff;\n            --color-brand-content: #2962ff;\n\n        }\n\n        @media not print {\n            body[data-theme=\"dark\"] {\n                --color-code-background: #202020;\n                --color-code-foreground: #d0d0d0;\n\n            }\n\n            @media (prefers-color-scheme: dark) {\n                body:not([data-theme=\"light\"]) {\n                    --color-code-background: #202020;\n                    --color-code-foreground: #d0d0d0;\n\n                }\n            }\n        }\n    </style>\n</head>\n<body>\n\n<script>\n    document.body.dataset.theme = localStorage.getItem(\"theme\") || \"auto\";\n</script>\n\n\n<svg style=\"display: none;\" xmlns=\"http://www.w3.org/2000/svg\">\n    <symbol id=\"svg-toc\" viewBox=\"0 0 24 24\">\n        <title>Contents</title>\n        <svg fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 1024 1024\">\n            <path d=\"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-menu\" viewBox=\"0 0 24 24\">\n        <title>Menu</title>\n        <svg class=\"feather-menu\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-arrow-right\" viewBox=\"0 0 24 24\">\n        <title>Expand</title>\n        <svg class=\"feather-chevron-right\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <polyline points=\"9 18 15 12 9 6\"></polyline>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun\" viewBox=\"0 0 24 24\">\n        <title>Light mode</title>\n        <svg class=\"feather-sun\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\n            <line x1=\"12\" x2=\"12\" y1=\"1\" y2=\"3\"></line>\n            <line x1=\"12\" x2=\"12\" y1=\"21\" y2=\"23\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"4.22\" y2=\"5.64\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"18.36\" y2=\"19.78\"></line>\n            <line x1=\"1\" x2=\"3\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"21\" x2=\"23\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"19.78\" y2=\"18.36\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"5.64\" y2=\"4.22\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon\" viewBox=\"0 0 24 24\">\n        <title>Dark mode</title>\n        <svg class=\"icon-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun-with-moon\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in light mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 5.411 14.504 C 5.471 14.504 5.532 14.504 5.591 14.504 C 3.639 16.319 4.383 19.569 6.931 20.352 C 7.693 20.586 8.512 20.551 9.25 20.252 C 8.023 23.207 4.056 23.725 2.11 21.184 C 0.166 18.642 1.702 14.949 4.874 14.536 C 5.051 14.512 5.231 14.5 5.411 14.5 L 5.411 14.504 Z\"\n                  style=\"opacity: 50%\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"3.25\" y2=\"1.25\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"15.85\" y2=\"17.85\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"5.094\" y2=\"3.68\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"14.05\" y2=\"15.464\"/>\n            <line x1=\"8.2\" x2=\"6.2\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"20.8\" x2=\"22.8\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"14.006\" y2=\"15.42\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"5.05\" y2=\"3.636\"/>\n            <circle cx=\"14.5\" cy=\"9.55\" r=\"3.6\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon-with-sun\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in dark mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 8.282 7.007 C 8.385 7.007 8.494 7.007 8.595 7.007 C 5.18 10.184 6.481 15.869 10.942 17.24 C 12.275 17.648 13.706 17.589 15 17.066 C 12.851 22.236 5.91 23.143 2.505 18.696 C -0.897 14.249 1.791 7.786 7.342 7.063 C 7.652 7.021 7.965 7 8.282 7 L 8.282 7.007 Z\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"3.705\" y2=\"2.5\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"11.295\" y2=\"12.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"4.816\" y2=\"3.964\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"10.212\" y2=\"11.063\"/>\n            <line style=\"opacity: 50%\" x1=\"14.205\" x2=\"13.001\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"21.795\" x2=\"23\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"10.184\" y2=\"11.036\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"4.789\" y2=\"3.937\"/>\n            <circle cx=\"18\" cy=\"7.5\" r=\"2.169\" style=\"opacity: 50%\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-pencil\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-pencil-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4\"/>\n            <path d=\"M13.5 6.5l4 4\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-eye\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-eye-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\"/>\n            <path\n                    d=\"M11.11 17.958c-3.209 -.307 -5.91 -2.293 -8.11 -5.958c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6c-.21 .352 -.427 .688 -.647 1.008\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n</svg>\n\n<input class=\"sidebar-toggle\" id=\"__navigation\" name=\"__navigation\" type=\"checkbox\">\n<input class=\"sidebar-toggle\" id=\"__toc\" name=\"__toc\" type=\"checkbox\">\n<label class=\"overlay sidebar-overlay\" for=\"__navigation\">\n    <div class=\"visually-hidden\">Hide navigation sidebar</div>\n</label>\n<label class=\"overlay toc-overlay\" for=\"__toc\">\n    <div class=\"visually-hidden\">Hide table of contents sidebar</div>\n</label>\n\n<a class=\"skip-to-content muted-link\" href=\"#furo-main-content\">Skip to content</a>\n\n\n\n<div class=\"page\">\n    <header class=\"mobile-header\">\n        <div class=\"header-left\">\n            <label class=\"nav-overlay-icon\" for=\"__navigation\">\n                <div class=\"visually-hidden\">Toggle site navigation sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-menu\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n        <div class=\"header-center\">\n            <a href=\"../../../index.html\">\n                <div class=\"brand\">XYBotV2</div>\n            </a>\n        </div>\n        <div class=\"header-right\">\n            <div class=\"theme-toggle-container theme-toggle-header\">\n                <button class=\"theme-toggle\">\n                    <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                    <svg class=\"theme-icon-when-auto-light\">\n                        <use href=\"#svg-sun-with-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-auto-dark\">\n                        <use href=\"#svg-moon-with-sun\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-dark\">\n                        <use href=\"#svg-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-light\">\n                        <use href=\"#svg-sun\"></use>\n                    </svg>\n                </button>\n            </div>\n            <label class=\"toc-overlay-icon toc-header-icon no-toc\" for=\"__toc\">\n                <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-toc\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n    </header>\n    <aside class=\"sidebar-drawer\">\n        <div class=\"sidebar-container\">\n\n            <div class=\"sidebar-sticky\">\n                <div class=\"sidebar-scroll\"><a class=\"sidebar-brand\" href=\"../../../index.html\">\n\n\n                    <span class=\"sidebar-brand-text\">XYBotV2</span>\n\n                </a>\n                    <form action=\"../../../search.html\" class=\"sidebar-search-container\" method=\"get\" role=\"search\">\n                        <input aria-label=\"搜索\" class=\"sidebar-search\" name=\"q\" placeholder=\"搜索\">\n                        <input name=\"check_keywords\" type=\"hidden\" value=\"yes\">\n                        <input name=\"area\" type=\"hidden\" value=\"default\">\n                    </form>\n                    <div id=\"searchbox\"></div>\n                    <div class=\"sidebar-tree\">\n\n                    </div>\n                    <div class=\"sidebar-tree\">\n                        <p class=\"caption\"><span class=\"caption-text\">重要函数导航</span></p>\n                        <ul>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">登录</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.is_running\">检查WechatAPI是否在运行</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_qr_code\">获取登录二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.awaken_login\">二次登录(唤醒登录)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.check_login_uuid\">检查登录的UUID状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_cached_info\">获取登录缓存信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.log_out\">登出当前账号</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.heartbeat\">发送心跳包</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\">开始自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\">停止自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\">获取自动心跳状态</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">消息</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.sync_message\">同步消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_text_message\">发送文本消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_image_message\">发送图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_voice_message\">发送语音消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_video_message\">发送视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_link_message\">发送链接消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_card_message\">发送名片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_app_message\">发送应用(xml)消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_emoji_message\">发送表情消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\">转发图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\">转发视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\">转发文件消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.revoke_message\">撤回消息</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">用户</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_my_qrcode\">获取个人二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_profile\">获取用户信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.is_logged_in\">检查是否登录</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">群聊</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\">获取群聊信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\">获取群聊公告</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\">获取群聊成员列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\">获取群聊二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\">添加群成员(群聊最多40人)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\">邀请群聊成员(群聊大于40人)</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">好友</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contact\">获取联系人信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_detail\">获取联系人详情</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_list\">获取联系人列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_nickname\">获取用户昵称</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.accept_friend\">接受好友请求</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">红包</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\">获取红包详情</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">工具</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.check_database\">检查数据库状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.set_step\">设置步数</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_image\">下载高清图片</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_video\">下载视频</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_voice\">下载语音文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_attach\">下载附件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_byte\">base64转字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_file\">base64转文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.byte_to_base64\">字节转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.file_to_base64\">文件转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\">silk的base64转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\">silk字节转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\">WAV字节转AMR的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\">WAV字节转AMR字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\">WAV字节转silk的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\">WAV字节转silk字节</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                        </ul>\n                    </div>\n                </div>\n            </div>\n\n        </div>\n    </aside>\n    <div class=\"main\">\n        <div class=\"content\">\n            <div class=\"article-container\">\n                <a class=\"back-to-top muted-link\" href=\"#\">\n                    <svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n                        <path d=\"M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z\"></path>\n                    </svg>\n                    <span>Back to top</span>\n                </a>\n                <div class=\"content-icon-container\">\n                    <div class=\"theme-toggle-container theme-toggle-content\">\n                        <button class=\"theme-toggle\">\n                            <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                            <svg class=\"theme-icon-when-auto-light\">\n                                <use href=\"#svg-sun-with-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-auto-dark\">\n                                <use href=\"#svg-moon-with-sun\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-dark\">\n                                <use href=\"#svg-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-light\">\n                                <use href=\"#svg-sun\"></use>\n                            </svg>\n                        </button>\n                    </div>\n                    <label class=\"toc-overlay-icon toc-content-icon no-toc\" for=\"__toc\">\n                        <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                        <i class=\"icon\">\n                            <svg>\n                                <use href=\"#svg-toc\"></use>\n                            </svg>\n                        </i>\n                    </label>\n                </div>\n                <article id=\"furo-main-content\" role=\"main\">\n                    <h1>WechatAPI.Client.base 源代码</h1>\n                    <div class=\"highlight\"><pre>\n<span></span><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">dataclasses</span><span\n                            class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">dataclass</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">WechatAPI.errors</span><span\n                            class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"o\">*</span>\n\n\n<div class=\"viewcode-block\" id=\"Proxy\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.base.Proxy\">[文档]</a>\n<span class=\"nd\">@dataclass</span>\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">Proxy</span><span class=\"p\">:</span>\n<span class=\"w\">    </span><span class=\"sd\">&quot;&quot;&quot;代理(无效果，别用！)</span>\n\n<span class=\"sd\">    Args:</span>\n<span class=\"sd\">        ip (str): 代理服务器IP地址</span>\n<span class=\"sd\">        port (int): 代理服务器端口</span>\n<span class=\"sd\">        username (str, optional): 代理认证用户名. 默认为空字符串</span>\n<span class=\"sd\">        password (str, optional): 代理认证密码. 默认为空字符串</span>\n<span class=\"sd\">    &quot;&quot;&quot;</span>\n    <span class=\"n\">ip</span><span class=\"p\">:</span> <span class=\"nb\">str</span>\n    <span class=\"n\">port</span><span class=\"p\">:</span> <span class=\"nb\">int</span>\n    <span class=\"n\">username</span><span class=\"p\">:</span> <span class=\"nb\">str</span> <span class=\"o\">=</span> <span\n        class=\"s2\">&quot;&quot;</span>\n    <span class=\"n\">password</span><span class=\"p\">:</span> <span class=\"nb\">str</span> <span class=\"o\">=</span> <span\n        class=\"s2\">&quot;&quot;</span></div>\n\n\n\n<div class=\"viewcode-block\" id=\"Section\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.base.Section\">[文档]</a>\n<span class=\"nd\">@dataclass</span>\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">Section</span><span class=\"p\">:</span>\n<span class=\"w\">    </span><span class=\"sd\">&quot;&quot;&quot;数据段配置类</span>\n\n<span class=\"sd\">    Args:</span>\n<span class=\"sd\">        data_len (int): 数据长度</span>\n<span class=\"sd\">        start_pos (int): 起始位置</span>\n<span class=\"sd\">    &quot;&quot;&quot;</span>\n    <span class=\"n\">data_len</span><span class=\"p\">:</span> <span class=\"nb\">int</span>\n    <span class=\"n\">start_pos</span><span class=\"p\">:</span> <span class=\"nb\">int</span></div>\n\n\n\n<div class=\"viewcode-block\" id=\"WechatAPIClientBase\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.base.WechatAPIClientBase\">[文档]</a>\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">WechatAPIClientBase</span><span class=\"p\">:</span>\n<span class=\"w\">    </span><span class=\"sd\">&quot;&quot;&quot;微信API客户端基类</span>\n\n<span class=\"sd\">    Args:</span>\n<span class=\"sd\">        ip (str): 服务器IP地址</span>\n<span class=\"sd\">        port (int): 服务器端口</span>\n\n<span class=\"sd\">    Attributes:</span>\n<span class=\"sd\">        wxid (str): 微信ID</span>\n<span class=\"sd\">        nickname (str): 昵称</span>\n<span class=\"sd\">        alias (str): 别名</span>\n<span class=\"sd\">        phone (str): 手机号</span>\n<span class=\"sd\">        ignore_protect (bool): 是否忽略保护机制</span>\n<span class=\"sd\">    &quot;&quot;&quot;</span>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"fm\">__init__</span><span class=\"p\">(</span><span\n        class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">ip</span><span class=\"p\">:</span> <span\n        class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">port</span><span class=\"p\">:</span> <span\n        class=\"nb\">int</span><span class=\"p\">):</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span> <span class=\"o\">=</span> <span\n        class=\"n\">ip</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">port</span> <span class=\"o\">=</span> <span\n        class=\"n\">port</span>\n\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span> <span class=\"o\">=</span> <span\n        class=\"s2\">&quot;&quot;</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">nickname</span> <span\n        class=\"o\">=</span> <span class=\"s2\">&quot;&quot;</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">alias</span> <span class=\"o\">=</span> <span\n        class=\"s2\">&quot;&quot;</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">phone</span> <span class=\"o\">=</span> <span\n        class=\"s2\">&quot;&quot;</span>\n\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span\n        class=\"o\">=</span> <span class=\"kc\">False</span>\n\n        <span class=\"c1\"># 调用所有 Mixin 的初始化方法</span>\n        <span class=\"nb\">super</span><span class=\"p\">()</span><span class=\"o\">.</span><span\n        class=\"fm\">__init__</span><span class=\"p\">()</span>\n\n<div class=\"viewcode-block\" id=\"WechatAPIClientBase.error_handler\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.base.WechatAPIClientBase.error_handler\">[文档]</a>\n    <span class=\"nd\">@staticmethod</span>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;处理API响应中的错误码</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            json_resp (dict): API响应的JSON数据</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            ValueError: 参数错误时抛出</span>\n<span class=\"sd\">            MarshallingError: 序列化错误时抛出</span>\n<span class=\"sd\">            UnmarshallingError: 反序列化错误时抛出</span>\n<span class=\"sd\">            MMTLSError: MMTLS初始化错误时抛出</span>\n<span class=\"sd\">            PacketError: 数据包长度错误时抛出</span>\n<span class=\"sd\">            UserLoggedOut: 用户已退出登录时抛出</span>\n<span class=\"sd\">            ParsePacketError: 解析数据包错误时抛出</span>\n<span class=\"sd\">            DatabaseError: 数据库错误时抛出</span>\n<span class=\"sd\">            Exception: 其他类型错误时抛出</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"n\">code</span> <span class=\"o\">=</span> <span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Code&quot;</span><span class=\"p\">)</span>\n        <span class=\"k\">if</span> <span class=\"n\">code</span> <span class=\"o\">==</span> <span class=\"o\">-</span><span\n        class=\"mi\">1</span><span class=\"p\">:</span>  <span class=\"c1\"># 参数错误</span>\n            <span class=\"k\">raise</span> <span class=\"ne\">ValueError</span><span class=\"p\">(</span><span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Message&quot;</span><span class=\"p\">))</span>\n        <span class=\"k\">elif</span> <span class=\"n\">code</span> <span class=\"o\">==</span> <span class=\"o\">-</span><span\n        class=\"mi\">2</span><span class=\"p\">:</span>  <span class=\"c1\"># 其他错误</span>\n            <span class=\"k\">raise</span> <span class=\"ne\">Exception</span><span class=\"p\">(</span><span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Message&quot;</span><span class=\"p\">))</span>\n        <span class=\"k\">elif</span> <span class=\"n\">code</span> <span class=\"o\">==</span> <span class=\"o\">-</span><span\n        class=\"mi\">3</span><span class=\"p\">:</span>  <span class=\"c1\"># 序列化错误</span>\n            <span class=\"k\">raise</span> <span class=\"n\">MarshallingError</span><span class=\"p\">(</span><span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Message&quot;</span><span class=\"p\">))</span>\n        <span class=\"k\">elif</span> <span class=\"n\">code</span> <span class=\"o\">==</span> <span class=\"o\">-</span><span\n        class=\"mi\">4</span><span class=\"p\">:</span>  <span class=\"c1\"># 反序列化错误</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UnmarshallingError</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Message&quot;</span><span class=\"p\">))</span>\n        <span class=\"k\">elif</span> <span class=\"n\">code</span> <span class=\"o\">==</span> <span class=\"o\">-</span><span\n        class=\"mi\">5</span><span class=\"p\">:</span>  <span class=\"c1\"># MMTLS初始化错误</span>\n            <span class=\"k\">raise</span> <span class=\"n\">MMTLSError</span><span class=\"p\">(</span><span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Message&quot;</span><span class=\"p\">))</span>\n        <span class=\"k\">elif</span> <span class=\"n\">code</span> <span class=\"o\">==</span> <span class=\"o\">-</span><span\n        class=\"mi\">6</span><span class=\"p\">:</span>  <span class=\"c1\"># 收到的数据包长度错误</span>\n            <span class=\"k\">raise</span> <span class=\"n\">PacketError</span><span class=\"p\">(</span><span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Message&quot;</span><span class=\"p\">))</span>\n        <span class=\"k\">elif</span> <span class=\"n\">code</span> <span class=\"o\">==</span> <span class=\"o\">-</span><span\n        class=\"mi\">7</span><span class=\"p\">:</span>  <span class=\"c1\"># 已退出登录</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;Already logged out&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"n\">code</span> <span class=\"o\">==</span> <span class=\"o\">-</span><span\n        class=\"mi\">8</span><span class=\"p\">:</span>  <span class=\"c1\"># 链接过期</span>\n            <span class=\"k\">raise</span> <span class=\"ne\">Exception</span><span class=\"p\">(</span><span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Message&quot;</span><span class=\"p\">))</span>\n        <span class=\"k\">elif</span> <span class=\"n\">code</span> <span class=\"o\">==</span> <span class=\"o\">-</span><span\n        class=\"mi\">9</span><span class=\"p\">:</span>  <span class=\"c1\"># 解析数据包错误</span>\n            <span class=\"k\">raise</span> <span class=\"n\">ParsePacketError</span><span class=\"p\">(</span><span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Message&quot;</span><span class=\"p\">))</span>\n        <span class=\"k\">elif</span> <span class=\"n\">code</span> <span class=\"o\">==</span> <span class=\"o\">-</span><span\n        class=\"mi\">10</span><span class=\"p\">:</span>  <span class=\"c1\"># 数据库错误</span>\n            <span class=\"k\">raise</span> <span class=\"n\">DatabaseError</span><span class=\"p\">(</span><span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Message&quot;</span><span class=\"p\">))</span>\n        <span class=\"k\">elif</span> <span class=\"n\">code</span> <span class=\"o\">==</span> <span class=\"o\">-</span><span\n        class=\"mi\">11</span><span class=\"p\">:</span>  <span class=\"c1\"># 登陆异常</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Message&quot;</span><span class=\"p\">))</span>\n        <span class=\"k\">elif</span> <span class=\"n\">code</span> <span class=\"o\">==</span> <span class=\"o\">-</span><span\n        class=\"mi\">12</span><span class=\"p\">:</span>  <span class=\"c1\"># 操作过于频繁</span>\n            <span class=\"k\">raise</span> <span class=\"ne\">Exception</span><span class=\"p\">(</span><span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Message&quot;</span><span class=\"p\">))</span>\n        <span class=\"k\">elif</span> <span class=\"n\">code</span> <span class=\"o\">==</span> <span class=\"o\">-</span><span\n        class=\"mi\">13</span><span class=\"p\">:</span>  <span class=\"c1\"># 上传失败</span>\n            <span class=\"k\">raise</span> <span class=\"ne\">Exception</span><span class=\"p\">(</span><span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Message&quot;</span><span class=\"p\">))</span></div>\n</div>\n\n</pre>\n                    </div>\n                </article>\n            </div>\n            <footer>\n\n                <div class=\"related-pages\">\n\n\n                </div>\n                <div class=\"bottom-of-page\">\n                    <div class=\"left-details\">\n                        <div class=\"copyright\">\n                            Copyright &#169; 2025, HenryXiaoYang\n                        </div>\n                        Made with <a href=\"https://www.sphinx-doc.org/\">Sphinx</a> and <a class=\"muted-link\"\n                                                                                          href=\"https://pradyunsg.me\">@pradyunsg</a>'s\n\n                        <a href=\"https://github.com/pradyunsg/furo\">Furo</a>\n\n                    </div>\n                    <div class=\"right-details\">\n\n                    </div>\n                </div>\n\n            </footer>\n        </div>\n        <aside class=\"toc-drawer no-toc\">\n\n\n        </aside>\n    </div>\n</div>\n<script src=\"../../../_static/documentation_options.js?v=91bfbbb6\"></script>\n<script src=\"../../../_static/doctools.js?v=9bcbadda\"></script>\n<script src=\"../../../_static/sphinx_highlight.js?v=dc90522c\"></script>\n<script src=\"../../../_static/scripts/furo.js?v=5fa4622c\"></script>\n<script src=\"../../../_static/translations.js?v=beaddf03\"></script>\n</body>\n</html>"
  },
  {
    "path": "docs/WechatAPIClient/_modules/WechatAPI/Client/chatroom.html",
    "content": "<!doctype html>\n<html class=\"no-js\" data-content_root=\"../../../\" lang=\"zh-CN\">\n<head>\n    <meta charset=\"utf-8\"/>\n    <meta content=\"width=device-width,initial-scale=1\" name=\"viewport\"/>\n    <meta content=\"light dark\" name=\"color-scheme\">\n    <link href=\"../../../genindex.html\" rel=\"index\" title=\"索引\"/>\n    <link href=\"../../../search.html\" rel=\"search\" title=\"搜索\"/>\n\n    <!-- Generated with Sphinx 8.1.3 and Furo 2024.08.06 -->\n    <title>WechatAPI.Client.chatroom - XYBotV2</title>\n    <link href=\"../../../_static/pygments.css?v=8f2a1f02\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo.css?v=354aac6f\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo-extensions.css?v=302659d7\" rel=\"stylesheet\" type=\"text/css\"/>\n\n\n    <style>\n        body {\n            --color-code-background: #f8f8f8;\n            --color-code-foreground: black;\n            --color-brand-primary: #2962ff;\n            --color-brand-content: #2962ff;\n\n        }\n\n        @media not print {\n            body[data-theme=\"dark\"] {\n                --color-code-background: #202020;\n                --color-code-foreground: #d0d0d0;\n\n            }\n\n            @media (prefers-color-scheme: dark) {\n                body:not([data-theme=\"light\"]) {\n                    --color-code-background: #202020;\n                    --color-code-foreground: #d0d0d0;\n\n                }\n            }\n        }\n    </style>\n</head>\n<body>\n\n<script>\n    document.body.dataset.theme = localStorage.getItem(\"theme\") || \"auto\";\n</script>\n\n\n<svg style=\"display: none;\" xmlns=\"http://www.w3.org/2000/svg\">\n    <symbol id=\"svg-toc\" viewBox=\"0 0 24 24\">\n        <title>Contents</title>\n        <svg fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 1024 1024\">\n            <path d=\"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-menu\" viewBox=\"0 0 24 24\">\n        <title>Menu</title>\n        <svg class=\"feather-menu\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-arrow-right\" viewBox=\"0 0 24 24\">\n        <title>Expand</title>\n        <svg class=\"feather-chevron-right\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <polyline points=\"9 18 15 12 9 6\"></polyline>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun\" viewBox=\"0 0 24 24\">\n        <title>Light mode</title>\n        <svg class=\"feather-sun\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\n            <line x1=\"12\" x2=\"12\" y1=\"1\" y2=\"3\"></line>\n            <line x1=\"12\" x2=\"12\" y1=\"21\" y2=\"23\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"4.22\" y2=\"5.64\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"18.36\" y2=\"19.78\"></line>\n            <line x1=\"1\" x2=\"3\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"21\" x2=\"23\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"19.78\" y2=\"18.36\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"5.64\" y2=\"4.22\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon\" viewBox=\"0 0 24 24\">\n        <title>Dark mode</title>\n        <svg class=\"icon-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun-with-moon\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in light mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 5.411 14.504 C 5.471 14.504 5.532 14.504 5.591 14.504 C 3.639 16.319 4.383 19.569 6.931 20.352 C 7.693 20.586 8.512 20.551 9.25 20.252 C 8.023 23.207 4.056 23.725 2.11 21.184 C 0.166 18.642 1.702 14.949 4.874 14.536 C 5.051 14.512 5.231 14.5 5.411 14.5 L 5.411 14.504 Z\"\n                  style=\"opacity: 50%\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"3.25\" y2=\"1.25\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"15.85\" y2=\"17.85\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"5.094\" y2=\"3.68\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"14.05\" y2=\"15.464\"/>\n            <line x1=\"8.2\" x2=\"6.2\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"20.8\" x2=\"22.8\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"14.006\" y2=\"15.42\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"5.05\" y2=\"3.636\"/>\n            <circle cx=\"14.5\" cy=\"9.55\" r=\"3.6\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon-with-sun\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in dark mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 8.282 7.007 C 8.385 7.007 8.494 7.007 8.595 7.007 C 5.18 10.184 6.481 15.869 10.942 17.24 C 12.275 17.648 13.706 17.589 15 17.066 C 12.851 22.236 5.91 23.143 2.505 18.696 C -0.897 14.249 1.791 7.786 7.342 7.063 C 7.652 7.021 7.965 7 8.282 7 L 8.282 7.007 Z\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"3.705\" y2=\"2.5\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"11.295\" y2=\"12.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"4.816\" y2=\"3.964\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"10.212\" y2=\"11.063\"/>\n            <line style=\"opacity: 50%\" x1=\"14.205\" x2=\"13.001\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"21.795\" x2=\"23\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"10.184\" y2=\"11.036\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"4.789\" y2=\"3.937\"/>\n            <circle cx=\"18\" cy=\"7.5\" r=\"2.169\" style=\"opacity: 50%\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-pencil\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-pencil-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4\"/>\n            <path d=\"M13.5 6.5l4 4\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-eye\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-eye-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\"/>\n            <path\n                    d=\"M11.11 17.958c-3.209 -.307 -5.91 -2.293 -8.11 -5.958c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6c-.21 .352 -.427 .688 -.647 1.008\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n</svg>\n\n<input class=\"sidebar-toggle\" id=\"__navigation\" name=\"__navigation\" type=\"checkbox\">\n<input class=\"sidebar-toggle\" id=\"__toc\" name=\"__toc\" type=\"checkbox\">\n<label class=\"overlay sidebar-overlay\" for=\"__navigation\">\n    <div class=\"visually-hidden\">Hide navigation sidebar</div>\n</label>\n<label class=\"overlay toc-overlay\" for=\"__toc\">\n    <div class=\"visually-hidden\">Hide table of contents sidebar</div>\n</label>\n\n<a class=\"skip-to-content muted-link\" href=\"#furo-main-content\">Skip to content</a>\n\n\n\n<div class=\"page\">\n    <header class=\"mobile-header\">\n        <div class=\"header-left\">\n            <label class=\"nav-overlay-icon\" for=\"__navigation\">\n                <div class=\"visually-hidden\">Toggle site navigation sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-menu\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n        <div class=\"header-center\">\n            <a href=\"../../../index.html\">\n                <div class=\"brand\">XYBotV2</div>\n            </a>\n        </div>\n        <div class=\"header-right\">\n            <div class=\"theme-toggle-container theme-toggle-header\">\n                <button class=\"theme-toggle\">\n                    <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                    <svg class=\"theme-icon-when-auto-light\">\n                        <use href=\"#svg-sun-with-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-auto-dark\">\n                        <use href=\"#svg-moon-with-sun\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-dark\">\n                        <use href=\"#svg-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-light\">\n                        <use href=\"#svg-sun\"></use>\n                    </svg>\n                </button>\n            </div>\n            <label class=\"toc-overlay-icon toc-header-icon no-toc\" for=\"__toc\">\n                <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-toc\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n    </header>\n    <aside class=\"sidebar-drawer\">\n        <div class=\"sidebar-container\">\n\n            <div class=\"sidebar-sticky\">\n                <div class=\"sidebar-scroll\"><a class=\"sidebar-brand\" href=\"../../../index.html\">\n\n\n                    <span class=\"sidebar-brand-text\">XYBotV2</span>\n\n                </a>\n                    <form action=\"../../../search.html\" class=\"sidebar-search-container\" method=\"get\" role=\"search\">\n                        <input aria-label=\"搜索\" class=\"sidebar-search\" name=\"q\" placeholder=\"搜索\">\n                        <input name=\"check_keywords\" type=\"hidden\" value=\"yes\">\n                        <input name=\"area\" type=\"hidden\" value=\"default\">\n                    </form>\n                    <div id=\"searchbox\"></div>\n                    <div class=\"sidebar-tree\">\n\n                    </div>\n                    <div class=\"sidebar-tree\">\n                        <p class=\"caption\"><span class=\"caption-text\">重要函数导航</span></p>\n                        <ul>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">登录</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.is_running\">检查WechatAPI是否在运行</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_qr_code\">获取登录二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.awaken_login\">二次登录(唤醒登录)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.check_login_uuid\">检查登录的UUID状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_cached_info\">获取登录缓存信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.log_out\">登出当前账号</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.heartbeat\">发送心跳包</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\">开始自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\">停止自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\">获取自动心跳状态</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">消息</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.sync_message\">同步消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_text_message\">发送文本消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_image_message\">发送图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_voice_message\">发送语音消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_video_message\">发送视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_link_message\">发送链接消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_card_message\">发送名片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_app_message\">发送应用(xml)消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_emoji_message\">发送表情消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\">转发图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\">转发视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\">转发文件消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.revoke_message\">撤回消息</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">用户</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_my_qrcode\">获取个人二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_profile\">获取用户信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.is_logged_in\">检查是否登录</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">群聊</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\">获取群聊信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\">获取群聊公告</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\">获取群聊成员列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\">获取群聊二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\">添加群成员(群聊最多40人)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\">邀请群聊成员(群聊大于40人)</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">好友</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contact\">获取联系人信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_detail\">获取联系人详情</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_list\">获取联系人列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_nickname\">获取用户昵称</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.accept_friend\">接受好友请求</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">红包</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\">获取红包详情</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">工具</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.check_database\">检查数据库状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.set_step\">设置步数</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_image\">下载高清图片</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_video\">下载视频</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_voice\">下载语音文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_attach\">下载附件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_byte\">base64转字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_file\">base64转文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.byte_to_base64\">字节转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.file_to_base64\">文件转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\">silk的base64转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\">silk字节转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\">WAV字节转AMR的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\">WAV字节转AMR字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\">WAV字节转silk的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\">WAV字节转silk字节</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                        </ul>\n                    </div>\n                </div>\n            </div>\n\n        </div>\n    </aside>\n    <div class=\"main\">\n        <div class=\"content\">\n            <div class=\"article-container\">\n                <a class=\"back-to-top muted-link\" href=\"#\">\n                    <svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n                        <path d=\"M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z\"></path>\n                    </svg>\n                    <span>Back to top</span>\n                </a>\n                <div class=\"content-icon-container\">\n                    <div class=\"theme-toggle-container theme-toggle-content\">\n                        <button class=\"theme-toggle\">\n                            <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                            <svg class=\"theme-icon-when-auto-light\">\n                                <use href=\"#svg-sun-with-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-auto-dark\">\n                                <use href=\"#svg-moon-with-sun\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-dark\">\n                                <use href=\"#svg-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-light\">\n                                <use href=\"#svg-sun\"></use>\n                            </svg>\n                        </button>\n                    </div>\n                    <label class=\"toc-overlay-icon toc-content-icon no-toc\" for=\"__toc\">\n                        <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                        <i class=\"icon\">\n                            <svg>\n                                <use href=\"#svg-toc\"></use>\n                            </svg>\n                        </i>\n                    </label>\n                </div>\n                <article id=\"furo-main-content\" role=\"main\">\n                    <h1>WechatAPI.Client.chatroom 源代码</h1>\n                    <div class=\"highlight\"><pre>\n<span></span><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">typing</span><span\n                            class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">Union</span><span\n                            class=\"p\">,</span> <span class=\"n\">Any</span>\n\n<span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">aiohttp</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.base</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"o\">*</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.protect</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"n\">protector</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">..errors</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"o\">*</span>\n\n\n<div class=\"viewcode-block\" id=\"ChatroomMixin\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.chatroom.ChatroomMixin\">[文档]</a>\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">ChatroomMixin</span><span class=\"p\">(</span><span\n        class=\"n\">WechatAPIClientBase</span><span class=\"p\">):</span>\n<div class=\"viewcode-block\" id=\"ChatroomMixin.add_chatroom_member\">\n<a class=\"viewcode-back\"\n   href=\"../../../index.html#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">add_chatroom_member</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">,</span> <span class=\"n\">chatroom</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">bool</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;添加群成员(群聊最多40人)</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            chatroom: 群聊wxid</span>\n<span class=\"sd\">            wxid: 要添加的wxid</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bool: 成功返回True, 失败False或者报错</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span class=\"ow\">and</span> <span\n        class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span class=\"p\">(</span><span\n        class=\"mi\">14400</span><span class=\"p\">):</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;风控保护: 新设备登录后4小时内请挂机&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Chatroom&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">chatroom</span><span class=\"p\">,</span> <span class=\"s2\">&quot;InviteWxids&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">wxid</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/AddChatroomMember&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"kc\">True</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ChatroomMixin.get_chatroom_announce\">\n<a class=\"viewcode-back\"\n   href=\"../../../index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">get_chatroom_announce</span><span\n        class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">chatroom</span><span\n        class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span\n        class=\"nb\">dict</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;获取群聊公告</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            chatroom: 群聊id</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            dict: 群聊信息字典</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Chatroom&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">chatroom</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/GetChatroomInfo&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"nb\">dict</span><span\n        class=\"p\">(</span><span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">))</span>\n                <span class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">pop</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;BaseResponse&quot;</span><span class=\"p\">)</span>\n                <span class=\"k\">return</span> <span class=\"n\">data</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ChatroomMixin.get_chatroom_info\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">get_chatroom_info</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">chatroom</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span\n        class=\"o\">-&gt;</span> <span class=\"nb\">dict</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;获取群聊信息</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            chatroom: 群聊id</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            dict: 群聊信息字典</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span class=\"ow\">and</span> <span\n        class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span class=\"p\">(</span><span\n        class=\"mi\">14400</span><span class=\"p\">):</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;风控保护: 新设备登录后4小时内请挂机&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Chatroom&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">chatroom</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span class=\"s1\">/GetChatroomInfoNoAnnounce&#39;</span><span\n        class=\"p\">,</span> <span class=\"n\">json</span><span class=\"o\">=</span><span class=\"n\">json_param</span><span\n        class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;ContactList&quot;</span><span\n        class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">]</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ChatroomMixin.get_chatroom_member_list\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">get_chatroom_member_list</span><span\n        class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">chatroom</span><span\n        class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span\n        class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">dict</span><span class=\"p\">]:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;获取群聊成员列表</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            chatroom: 群聊id</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            list[dict]: 群聊成员列表</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Chatroom&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">chatroom</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span class=\"s1\">/GetChatroomMemberDetail&#39;</span><span\n        class=\"p\">,</span> <span class=\"n\">json</span><span class=\"o\">=</span><span class=\"n\">json_param</span><span\n        class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;NewChatroomData&quot;</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;ChatRoomMember&quot;</span><span\n        class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ChatroomMixin.get_chatroom_qrcode\">\n<a class=\"viewcode-back\"\n   href=\"../../../index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">get_chatroom_qrcode</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">,</span> <span class=\"n\">chatroom</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">dict</span><span class=\"p\">[</span><span\n        class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">Any</span><span class=\"p\">]:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;获取群聊二维码</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            chatroom: 群聊id</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            dict: {&quot;base64&quot;: 二维码的base64, &quot;description&quot;: 二维码描述}</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span class=\"ow\">and</span> <span\n        class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span class=\"p\">(</span><span\n        class=\"mi\">86400</span><span class=\"p\">):</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;获取二维码需要在登录后24小时才可使用&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Chatroom&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">chatroom</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/GetChatroomQRCode&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span>\n                <span class=\"k\">return</span> <span class=\"p\">{</span><span class=\"s2\">&quot;base64&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;qrcode&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;buffer&quot;</span><span class=\"p\">),</span> <span\n        class=\"s2\">&quot;description&quot;</span><span class=\"p\">:</span> <span class=\"n\">data</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;revokeQrcodeWording&quot;</span><span\n        class=\"p\">)}</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ChatroomMixin.invite_chatroom_member\">\n<a class=\"viewcode-back\"\n   href=\"../../../index.html#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">invite_chatroom_member</span><span\n        class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">wxid</span><span\n        class=\"p\">:</span> <span class=\"n\">Union</span><span class=\"p\">[</span><span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"nb\">list</span><span class=\"p\">],</span> <span class=\"n\">chatroom</span><span\n        class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span\n        class=\"nb\">bool</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;邀请群聊成员(群聊大于40人)</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid: 要邀请的用户wxid或wxid列表</span>\n<span class=\"sd\">            chatroom: 群聊id</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bool: 成功返回True, 失败False或者报错</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span class=\"ow\">and</span> <span\n        class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span class=\"p\">(</span><span\n        class=\"mi\">14400</span><span class=\"p\">):</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;风控保护: 新设备登录后4小时内请挂机&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">if</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"nb\">list</span><span class=\"p\">):</span>\n            <span class=\"n\">wxid</span> <span class=\"o\">=</span> <span class=\"s2\">&quot;,&quot;</span><span\n        class=\"o\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">wxid</span><span class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Chatroom&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">chatroom</span><span class=\"p\">,</span> <span class=\"s2\">&quot;InviteWxids&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">wxid</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span class=\"s1\">/InviteChatroomMember&#39;</span><span\n        class=\"p\">,</span> <span class=\"n\">json</span><span class=\"o\">=</span><span class=\"n\">json_param</span><span\n        class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"kc\">True</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n</div>\n\n</pre>\n                    </div>\n                </article>\n            </div>\n            <footer>\n\n                <div class=\"related-pages\">\n\n\n                </div>\n                <div class=\"bottom-of-page\">\n                    <div class=\"left-details\">\n                        <div class=\"copyright\">\n                            Copyright &#169; 2025, HenryXiaoYang\n                        </div>\n                        Made with <a href=\"https://www.sphinx-doc.org/\">Sphinx</a> and <a class=\"muted-link\"\n                                                                                          href=\"https://pradyunsg.me\">@pradyunsg</a>'s\n\n                        <a href=\"https://github.com/pradyunsg/furo\">Furo</a>\n\n                    </div>\n                    <div class=\"right-details\">\n\n                    </div>\n                </div>\n\n            </footer>\n        </div>\n        <aside class=\"toc-drawer no-toc\">\n\n\n        </aside>\n    </div>\n</div>\n<script src=\"../../../_static/documentation_options.js?v=91bfbbb6\"></script>\n<script src=\"../../../_static/doctools.js?v=9bcbadda\"></script>\n<script src=\"../../../_static/sphinx_highlight.js?v=dc90522c\"></script>\n<script src=\"../../../_static/scripts/furo.js?v=5fa4622c\"></script>\n<script src=\"../../../_static/translations.js?v=beaddf03\"></script>\n</body>\n</html>"
  },
  {
    "path": "docs/WechatAPIClient/_modules/WechatAPI/Client/friend.html",
    "content": "<!doctype html>\n<html class=\"no-js\" data-content_root=\"../../../\" lang=\"zh-CN\">\n<head>\n    <meta charset=\"utf-8\"/>\n    <meta content=\"width=device-width,initial-scale=1\" name=\"viewport\"/>\n    <meta content=\"light dark\" name=\"color-scheme\">\n    <link href=\"../../../genindex.html\" rel=\"index\" title=\"索引\"/>\n    <link href=\"../../../search.html\" rel=\"search\" title=\"搜索\"/>\n\n    <!-- Generated with Sphinx 8.1.3 and Furo 2024.08.06 -->\n    <title>WechatAPI.Client.friend - XYBotV2</title>\n    <link href=\"../../../_static/pygments.css?v=8f2a1f02\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo.css?v=354aac6f\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo-extensions.css?v=302659d7\" rel=\"stylesheet\" type=\"text/css\"/>\n\n\n    <style>\n        body {\n            --color-code-background: #f8f8f8;\n            --color-code-foreground: black;\n            --color-brand-primary: #2962ff;\n            --color-brand-content: #2962ff;\n\n        }\n\n        @media not print {\n            body[data-theme=\"dark\"] {\n                --color-code-background: #202020;\n                --color-code-foreground: #d0d0d0;\n\n            }\n\n            @media (prefers-color-scheme: dark) {\n                body:not([data-theme=\"light\"]) {\n                    --color-code-background: #202020;\n                    --color-code-foreground: #d0d0d0;\n\n                }\n            }\n        }\n    </style>\n</head>\n<body>\n\n<script>\n    document.body.dataset.theme = localStorage.getItem(\"theme\") || \"auto\";\n</script>\n\n\n<svg style=\"display: none;\" xmlns=\"http://www.w3.org/2000/svg\">\n    <symbol id=\"svg-toc\" viewBox=\"0 0 24 24\">\n        <title>Contents</title>\n        <svg fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 1024 1024\">\n            <path d=\"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-menu\" viewBox=\"0 0 24 24\">\n        <title>Menu</title>\n        <svg class=\"feather-menu\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-arrow-right\" viewBox=\"0 0 24 24\">\n        <title>Expand</title>\n        <svg class=\"feather-chevron-right\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <polyline points=\"9 18 15 12 9 6\"></polyline>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun\" viewBox=\"0 0 24 24\">\n        <title>Light mode</title>\n        <svg class=\"feather-sun\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\n            <line x1=\"12\" x2=\"12\" y1=\"1\" y2=\"3\"></line>\n            <line x1=\"12\" x2=\"12\" y1=\"21\" y2=\"23\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"4.22\" y2=\"5.64\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"18.36\" y2=\"19.78\"></line>\n            <line x1=\"1\" x2=\"3\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"21\" x2=\"23\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"19.78\" y2=\"18.36\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"5.64\" y2=\"4.22\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon\" viewBox=\"0 0 24 24\">\n        <title>Dark mode</title>\n        <svg class=\"icon-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun-with-moon\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in light mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 5.411 14.504 C 5.471 14.504 5.532 14.504 5.591 14.504 C 3.639 16.319 4.383 19.569 6.931 20.352 C 7.693 20.586 8.512 20.551 9.25 20.252 C 8.023 23.207 4.056 23.725 2.11 21.184 C 0.166 18.642 1.702 14.949 4.874 14.536 C 5.051 14.512 5.231 14.5 5.411 14.5 L 5.411 14.504 Z\"\n                  style=\"opacity: 50%\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"3.25\" y2=\"1.25\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"15.85\" y2=\"17.85\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"5.094\" y2=\"3.68\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"14.05\" y2=\"15.464\"/>\n            <line x1=\"8.2\" x2=\"6.2\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"20.8\" x2=\"22.8\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"14.006\" y2=\"15.42\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"5.05\" y2=\"3.636\"/>\n            <circle cx=\"14.5\" cy=\"9.55\" r=\"3.6\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon-with-sun\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in dark mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 8.282 7.007 C 8.385 7.007 8.494 7.007 8.595 7.007 C 5.18 10.184 6.481 15.869 10.942 17.24 C 12.275 17.648 13.706 17.589 15 17.066 C 12.851 22.236 5.91 23.143 2.505 18.696 C -0.897 14.249 1.791 7.786 7.342 7.063 C 7.652 7.021 7.965 7 8.282 7 L 8.282 7.007 Z\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"3.705\" y2=\"2.5\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"11.295\" y2=\"12.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"4.816\" y2=\"3.964\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"10.212\" y2=\"11.063\"/>\n            <line style=\"opacity: 50%\" x1=\"14.205\" x2=\"13.001\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"21.795\" x2=\"23\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"10.184\" y2=\"11.036\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"4.789\" y2=\"3.937\"/>\n            <circle cx=\"18\" cy=\"7.5\" r=\"2.169\" style=\"opacity: 50%\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-pencil\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-pencil-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4\"/>\n            <path d=\"M13.5 6.5l4 4\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-eye\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-eye-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\"/>\n            <path\n                    d=\"M11.11 17.958c-3.209 -.307 -5.91 -2.293 -8.11 -5.958c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6c-.21 .352 -.427 .688 -.647 1.008\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n</svg>\n\n<input class=\"sidebar-toggle\" id=\"__navigation\" name=\"__navigation\" type=\"checkbox\">\n<input class=\"sidebar-toggle\" id=\"__toc\" name=\"__toc\" type=\"checkbox\">\n<label class=\"overlay sidebar-overlay\" for=\"__navigation\">\n    <div class=\"visually-hidden\">Hide navigation sidebar</div>\n</label>\n<label class=\"overlay toc-overlay\" for=\"__toc\">\n    <div class=\"visually-hidden\">Hide table of contents sidebar</div>\n</label>\n\n<a class=\"skip-to-content muted-link\" href=\"#furo-main-content\">Skip to content</a>\n\n\n\n<div class=\"page\">\n    <header class=\"mobile-header\">\n        <div class=\"header-left\">\n            <label class=\"nav-overlay-icon\" for=\"__navigation\">\n                <div class=\"visually-hidden\">Toggle site navigation sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-menu\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n        <div class=\"header-center\">\n            <a href=\"../../../index.html\">\n                <div class=\"brand\">XYBotV2</div>\n            </a>\n        </div>\n        <div class=\"header-right\">\n            <div class=\"theme-toggle-container theme-toggle-header\">\n                <button class=\"theme-toggle\">\n                    <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                    <svg class=\"theme-icon-when-auto-light\">\n                        <use href=\"#svg-sun-with-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-auto-dark\">\n                        <use href=\"#svg-moon-with-sun\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-dark\">\n                        <use href=\"#svg-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-light\">\n                        <use href=\"#svg-sun\"></use>\n                    </svg>\n                </button>\n            </div>\n            <label class=\"toc-overlay-icon toc-header-icon no-toc\" for=\"__toc\">\n                <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-toc\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n    </header>\n    <aside class=\"sidebar-drawer\">\n        <div class=\"sidebar-container\">\n\n            <div class=\"sidebar-sticky\">\n                <div class=\"sidebar-scroll\"><a class=\"sidebar-brand\" href=\"../../../index.html\">\n\n\n                    <span class=\"sidebar-brand-text\">XYBotV2</span>\n\n                </a>\n                    <form action=\"../../../search.html\" class=\"sidebar-search-container\" method=\"get\" role=\"search\">\n                        <input aria-label=\"搜索\" class=\"sidebar-search\" name=\"q\" placeholder=\"搜索\">\n                        <input name=\"check_keywords\" type=\"hidden\" value=\"yes\">\n                        <input name=\"area\" type=\"hidden\" value=\"default\">\n                    </form>\n                    <div id=\"searchbox\"></div>\n                    <div class=\"sidebar-tree\">\n\n                    </div>\n                    <div class=\"sidebar-tree\">\n                        <p class=\"caption\"><span class=\"caption-text\">重要函数导航</span></p>\n                        <ul>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">登录</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.is_running\">检查WechatAPI是否在运行</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_qr_code\">获取登录二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.awaken_login\">二次登录(唤醒登录)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.check_login_uuid\">检查登录的UUID状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_cached_info\">获取登录缓存信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.log_out\">登出当前账号</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.heartbeat\">发送心跳包</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\">开始自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\">停止自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\">获取自动心跳状态</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">消息</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.sync_message\">同步消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_text_message\">发送文本消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_image_message\">发送图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_voice_message\">发送语音消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_video_message\">发送视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_link_message\">发送链接消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_card_message\">发送名片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_app_message\">发送应用(xml)消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_emoji_message\">发送表情消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\">转发图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\">转发视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\">转发文件消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.revoke_message\">撤回消息</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">用户</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_my_qrcode\">获取个人二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_profile\">获取用户信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.is_logged_in\">检查是否登录</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">群聊</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\">获取群聊信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\">获取群聊公告</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\">获取群聊成员列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\">获取群聊二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\">添加群成员(群聊最多40人)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\">邀请群聊成员(群聊大于40人)</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">好友</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contact\">获取联系人信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_detail\">获取联系人详情</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_list\">获取联系人列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_nickname\">获取用户昵称</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.accept_friend\">接受好友请求</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">红包</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\">获取红包详情</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">工具</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.check_database\">检查数据库状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.set_step\">设置步数</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_image\">下载高清图片</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_video\">下载视频</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_voice\">下载语音文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_attach\">下载附件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_byte\">base64转字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_file\">base64转文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.byte_to_base64\">字节转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.file_to_base64\">文件转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\">silk的base64转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\">silk字节转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\">WAV字节转AMR的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\">WAV字节转AMR字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\">WAV字节转silk的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\">WAV字节转silk字节</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                        </ul>\n                    </div>\n                </div>\n            </div>\n\n        </div>\n    </aside>\n    <div class=\"main\">\n        <div class=\"content\">\n            <div class=\"article-container\">\n                <a class=\"back-to-top muted-link\" href=\"#\">\n                    <svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n                        <path d=\"M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z\"></path>\n                    </svg>\n                    <span>Back to top</span>\n                </a>\n                <div class=\"content-icon-container\">\n                    <div class=\"theme-toggle-container theme-toggle-content\">\n                        <button class=\"theme-toggle\">\n                            <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                            <svg class=\"theme-icon-when-auto-light\">\n                                <use href=\"#svg-sun-with-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-auto-dark\">\n                                <use href=\"#svg-moon-with-sun\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-dark\">\n                                <use href=\"#svg-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-light\">\n                                <use href=\"#svg-sun\"></use>\n                            </svg>\n                        </button>\n                    </div>\n                    <label class=\"toc-overlay-icon toc-content-icon no-toc\" for=\"__toc\">\n                        <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                        <i class=\"icon\">\n                            <svg>\n                                <use href=\"#svg-toc\"></use>\n                            </svg>\n                        </i>\n                    </label>\n                </div>\n                <article id=\"furo-main-content\" role=\"main\">\n                    <h1>WechatAPI.Client.friend 源代码</h1>\n                    <div class=\"highlight\"><pre>\n<span></span><span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">typing</span><span\n                            class=\"w\"> </span><span class=\"kn\">import</span> <span class=\"n\">Union</span>\n\n<span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">aiohttp</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.base</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"o\">*</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.protect</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"n\">protector</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">..errors</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"o\">*</span>\n\n\n<div class=\"viewcode-block\" id=\"FriendMixin\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.friend.FriendMixin\">[文档]</a>\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">FriendMixin</span><span class=\"p\">(</span><span\n        class=\"n\">WechatAPIClientBase</span><span class=\"p\">):</span>\n<div class=\"viewcode-block\" id=\"FriendMixin.accept_friend\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.friend.FriendMixin.accept_friend\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">accept_friend</span><span\n        class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">scene</span><span\n        class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"n\">v1</span><span class=\"p\">:</span> <span\n        class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">v2</span><span class=\"p\">:</span> <span\n        class=\"nb\">str</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">bool</span><span\n        class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;接受好友请求</span>\n\n<span class=\"sd\">        主动添加好友单天上限如下所示：1小时内上限为 5个，超过上限时，无法发出好友请求，也收不到好友请求。</span>\n\n<span class=\"sd\">        - 新账号：5/天</span>\n<span class=\"sd\">        - 注册超过7天：10个/天</span>\n<span class=\"sd\">        - 注册满3个月&amp;&amp;近期登录过该电脑：15/天</span>\n<span class=\"sd\">        - 注册满6个月&amp;&amp;近期经常登录过该电脑：20/天</span>\n<span class=\"sd\">        - 注册满6个月&amp;&amp;近期频繁登陆过该电脑：30/天</span>\n<span class=\"sd\">        - 注册1年以上&amp;&amp;一直登录：50/天</span>\n<span class=\"sd\">        - 上一次通过好友到下一次通过间隔20-40s</span>\n<span class=\"sd\">        - 收到加人申请，到通过好友申请（每天最多通过300个好友申请），间隔30s+（随机时间）</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            scene: 来源 在消息的xml获取</span>\n<span class=\"sd\">            v1: v1key</span>\n<span class=\"sd\">            v2: v2key</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bool: 操作是否成功</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span class=\"ow\">and</span> <span\n        class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span class=\"p\">(</span><span\n        class=\"mi\">14400</span><span class=\"p\">):</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;风控保护: 新设备登录后4小时内请挂机&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Scene&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">scene</span><span class=\"p\">,</span> <span class=\"s2\">&quot;V1&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">v1</span><span class=\"p\">,</span> <span class=\"s2\">&quot;V2&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">v2</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span class=\"s1\">/AcceptFriend&#39;</span><span\n        class=\"p\">,</span> <span class=\"n\">json</span><span class=\"o\">=</span><span class=\"n\">json_param</span><span\n        class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"kc\">True</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"FriendMixin.get_contact\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.friend.FriendMixin.get_contact\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">get_contact</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"n\">Union</span><span class=\"p\">[</span><span\n        class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span\n        class=\"nb\">str</span><span class=\"p\">]])</span> <span class=\"o\">-&gt;</span> <span class=\"n\">Union</span><span\n        class=\"p\">[</span><span class=\"nb\">dict</span><span class=\"p\">,</span> <span class=\"nb\">list</span><span\n        class=\"p\">[</span><span class=\"nb\">dict</span><span class=\"p\">]]:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;获取联系人信息</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid: 联系人wxid, 可以是多个wxid在list里，也可查询chatroom</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            Union[dict, list[dict]]: 单个联系人返回dict，多个联系人返回list[dict]</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">if</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"nb\">list</span><span class=\"p\">):</span>\n            <span class=\"n\">wxid</span> <span class=\"o\">=</span> <span class=\"s2\">&quot;,&quot;</span><span\n        class=\"o\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">wxid</span><span class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;RequestWxids&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">wxid</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/GetContact&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"n\">contact_list</span> <span class=\"o\">=</span> <span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;ContactList&quot;</span><span\n        class=\"p\">)</span>\n                <span class=\"k\">if</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span class=\"n\">contact_list</span><span\n        class=\"p\">)</span> <span class=\"o\">==</span> <span class=\"mi\">1</span><span class=\"p\">:</span>\n                    <span class=\"k\">return</span> <span class=\"n\">contact_list</span><span class=\"p\">[</span><span\n        class=\"mi\">0</span><span class=\"p\">]</span>\n                <span class=\"k\">else</span><span class=\"p\">:</span>\n                    <span class=\"k\">return</span> <span class=\"n\">contact_list</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"FriendMixin.get_contract_detail\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.friend.FriendMixin.get_contract_detail\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">get_contract_detail</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">,</span> <span class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"n\">Union</span><span\n        class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"nb\">list</span><span\n        class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">]],</span> <span class=\"n\">chatroom</span><span\n        class=\"p\">:</span> <span class=\"nb\">str</span> <span class=\"o\">=</span> <span\n        class=\"s2\">&quot;&quot;</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">list</span><span\n        class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;获取联系人详情</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid: 联系人wxid</span>\n<span class=\"sd\">            chatroom: 群聊wxid</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            list: 联系人详情列表</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">if</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"nb\">list</span><span class=\"p\">):</span>\n            <span class=\"k\">if</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span\n        class=\"n\">wxid</span><span class=\"p\">)</span> <span class=\"o\">&gt;</span> <span class=\"mi\">20</span><span\n        class=\"p\">:</span>\n                <span class=\"k\">raise</span> <span class=\"ne\">ValueError</span><span class=\"p\">(</span><span class=\"s2\">&quot;一次最多查询20个联系人&quot;</span><span\n        class=\"p\">)</span>\n            <span class=\"n\">wxid</span> <span class=\"o\">=</span> <span class=\"s2\">&quot;,&quot;</span><span\n        class=\"o\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">wxid</span><span class=\"p\">)</span>\n\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;RequestWxids&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Chatroom&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">chatroom</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/GetContractDetail&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;ContactList&quot;</span><span\n        class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"FriendMixin.get_contract_list\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.friend.FriendMixin.get_contract_list\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">get_contract_list</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wx_seq</span><span class=\"p\">:</span> <span class=\"nb\">int</span> <span class=\"o\">=</span> <span\n        class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"n\">chatroom_seq</span><span class=\"p\">:</span> <span\n        class=\"nb\">int</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span class=\"p\">)</span> <span\n        class=\"o\">-&gt;</span> <span class=\"nb\">dict</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;获取联系人列表</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wx_seq: 联系人序列</span>\n<span class=\"sd\">            chatroom_seq: 群聊序列</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            dict: 联系人列表数据</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;CurrentWxcontactSeq&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">wx_seq</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;CurrentChatroomContactSeq&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">chatroom_seq</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/GetContractList&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"FriendMixin.get_nickname\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.friend.FriendMixin.get_nickname\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">get_nickname</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"n\">Union</span><span class=\"p\">[</span><span\n        class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span\n        class=\"nb\">str</span><span class=\"p\">]])</span> <span class=\"o\">-&gt;</span> <span class=\"n\">Union</span><span\n        class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"nb\">list</span><span\n        class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">]]:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;获取用户昵称</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid: 用户wxid，可以是单个wxid或最多20个wxid的列表</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            Union[str, list[str]]: 如果输入单个wxid返回str，如果输入wxid列表则返回对应的昵称列表</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span\n        class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">get_contract_detail</span><span class=\"p\">(</span><span\n        class=\"n\">wxid</span><span class=\"p\">)</span>\n\n        <span class=\"k\">if</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"nb\">str</span><span class=\"p\">):</span>\n            <span class=\"k\">try</span><span class=\"p\">:</span>\n                <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"p\">[</span><span\n        class=\"mi\">0</span><span class=\"p\">]</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;NickName&quot;</span><span class=\"p\">)</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;string&quot;</span><span class=\"p\">)</span>\n            <span class=\"k\">except</span><span class=\"p\">:</span>\n                <span class=\"k\">return</span> <span class=\"s2\">&quot;&quot;</span>\n        <span class=\"k\">else</span><span class=\"p\">:</span>\n            <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n            <span class=\"k\">for</span> <span class=\"n\">contact</span> <span class=\"ow\">in</span> <span\n        class=\"n\">data</span><span class=\"p\">:</span>\n                <span class=\"k\">try</span><span class=\"p\">:</span>\n                    <span class=\"n\">result</span><span class=\"o\">.</span><span class=\"n\">append</span><span\n        class=\"p\">(</span><span class=\"n\">contact</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;NickName&quot;</span><span class=\"p\">)</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;string&quot;</span><span class=\"p\">))</span>\n                <span class=\"k\">except</span><span class=\"p\">:</span>\n                    <span class=\"n\">result</span><span class=\"o\">.</span><span class=\"n\">append</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;&quot;</span><span class=\"p\">)</span>\n            <span class=\"k\">return</span> <span class=\"n\">result</span></div>\n</div>\n\n</pre>\n                    </div>\n                </article>\n            </div>\n            <footer>\n\n                <div class=\"related-pages\">\n\n\n                </div>\n                <div class=\"bottom-of-page\">\n                    <div class=\"left-details\">\n                        <div class=\"copyright\">\n                            Copyright &#169; 2025, HenryXiaoYang\n                        </div>\n                        Made with <a href=\"https://www.sphinx-doc.org/\">Sphinx</a> and <a class=\"muted-link\"\n                                                                                          href=\"https://pradyunsg.me\">@pradyunsg</a>'s\n\n                        <a href=\"https://github.com/pradyunsg/furo\">Furo</a>\n\n                    </div>\n                    <div class=\"right-details\">\n\n                    </div>\n                </div>\n\n            </footer>\n        </div>\n        <aside class=\"toc-drawer no-toc\">\n\n\n        </aside>\n    </div>\n</div>\n<script src=\"../../../_static/documentation_options.js?v=91bfbbb6\"></script>\n<script src=\"../../../_static/doctools.js?v=9bcbadda\"></script>\n<script src=\"../../../_static/sphinx_highlight.js?v=dc90522c\"></script>\n<script src=\"../../../_static/scripts/furo.js?v=5fa4622c\"></script>\n<script src=\"../../../_static/translations.js?v=beaddf03\"></script>\n</body>\n</html>"
  },
  {
    "path": "docs/WechatAPIClient/_modules/WechatAPI/Client/hongbao.html",
    "content": "<!doctype html>\n<html class=\"no-js\" data-content_root=\"../../../\" lang=\"zh-CN\">\n<head>\n    <meta charset=\"utf-8\"/>\n    <meta content=\"width=device-width,initial-scale=1\" name=\"viewport\"/>\n    <meta content=\"light dark\" name=\"color-scheme\">\n    <link href=\"../../../genindex.html\" rel=\"index\" title=\"索引\"/>\n    <link href=\"../../../search.html\" rel=\"search\" title=\"搜索\"/>\n\n    <!-- Generated with Sphinx 8.1.3 and Furo 2024.08.06 -->\n    <title>WechatAPI.Client.hongbao - XYBotV2</title>\n    <link href=\"../../../_static/pygments.css?v=8f2a1f02\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo.css?v=354aac6f\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo-extensions.css?v=302659d7\" rel=\"stylesheet\" type=\"text/css\"/>\n\n\n    <style>\n        body {\n            --color-code-background: #f8f8f8;\n            --color-code-foreground: black;\n            --color-brand-primary: #2962ff;\n            --color-brand-content: #2962ff;\n\n        }\n\n        @media not print {\n            body[data-theme=\"dark\"] {\n                --color-code-background: #202020;\n                --color-code-foreground: #d0d0d0;\n\n            }\n\n            @media (prefers-color-scheme: dark) {\n                body:not([data-theme=\"light\"]) {\n                    --color-code-background: #202020;\n                    --color-code-foreground: #d0d0d0;\n\n                }\n            }\n        }\n    </style>\n</head>\n<body>\n\n<script>\n    document.body.dataset.theme = localStorage.getItem(\"theme\") || \"auto\";\n</script>\n\n\n<svg style=\"display: none;\" xmlns=\"http://www.w3.org/2000/svg\">\n    <symbol id=\"svg-toc\" viewBox=\"0 0 24 24\">\n        <title>Contents</title>\n        <svg fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 1024 1024\">\n            <path d=\"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-menu\" viewBox=\"0 0 24 24\">\n        <title>Menu</title>\n        <svg class=\"feather-menu\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-arrow-right\" viewBox=\"0 0 24 24\">\n        <title>Expand</title>\n        <svg class=\"feather-chevron-right\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <polyline points=\"9 18 15 12 9 6\"></polyline>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun\" viewBox=\"0 0 24 24\">\n        <title>Light mode</title>\n        <svg class=\"feather-sun\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\n            <line x1=\"12\" x2=\"12\" y1=\"1\" y2=\"3\"></line>\n            <line x1=\"12\" x2=\"12\" y1=\"21\" y2=\"23\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"4.22\" y2=\"5.64\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"18.36\" y2=\"19.78\"></line>\n            <line x1=\"1\" x2=\"3\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"21\" x2=\"23\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"19.78\" y2=\"18.36\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"5.64\" y2=\"4.22\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon\" viewBox=\"0 0 24 24\">\n        <title>Dark mode</title>\n        <svg class=\"icon-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun-with-moon\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in light mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 5.411 14.504 C 5.471 14.504 5.532 14.504 5.591 14.504 C 3.639 16.319 4.383 19.569 6.931 20.352 C 7.693 20.586 8.512 20.551 9.25 20.252 C 8.023 23.207 4.056 23.725 2.11 21.184 C 0.166 18.642 1.702 14.949 4.874 14.536 C 5.051 14.512 5.231 14.5 5.411 14.5 L 5.411 14.504 Z\"\n                  style=\"opacity: 50%\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"3.25\" y2=\"1.25\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"15.85\" y2=\"17.85\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"5.094\" y2=\"3.68\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"14.05\" y2=\"15.464\"/>\n            <line x1=\"8.2\" x2=\"6.2\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"20.8\" x2=\"22.8\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"14.006\" y2=\"15.42\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"5.05\" y2=\"3.636\"/>\n            <circle cx=\"14.5\" cy=\"9.55\" r=\"3.6\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon-with-sun\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in dark mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 8.282 7.007 C 8.385 7.007 8.494 7.007 8.595 7.007 C 5.18 10.184 6.481 15.869 10.942 17.24 C 12.275 17.648 13.706 17.589 15 17.066 C 12.851 22.236 5.91 23.143 2.505 18.696 C -0.897 14.249 1.791 7.786 7.342 7.063 C 7.652 7.021 7.965 7 8.282 7 L 8.282 7.007 Z\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"3.705\" y2=\"2.5\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"11.295\" y2=\"12.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"4.816\" y2=\"3.964\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"10.212\" y2=\"11.063\"/>\n            <line style=\"opacity: 50%\" x1=\"14.205\" x2=\"13.001\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"21.795\" x2=\"23\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"10.184\" y2=\"11.036\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"4.789\" y2=\"3.937\"/>\n            <circle cx=\"18\" cy=\"7.5\" r=\"2.169\" style=\"opacity: 50%\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-pencil\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-pencil-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4\"/>\n            <path d=\"M13.5 6.5l4 4\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-eye\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-eye-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\"/>\n            <path\n                    d=\"M11.11 17.958c-3.209 -.307 -5.91 -2.293 -8.11 -5.958c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6c-.21 .352 -.427 .688 -.647 1.008\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n</svg>\n\n<input class=\"sidebar-toggle\" id=\"__navigation\" name=\"__navigation\" type=\"checkbox\">\n<input class=\"sidebar-toggle\" id=\"__toc\" name=\"__toc\" type=\"checkbox\">\n<label class=\"overlay sidebar-overlay\" for=\"__navigation\">\n    <div class=\"visually-hidden\">Hide navigation sidebar</div>\n</label>\n<label class=\"overlay toc-overlay\" for=\"__toc\">\n    <div class=\"visually-hidden\">Hide table of contents sidebar</div>\n</label>\n\n<a class=\"skip-to-content muted-link\" href=\"#furo-main-content\">Skip to content</a>\n\n\n\n<div class=\"page\">\n    <header class=\"mobile-header\">\n        <div class=\"header-left\">\n            <label class=\"nav-overlay-icon\" for=\"__navigation\">\n                <div class=\"visually-hidden\">Toggle site navigation sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-menu\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n        <div class=\"header-center\">\n            <a href=\"../../../index.html\">\n                <div class=\"brand\">XYBotV2</div>\n            </a>\n        </div>\n        <div class=\"header-right\">\n            <div class=\"theme-toggle-container theme-toggle-header\">\n                <button class=\"theme-toggle\">\n                    <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                    <svg class=\"theme-icon-when-auto-light\">\n                        <use href=\"#svg-sun-with-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-auto-dark\">\n                        <use href=\"#svg-moon-with-sun\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-dark\">\n                        <use href=\"#svg-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-light\">\n                        <use href=\"#svg-sun\"></use>\n                    </svg>\n                </button>\n            </div>\n            <label class=\"toc-overlay-icon toc-header-icon no-toc\" for=\"__toc\">\n                <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-toc\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n    </header>\n    <aside class=\"sidebar-drawer\">\n        <div class=\"sidebar-container\">\n\n            <div class=\"sidebar-sticky\">\n                <div class=\"sidebar-scroll\"><a class=\"sidebar-brand\" href=\"../../../index.html\">\n\n\n                    <span class=\"sidebar-brand-text\">XYBotV2</span>\n\n                </a>\n                    <form action=\"../../../search.html\" class=\"sidebar-search-container\" method=\"get\" role=\"search\">\n                        <input aria-label=\"搜索\" class=\"sidebar-search\" name=\"q\" placeholder=\"搜索\">\n                        <input name=\"check_keywords\" type=\"hidden\" value=\"yes\">\n                        <input name=\"area\" type=\"hidden\" value=\"default\">\n                    </form>\n                    <div id=\"searchbox\"></div>\n                    <div class=\"sidebar-tree\">\n\n                    </div>\n                    <div class=\"sidebar-tree\">\n                        <p class=\"caption\"><span class=\"caption-text\">重要函数导航</span></p>\n                        <ul>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">登录</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.is_running\">检查WechatAPI是否在运行</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_qr_code\">获取登录二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.awaken_login\">二次登录(唤醒登录)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.check_login_uuid\">检查登录的UUID状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_cached_info\">获取登录缓存信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.log_out\">登出当前账号</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.heartbeat\">发送心跳包</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\">开始自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\">停止自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\">获取自动心跳状态</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">消息</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.sync_message\">同步消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_text_message\">发送文本消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_image_message\">发送图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_voice_message\">发送语音消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_video_message\">发送视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_link_message\">发送链接消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_card_message\">发送名片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_app_message\">发送应用(xml)消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_emoji_message\">发送表情消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\">转发图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\">转发视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\">转发文件消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.revoke_message\">撤回消息</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">用户</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_my_qrcode\">获取个人二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_profile\">获取用户信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.is_logged_in\">检查是否登录</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">群聊</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\">获取群聊信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\">获取群聊公告</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\">获取群聊成员列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\">获取群聊二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\">添加群成员(群聊最多40人)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\">邀请群聊成员(群聊大于40人)</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">好友</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contact\">获取联系人信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_detail\">获取联系人详情</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_list\">获取联系人列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_nickname\">获取用户昵称</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.accept_friend\">接受好友请求</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">红包</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\">获取红包详情</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">工具</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.check_database\">检查数据库状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.set_step\">设置步数</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_image\">下载高清图片</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_video\">下载视频</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_voice\">下载语音文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_attach\">下载附件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_byte\">base64转字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_file\">base64转文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.byte_to_base64\">字节转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.file_to_base64\">文件转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\">silk的base64转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\">silk字节转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\">WAV字节转AMR的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\">WAV字节转AMR字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\">WAV字节转silk的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\">WAV字节转silk字节</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                        </ul>\n                    </div>\n                </div>\n            </div>\n\n        </div>\n    </aside>\n    <div class=\"main\">\n        <div class=\"content\">\n            <div class=\"article-container\">\n                <a class=\"back-to-top muted-link\" href=\"#\">\n                    <svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n                        <path d=\"M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z\"></path>\n                    </svg>\n                    <span>Back to top</span>\n                </a>\n                <div class=\"content-icon-container\">\n                    <div class=\"theme-toggle-container theme-toggle-content\">\n                        <button class=\"theme-toggle\">\n                            <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                            <svg class=\"theme-icon-when-auto-light\">\n                                <use href=\"#svg-sun-with-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-auto-dark\">\n                                <use href=\"#svg-moon-with-sun\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-dark\">\n                                <use href=\"#svg-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-light\">\n                                <use href=\"#svg-sun\"></use>\n                            </svg>\n                        </button>\n                    </div>\n                    <label class=\"toc-overlay-icon toc-content-icon no-toc\" for=\"__toc\">\n                        <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                        <i class=\"icon\">\n                            <svg>\n                                <use href=\"#svg-toc\"></use>\n                            </svg>\n                        </i>\n                    </label>\n                </div>\n                <article id=\"furo-main-content\" role=\"main\">\n                    <h1>WechatAPI.Client.hongbao 源代码</h1>\n                    <div class=\"highlight\"><pre>\n<span></span><span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">aiohttp</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.base</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"o\">*</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">..errors</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"o\">*</span>\n\n\n<div class=\"viewcode-block\" id=\"HongBaoMixin\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.hongbao.HongBaoMixin\">[文档]</a>\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">HongBaoMixin</span><span class=\"p\">(</span><span\n        class=\"n\">WechatAPIClientBase</span><span class=\"p\">):</span>\n<div class=\"viewcode-block\" id=\"HongBaoMixin.get_hongbao_detail\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">get_hongbao_detail</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">xml</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">encrypt_key</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">encrypt_userinfo</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">dict</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;获取红包详情</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            xml: 红包 XML 数据</span>\n<span class=\"sd\">            encrypt_key: 加密密钥</span>\n<span class=\"sd\">            encrypt_userinfo: 加密的用户信息</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            dict: 红包详情数据</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Xml&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">xml</span><span class=\"p\">,</span> <span class=\"s2\">&quot;EncryptKey&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">encrypt_key</span><span class=\"p\">,</span> <span class=\"s2\">&quot;EncryptUserinfo&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">encrypt_userinfo</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/GetHongBaoDetail&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n</div>\n\n</pre>\n                    </div>\n                </article>\n            </div>\n            <footer>\n\n                <div class=\"related-pages\">\n\n\n                </div>\n                <div class=\"bottom-of-page\">\n                    <div class=\"left-details\">\n                        <div class=\"copyright\">\n                            Copyright &#169; 2025, HenryXiaoYang\n                        </div>\n                        Made with <a href=\"https://www.sphinx-doc.org/\">Sphinx</a> and <a class=\"muted-link\"\n                                                                                          href=\"https://pradyunsg.me\">@pradyunsg</a>'s\n\n                        <a href=\"https://github.com/pradyunsg/furo\">Furo</a>\n\n                    </div>\n                    <div class=\"right-details\">\n\n                    </div>\n                </div>\n\n            </footer>\n        </div>\n        <aside class=\"toc-drawer no-toc\">\n\n\n        </aside>\n    </div>\n</div>\n<script src=\"../../../_static/documentation_options.js?v=91bfbbb6\"></script>\n<script src=\"../../../_static/doctools.js?v=9bcbadda\"></script>\n<script src=\"../../../_static/sphinx_highlight.js?v=dc90522c\"></script>\n<script src=\"../../../_static/scripts/furo.js?v=5fa4622c\"></script>\n<script src=\"../../../_static/translations.js?v=beaddf03\"></script>\n</body>\n</html>"
  },
  {
    "path": "docs/WechatAPIClient/_modules/WechatAPI/Client/login.html",
    "content": "<!doctype html>\n<html class=\"no-js\" data-content_root=\"../../../\" lang=\"zh-CN\">\n<head>\n    <meta charset=\"utf-8\"/>\n    <meta content=\"width=device-width,initial-scale=1\" name=\"viewport\"/>\n    <meta content=\"light dark\" name=\"color-scheme\">\n    <link href=\"../../../genindex.html\" rel=\"index\" title=\"索引\"/>\n    <link href=\"../../../search.html\" rel=\"search\" title=\"搜索\"/>\n\n    <!-- Generated with Sphinx 8.1.3 and Furo 2024.08.06 -->\n    <title>WechatAPI.Client.login - XYBotV2</title>\n    <link href=\"../../../_static/pygments.css?v=8f2a1f02\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo.css?v=354aac6f\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo-extensions.css?v=302659d7\" rel=\"stylesheet\" type=\"text/css\"/>\n\n\n    <style>\n        body {\n            --color-code-background: #f8f8f8;\n            --color-code-foreground: black;\n            --color-brand-primary: #2962ff;\n            --color-brand-content: #2962ff;\n\n        }\n\n        @media not print {\n            body[data-theme=\"dark\"] {\n                --color-code-background: #202020;\n                --color-code-foreground: #d0d0d0;\n\n            }\n\n            @media (prefers-color-scheme: dark) {\n                body:not([data-theme=\"light\"]) {\n                    --color-code-background: #202020;\n                    --color-code-foreground: #d0d0d0;\n\n                }\n            }\n        }\n    </style>\n</head>\n<body>\n\n<script>\n    document.body.dataset.theme = localStorage.getItem(\"theme\") || \"auto\";\n</script>\n\n\n<svg style=\"display: none;\" xmlns=\"http://www.w3.org/2000/svg\">\n    <symbol id=\"svg-toc\" viewBox=\"0 0 24 24\">\n        <title>Contents</title>\n        <svg fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 1024 1024\">\n            <path d=\"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-menu\" viewBox=\"0 0 24 24\">\n        <title>Menu</title>\n        <svg class=\"feather-menu\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-arrow-right\" viewBox=\"0 0 24 24\">\n        <title>Expand</title>\n        <svg class=\"feather-chevron-right\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <polyline points=\"9 18 15 12 9 6\"></polyline>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun\" viewBox=\"0 0 24 24\">\n        <title>Light mode</title>\n        <svg class=\"feather-sun\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\n            <line x1=\"12\" x2=\"12\" y1=\"1\" y2=\"3\"></line>\n            <line x1=\"12\" x2=\"12\" y1=\"21\" y2=\"23\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"4.22\" y2=\"5.64\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"18.36\" y2=\"19.78\"></line>\n            <line x1=\"1\" x2=\"3\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"21\" x2=\"23\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"19.78\" y2=\"18.36\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"5.64\" y2=\"4.22\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon\" viewBox=\"0 0 24 24\">\n        <title>Dark mode</title>\n        <svg class=\"icon-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun-with-moon\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in light mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 5.411 14.504 C 5.471 14.504 5.532 14.504 5.591 14.504 C 3.639 16.319 4.383 19.569 6.931 20.352 C 7.693 20.586 8.512 20.551 9.25 20.252 C 8.023 23.207 4.056 23.725 2.11 21.184 C 0.166 18.642 1.702 14.949 4.874 14.536 C 5.051 14.512 5.231 14.5 5.411 14.5 L 5.411 14.504 Z\"\n                  style=\"opacity: 50%\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"3.25\" y2=\"1.25\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"15.85\" y2=\"17.85\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"5.094\" y2=\"3.68\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"14.05\" y2=\"15.464\"/>\n            <line x1=\"8.2\" x2=\"6.2\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"20.8\" x2=\"22.8\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"14.006\" y2=\"15.42\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"5.05\" y2=\"3.636\"/>\n            <circle cx=\"14.5\" cy=\"9.55\" r=\"3.6\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon-with-sun\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in dark mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 8.282 7.007 C 8.385 7.007 8.494 7.007 8.595 7.007 C 5.18 10.184 6.481 15.869 10.942 17.24 C 12.275 17.648 13.706 17.589 15 17.066 C 12.851 22.236 5.91 23.143 2.505 18.696 C -0.897 14.249 1.791 7.786 7.342 7.063 C 7.652 7.021 7.965 7 8.282 7 L 8.282 7.007 Z\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"3.705\" y2=\"2.5\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"11.295\" y2=\"12.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"4.816\" y2=\"3.964\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"10.212\" y2=\"11.063\"/>\n            <line style=\"opacity: 50%\" x1=\"14.205\" x2=\"13.001\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"21.795\" x2=\"23\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"10.184\" y2=\"11.036\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"4.789\" y2=\"3.937\"/>\n            <circle cx=\"18\" cy=\"7.5\" r=\"2.169\" style=\"opacity: 50%\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-pencil\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-pencil-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4\"/>\n            <path d=\"M13.5 6.5l4 4\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-eye\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-eye-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\"/>\n            <path\n                    d=\"M11.11 17.958c-3.209 -.307 -5.91 -2.293 -8.11 -5.958c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6c-.21 .352 -.427 .688 -.647 1.008\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n</svg>\n\n<input class=\"sidebar-toggle\" id=\"__navigation\" name=\"__navigation\" type=\"checkbox\">\n<input class=\"sidebar-toggle\" id=\"__toc\" name=\"__toc\" type=\"checkbox\">\n<label class=\"overlay sidebar-overlay\" for=\"__navigation\">\n    <div class=\"visually-hidden\">Hide navigation sidebar</div>\n</label>\n<label class=\"overlay toc-overlay\" for=\"__toc\">\n    <div class=\"visually-hidden\">Hide table of contents sidebar</div>\n</label>\n\n<a class=\"skip-to-content muted-link\" href=\"#furo-main-content\">Skip to content</a>\n\n\n\n<div class=\"page\">\n    <header class=\"mobile-header\">\n        <div class=\"header-left\">\n            <label class=\"nav-overlay-icon\" for=\"__navigation\">\n                <div class=\"visually-hidden\">Toggle site navigation sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-menu\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n        <div class=\"header-center\">\n            <a href=\"../../../index.html\">\n                <div class=\"brand\">XYBotV2</div>\n            </a>\n        </div>\n        <div class=\"header-right\">\n            <div class=\"theme-toggle-container theme-toggle-header\">\n                <button class=\"theme-toggle\">\n                    <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                    <svg class=\"theme-icon-when-auto-light\">\n                        <use href=\"#svg-sun-with-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-auto-dark\">\n                        <use href=\"#svg-moon-with-sun\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-dark\">\n                        <use href=\"#svg-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-light\">\n                        <use href=\"#svg-sun\"></use>\n                    </svg>\n                </button>\n            </div>\n            <label class=\"toc-overlay-icon toc-header-icon no-toc\" for=\"__toc\">\n                <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-toc\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n    </header>\n    <aside class=\"sidebar-drawer\">\n        <div class=\"sidebar-container\">\n\n            <div class=\"sidebar-sticky\">\n                <div class=\"sidebar-scroll\"><a class=\"sidebar-brand\" href=\"../../../index.html\">\n\n\n                    <span class=\"sidebar-brand-text\">XYBotV2</span>\n\n                </a>\n                    <form action=\"../../../search.html\" class=\"sidebar-search-container\" method=\"get\" role=\"search\">\n                        <input aria-label=\"搜索\" class=\"sidebar-search\" name=\"q\" placeholder=\"搜索\">\n                        <input name=\"check_keywords\" type=\"hidden\" value=\"yes\">\n                        <input name=\"area\" type=\"hidden\" value=\"default\">\n                    </form>\n                    <div id=\"searchbox\"></div>\n                    <div class=\"sidebar-tree\">\n\n                    </div>\n                    <div class=\"sidebar-tree\">\n                        <p class=\"caption\"><span class=\"caption-text\">重要函数导航</span></p>\n                        <ul>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">登录</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.is_running\">检查WechatAPI是否在运行</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_qr_code\">获取登录二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.awaken_login\">二次登录(唤醒登录)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.check_login_uuid\">检查登录的UUID状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_cached_info\">获取登录缓存信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.log_out\">登出当前账号</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.heartbeat\">发送心跳包</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\">开始自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\">停止自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\">获取自动心跳状态</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">消息</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.sync_message\">同步消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_text_message\">发送文本消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_image_message\">发送图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_voice_message\">发送语音消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_video_message\">发送视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_link_message\">发送链接消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_card_message\">发送名片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_app_message\">发送应用(xml)消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_emoji_message\">发送表情消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\">转发图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\">转发视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\">转发文件消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.revoke_message\">撤回消息</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">用户</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_my_qrcode\">获取个人二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_profile\">获取用户信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.is_logged_in\">检查是否登录</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">群聊</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\">获取群聊信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\">获取群聊公告</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\">获取群聊成员列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\">获取群聊二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\">添加群成员(群聊最多40人)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\">邀请群聊成员(群聊大于40人)</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">好友</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contact\">获取联系人信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_detail\">获取联系人详情</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_list\">获取联系人列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_nickname\">获取用户昵称</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.accept_friend\">接受好友请求</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">红包</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\">获取红包详情</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">工具</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.check_database\">检查数据库状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.set_step\">设置步数</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_image\">下载高清图片</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_video\">下载视频</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_voice\">下载语音文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_attach\">下载附件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_byte\">base64转字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_file\">base64转文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.byte_to_base64\">字节转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.file_to_base64\">文件转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\">silk的base64转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\">silk字节转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\">WAV字节转AMR的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\">WAV字节转AMR字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\">WAV字节转silk的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\">WAV字节转silk字节</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                        </ul>\n                    </div>\n                </div>\n            </div>\n\n        </div>\n    </aside>\n    <div class=\"main\">\n        <div class=\"content\">\n            <div class=\"article-container\">\n                <a class=\"back-to-top muted-link\" href=\"#\">\n                    <svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n                        <path d=\"M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z\"></path>\n                    </svg>\n                    <span>Back to top</span>\n                </a>\n                <div class=\"content-icon-container\">\n                    <div class=\"theme-toggle-container theme-toggle-content\">\n                        <button class=\"theme-toggle\">\n                            <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                            <svg class=\"theme-icon-when-auto-light\">\n                                <use href=\"#svg-sun-with-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-auto-dark\">\n                                <use href=\"#svg-moon-with-sun\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-dark\">\n                                <use href=\"#svg-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-light\">\n                                <use href=\"#svg-sun\"></use>\n                            </svg>\n                        </button>\n                    </div>\n                    <label class=\"toc-overlay-icon toc-content-icon no-toc\" for=\"__toc\">\n                        <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                        <i class=\"icon\">\n                            <svg>\n                                <use href=\"#svg-toc\"></use>\n                            </svg>\n                        </i>\n                    </label>\n                </div>\n                <article id=\"furo-main-content\" role=\"main\">\n                    <h1>WechatAPI.Client.login 源代码</h1>\n                    <div class=\"highlight\"><pre>\n<span></span><span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">hashlib</span>\n<span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">string</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">random</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"n\">choice</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">typing</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"n\">Union</span>\n\n<span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">aiohttp</span>\n<span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">qrcode</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.base</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"o\">*</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.protect</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"n\">protector</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">..errors</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"o\">*</span>\n\n\n<div class=\"viewcode-block\" id=\"LoginMixin\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.login.LoginMixin\">[文档]</a>\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">LoginMixin</span><span class=\"p\">(</span><span\n        class=\"n\">WechatAPIClientBase</span><span class=\"p\">):</span>\n<div class=\"viewcode-block\" id=\"LoginMixin.is_running\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.login.LoginMixin.is_running\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">is_running</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">)</span> <span\n        class=\"o\">-&gt;</span> <span class=\"nb\">bool</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;检查WechatAPI是否在运行。</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bool: 如果WechatAPI正在运行返回True，否则返回False。</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">try</span><span class=\"p\">:</span>\n            <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n                <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/IsRunning&#39;</span><span class=\"p\">)</span>\n                <span class=\"k\">return</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">text</span><span class=\"p\">()</span> <span class=\"o\">==</span> <span\n        class=\"s1\">&#39;OK&#39;</span>\n        <span class=\"k\">except</span> <span class=\"n\">aiohttp</span><span class=\"o\">.</span><span class=\"n\">client_exceptions</span><span\n        class=\"o\">.</span><span class=\"n\">ClientConnectorError</span><span class=\"p\">:</span>\n            <span class=\"k\">return</span> <span class=\"kc\">False</span></div>\n\n\n<div class=\"viewcode-block\" id=\"LoginMixin.get_qr_code\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.login.LoginMixin.get_qr_code\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">get_qr_code</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">device_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">device_id</span><span class=\"p\">:</span> <span class=\"nb\">str</span> <span class=\"o\">=</span> <span\n        class=\"s2\">&quot;&quot;</span><span class=\"p\">,</span> <span class=\"n\">proxy</span><span\n        class=\"p\">:</span> <span class=\"n\">Proxy</span> <span class=\"o\">=</span> <span class=\"kc\">None</span><span\n        class=\"p\">,</span> <span class=\"n\">print_qr</span><span class=\"p\">:</span> <span class=\"nb\">bool</span> <span\n        class=\"o\">=</span> <span class=\"kc\">False</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span\n        class=\"p\">(</span>\n            <span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"nb\">str</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;获取登录二维码。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            device_name (str): 设备名称</span>\n<span class=\"sd\">            device_id (str, optional): 设备ID. Defaults to &quot;&quot;.</span>\n<span class=\"sd\">            proxy (Proxy, optional): 代理信息. Defaults to None.</span>\n<span class=\"sd\">            print_qr (bool, optional): 是否在控制台打印二维码. Defaults to False.</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            tuple[str, str]: 返回登录二维码的UUID和URL</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s1\">&#39;DeviceName&#39;</span><span\n        class=\"p\">:</span> <span class=\"n\">device_name</span><span class=\"p\">,</span> <span class=\"s1\">&#39;DeviceID&#39;</span><span\n        class=\"p\">:</span> <span class=\"n\">device_id</span><span class=\"p\">}</span>\n            <span class=\"k\">if</span> <span class=\"n\">proxy</span><span class=\"p\">:</span>\n                <span class=\"n\">json_param</span><span class=\"p\">[</span><span\n        class=\"s1\">&#39;ProxyInfo&#39;</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span\n        class=\"p\">{</span><span class=\"s1\">&#39;ProxyIp&#39;</span><span class=\"p\">:</span> <span\n        class=\"sa\">f</span><span class=\"s1\">&#39;</span><span class=\"si\">{</span><span class=\"n\">proxy</span><span\n        class=\"o\">.</span><span class=\"n\">ip</span><span class=\"si\">}</span><span class=\"s1\">:</span><span\n        class=\"si\">{</span><span class=\"n\">proxy</span><span class=\"o\">.</span><span class=\"n\">port</span><span\n        class=\"si\">}</span><span class=\"s1\">&#39;</span><span class=\"p\">,</span>\n                                           <span class=\"s1\">&#39;ProxyPassword&#39;</span><span class=\"p\">:</span> <span\n        class=\"n\">proxy</span><span class=\"o\">.</span><span class=\"n\">password</span><span class=\"p\">,</span>\n                                           <span class=\"s1\">&#39;ProxyUser&#39;</span><span class=\"p\">:</span> <span\n        class=\"n\">proxy</span><span class=\"o\">.</span><span class=\"n\">username</span><span class=\"p\">}</span>\n\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/GetQRCode&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n\n                <span class=\"k\">if</span> <span class=\"n\">print_qr</span><span class=\"p\">:</span>\n                    <span class=\"n\">qr</span> <span class=\"o\">=</span> <span class=\"n\">qrcode</span><span\n        class=\"o\">.</span><span class=\"n\">QRCode</span><span class=\"p\">(</span>\n                        <span class=\"n\">version</span><span class=\"o\">=</span><span class=\"mi\">1</span><span\n        class=\"p\">,</span>\n                        <span class=\"n\">error_correction</span><span class=\"o\">=</span><span\n        class=\"n\">qrcode</span><span class=\"o\">.</span><span class=\"n\">constants</span><span class=\"o\">.</span><span\n        class=\"n\">ERROR_CORRECT_L</span><span class=\"p\">,</span>\n                        <span class=\"n\">box_size</span><span class=\"o\">=</span><span class=\"mi\">10</span><span\n        class=\"p\">,</span>\n                        <span class=\"n\">border</span><span class=\"o\">=</span><span class=\"mi\">4</span><span\n        class=\"p\">,</span>\n                    <span class=\"p\">)</span>\n                    <span class=\"n\">qr</span><span class=\"o\">.</span><span class=\"n\">add_data</span><span\n        class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://weixin.qq.com/x/</span><span class=\"si\">{</span><span\n        class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;Uuid&quot;</span><span\n        class=\"p\">)</span><span class=\"si\">}</span><span class=\"s1\">&#39;</span><span class=\"p\">)</span>\n                    <span class=\"n\">qr</span><span class=\"o\">.</span><span class=\"n\">make</span><span class=\"p\">(</span><span\n        class=\"n\">fit</span><span class=\"o\">=</span><span class=\"kc\">True</span><span class=\"p\">)</span>\n                    <span class=\"n\">qr</span><span class=\"o\">.</span><span class=\"n\">print_ascii</span><span class=\"p\">()</span>\n\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;Uuid&quot;</span><span\n        class=\"p\">),</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;QRCodeURL&quot;</span><span\n        class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"LoginMixin.check_login_uuid\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.login.LoginMixin.check_login_uuid\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">check_login_uuid</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">,</span> <span class=\"n\">uuid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"n\">device_id</span><span class=\"p\">:</span> <span class=\"nb\">str</span> <span\n        class=\"o\">=</span> <span class=\"s2\">&quot;&quot;</span><span class=\"p\">)</span> <span\n        class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span><span class=\"nb\">bool</span><span\n        class=\"p\">,</span> <span class=\"n\">Union</span><span class=\"p\">[</span><span class=\"nb\">dict</span><span\n        class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">]]:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;检查登录的UUID状态。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            uuid (str): 登录的UUID</span>\n<span class=\"sd\">            device_id (str, optional): 设备ID. Defaults to &quot;&quot;.</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            tuple[bool, Union[dict, int]]: 如果登录成功返回(True, 用户信息)，否则返回(False, 过期时间)</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Uuid&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">uuid</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/CheckUuid&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;acctSectResp&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;&quot;</span><span class=\"p\">):</span>\n                    <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span> <span\n        class=\"o\">=</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;acctSectResp&quot;</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;userName&quot;</span><span\n        class=\"p\">)</span>\n                    <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">nickname</span> <span class=\"o\">=</span> <span\n        class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;acctSectResp&quot;</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;nickName&quot;</span><span\n        class=\"p\">)</span>\n                    <span class=\"n\">protector</span><span class=\"o\">.</span><span\n        class=\"n\">update_login_status</span><span class=\"p\">(</span><span class=\"n\">device_id</span><span\n        class=\"o\">=</span><span class=\"n\">device_id</span><span class=\"p\">)</span>\n                    <span class=\"k\">return</span> <span class=\"kc\">True</span><span class=\"p\">,</span> <span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span>\n                <span class=\"k\">else</span><span class=\"p\">:</span>\n                    <span class=\"k\">return</span> <span class=\"kc\">False</span><span class=\"p\">,</span> <span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;expiredTime&quot;</span><span\n        class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"LoginMixin.log_out\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.login.LoginMixin.log_out\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">log_out</span><span\n        class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span\n        class=\"nb\">bool</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;登出当前账号。</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bool: 登出成功返回True，否则返回False</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 如果未登录时调用</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span class=\"s1\">/Logout&#39;</span><span\n        class=\"p\">,</span> <span class=\"n\">json</span><span class=\"o\">=</span><span class=\"n\">json_param</span><span\n        class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"kc\">True</span>\n            <span class=\"k\">elif</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"kc\">False</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"LoginMixin.awaken_login\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.login.LoginMixin.awaken_login\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">awaken_login</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span> <span class=\"o\">=</span> <span\n        class=\"s2\">&quot;&quot;</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span\n        class=\"nb\">str</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;唤醒登录。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid (str, optional): 要唤醒的微信ID. Defaults to &quot;&quot;.</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            str: 返回新的登录UUID</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            Exception: 如果未提供wxid且未登录</span>\n<span class=\"sd\">            LoginError: 如果无法获取UUID</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">wxid</span> <span\n        class=\"ow\">and</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"ne\">Exception</span><span class=\"p\">(</span><span class=\"s2\">&quot;Please login using QRCode first&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">wxid</span> <span\n        class=\"ow\">and</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">:</span>\n            <span class=\"n\">wxid</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">wxid</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">wxid</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/AwakenLogin&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">)</span> <span\n        class=\"ow\">and</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;QrCodeResponse&quot;</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;Uuid&quot;</span><span\n        class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;QrCodeResponse&quot;</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;Uuid&quot;</span><span\n        class=\"p\">)</span>\n            <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;QrCodeResponse&quot;</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;Uuid&quot;</span><span\n        class=\"p\">):</span>\n                <span class=\"k\">raise</span> <span class=\"n\">LoginError</span><span class=\"p\">(</span><span class=\"s2\">&quot;Please login using QRCode first&quot;</span><span\n        class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"LoginMixin.get_cached_info\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.login.LoginMixin.get_cached_info\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">get_cached_info</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">,</span> <span class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span> <span\n        class=\"o\">=</span> <span class=\"kc\">None</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span\n        class=\"nb\">dict</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;获取登录缓存信息。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid (str, optional): 要查询的微信ID. Defaults to None.</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            dict: 返回缓存信息，如果未提供wxid且未登录返回空字典</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"n\">wxid</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">wxid</span>\n\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">return</span> <span class=\"p\">{}</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">wxid</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/GetCachedInfo&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"k\">return</span> <span class=\"p\">{}</span></div>\n\n\n<div class=\"viewcode-block\" id=\"LoginMixin.heartbeat\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.login.LoginMixin.heartbeat\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">heartbeat</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">)</span> <span\n        class=\"o\">-&gt;</span> <span class=\"nb\">bool</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;发送心跳包。</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bool: 成功返回True，否则返回False</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 如果未登录时调用</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/Heartbeat&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"kc\">True</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"LoginMixin.start_auto_heartbeat\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">start_auto_heartbeat</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">bool</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;开始自动心跳。</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bool: 成功返回True，否则返回False</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 如果未登录时调用</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/AutoHeartbeatStart&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"kc\">True</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"LoginMixin.stop_auto_heartbeat\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">stop_auto_heartbeat</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">bool</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;停止自动心跳。</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bool: 成功返回True，否则返回False</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 如果未登录时调用</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/AutoHeartbeatStop&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"kc\">True</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"LoginMixin.get_auto_heartbeat_status\">\n<a class=\"viewcode-back\"\n   href=\"../../../index.html#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">get_auto_heartbeat_status</span><span\n        class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span\n        class=\"nb\">bool</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;获取自动心跳状态。</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bool: 如果正在运行返回True，否则返回False</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 如果未登录时调用</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/AutoHeartbeatStatus&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;Running&quot;</span><span class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"k\">return</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span\n        class=\"p\">(</span><span class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"LoginMixin.create_device_name\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.login.LoginMixin.create_device_name\">[文档]</a>\n    <span class=\"nd\">@staticmethod</span>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">create_device_name</span><span\n        class=\"p\">()</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;生成一个随机的设备名。</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            str: 返回生成的设备名</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"n\">first_names</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n            <span class=\"s2\">&quot;Oliver&quot;</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;Emma&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Liam&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Ava&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Noah&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Sophia&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Elijah&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Isabella&quot;</span><span class=\"p\">,</span>\n            <span class=\"s2\">&quot;James&quot;</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;Mia&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;William&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Amelia&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Benjamin&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Harper&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Lucas&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Evelyn&quot;</span><span class=\"p\">,</span>\n            <span class=\"s2\">&quot;Henry&quot;</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;Abigail&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Alexander&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Ella&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Jackson&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Scarlett&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Sebastian&quot;</span><span\n        class=\"p\">,</span>\n            <span class=\"s2\">&quot;Grace&quot;</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;Aiden&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Chloe&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Matthew&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Zoey&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Samuel&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Lily&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;David&quot;</span><span class=\"p\">,</span>\n            <span class=\"s2\">&quot;Aria&quot;</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;Joseph&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Riley&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Carter&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Nora&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Owen&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Luna&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Daniel&quot;</span><span class=\"p\">,</span>\n            <span class=\"s2\">&quot;Sofia&quot;</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;Gabriel&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Ellie&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Matthew&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Avery&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Isaac&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Mila&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Leo&quot;</span><span class=\"p\">,</span>\n            <span class=\"s2\">&quot;Julian&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Layla&quot;</span>\n        <span class=\"p\">]</span>\n\n        <span class=\"n\">last_names</span> <span class=\"o\">=</span> <span class=\"p\">[</span>\n            <span class=\"s2\">&quot;Smith&quot;</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;Johnson&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Williams&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Brown&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Jones&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Garcia&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Miller&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Davis&quot;</span><span class=\"p\">,</span>\n            <span class=\"s2\">&quot;Rodriguez&quot;</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;Martinez&quot;</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;Hernandez&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Lopez&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Gonzalez&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Wilson&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Anderson&quot;</span><span class=\"p\">,</span>\n            <span class=\"s2\">&quot;Thomas&quot;</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;Taylor&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Moore&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Jackson&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Martin&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Lee&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Perez&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Thompson&quot;</span><span class=\"p\">,</span>\n            <span class=\"s2\">&quot;White&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Harris&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Sanchez&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Clark&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Ramirez&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Lewis&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Robinson&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Walker&quot;</span><span\n        class=\"p\">,</span>\n            <span class=\"s2\">&quot;Young&quot;</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;Allen&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;King&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Wright&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Scott&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Torres&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Nguyen&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Hill&quot;</span><span class=\"p\">,</span>\n            <span class=\"s2\">&quot;Flores&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Green&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Adams&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Nelson&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Baker&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Hall&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Rivera&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Campbell&quot;</span><span\n        class=\"p\">,</span>\n            <span class=\"s2\">&quot;Mitchell&quot;</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;Carter&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Roberts&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Gomez&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Phillips&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Evans&quot;</span>\n        <span class=\"p\">]</span>\n\n        <span class=\"k\">return</span> <span class=\"n\">choice</span><span class=\"p\">(</span><span\n        class=\"n\">first_names</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span\n        class=\"s2\">&quot; &quot;</span> <span class=\"o\">+</span> <span class=\"n\">choice</span><span\n        class=\"p\">(</span><span class=\"n\">last_names</span><span class=\"p\">)</span> <span class=\"o\">+</span> <span\n        class=\"s2\">&quot;&#39;s Pad&quot;</span></div>\n\n\n<div class=\"viewcode-block\" id=\"LoginMixin.create_device_id\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.login.LoginMixin.create_device_id\">[文档]</a>\n    <span class=\"nd\">@staticmethod</span>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">create_device_id</span><span\n        class=\"p\">(</span><span class=\"n\">s</span><span class=\"p\">:</span> <span class=\"nb\">str</span> <span\n        class=\"o\">=</span> <span class=\"s2\">&quot;&quot;</span><span class=\"p\">)</span> <span\n        class=\"o\">-&gt;</span> <span class=\"nb\">str</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;生成设备ID。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            s (str, optional): 用于生成ID的字符串. Defaults to &quot;&quot;.</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            str: 返回生成的设备ID</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"n\">s</span> <span class=\"o\">==</span> <span\n        class=\"s2\">&quot;&quot;</span> <span class=\"ow\">or</span> <span class=\"n\">s</span> <span\n        class=\"o\">==</span> <span class=\"s2\">&quot;string&quot;</span><span class=\"p\">:</span>\n            <span class=\"n\">s</span> <span class=\"o\">=</span> <span class=\"s1\">&#39;&#39;</span><span class=\"o\">.</span><span\n        class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">choice</span><span class=\"p\">(</span><span\n        class=\"n\">string</span><span class=\"o\">.</span><span class=\"n\">ascii_letters</span><span\n        class=\"p\">)</span> <span class=\"k\">for</span> <span class=\"n\">_</span> <span class=\"ow\">in</span> <span\n        class=\"nb\">range</span><span class=\"p\">(</span><span class=\"mi\">15</span><span class=\"p\">))</span>\n        <span class=\"n\">md5_hash</span> <span class=\"o\">=</span> <span class=\"n\">hashlib</span><span\n        class=\"o\">.</span><span class=\"n\">md5</span><span class=\"p\">(</span><span class=\"n\">s</span><span\n        class=\"o\">.</span><span class=\"n\">encode</span><span class=\"p\">())</span><span class=\"o\">.</span><span\n        class=\"n\">hexdigest</span><span class=\"p\">()</span>\n        <span class=\"k\">return</span> <span class=\"s2\">&quot;49&quot;</span> <span class=\"o\">+</span> <span class=\"n\">md5_hash</span><span\n        class=\"p\">[</span><span class=\"mi\">2</span><span class=\"p\">:]</span></div>\n</div>\n\n</pre>\n                    </div>\n                </article>\n            </div>\n            <footer>\n\n                <div class=\"related-pages\">\n\n\n                </div>\n                <div class=\"bottom-of-page\">\n                    <div class=\"left-details\">\n                        <div class=\"copyright\">\n                            Copyright &#169; 2025, HenryXiaoYang\n                        </div>\n                        Made with <a href=\"https://www.sphinx-doc.org/\">Sphinx</a> and <a class=\"muted-link\"\n                                                                                          href=\"https://pradyunsg.me\">@pradyunsg</a>'s\n\n                        <a href=\"https://github.com/pradyunsg/furo\">Furo</a>\n\n                    </div>\n                    <div class=\"right-details\">\n\n                    </div>\n                </div>\n\n            </footer>\n        </div>\n        <aside class=\"toc-drawer no-toc\">\n\n\n        </aside>\n    </div>\n</div>\n<script src=\"../../../_static/documentation_options.js?v=91bfbbb6\"></script>\n<script src=\"../../../_static/doctools.js?v=9bcbadda\"></script>\n<script src=\"../../../_static/sphinx_highlight.js?v=dc90522c\"></script>\n<script src=\"../../../_static/scripts/furo.js?v=5fa4622c\"></script>\n<script src=\"../../../_static/translations.js?v=beaddf03\"></script>\n</body>\n</html>"
  },
  {
    "path": "docs/WechatAPIClient/_modules/WechatAPI/Client/message.html",
    "content": "<!doctype html>\n<html class=\"no-js\" data-content_root=\"../../../\" lang=\"zh-CN\">\n<head>\n    <meta charset=\"utf-8\"/>\n    <meta content=\"width=device-width,initial-scale=1\" name=\"viewport\"/>\n    <meta content=\"light dark\" name=\"color-scheme\">\n    <link href=\"../../../genindex.html\" rel=\"index\" title=\"索引\"/>\n    <link href=\"../../../search.html\" rel=\"search\" title=\"搜索\"/>\n\n    <!-- Generated with Sphinx 8.1.3 and Furo 2024.08.06 -->\n    <title>WechatAPI.Client.message - XYBotV2</title>\n    <link href=\"../../../_static/pygments.css?v=8f2a1f02\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo.css?v=354aac6f\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo-extensions.css?v=302659d7\" rel=\"stylesheet\" type=\"text/css\"/>\n\n\n    <style>\n        body {\n            --color-code-background: #f8f8f8;\n            --color-code-foreground: black;\n            --color-brand-primary: #2962ff;\n            --color-brand-content: #2962ff;\n\n        }\n\n        @media not print {\n            body[data-theme=\"dark\"] {\n                --color-code-background: #202020;\n                --color-code-foreground: #d0d0d0;\n\n            }\n\n            @media (prefers-color-scheme: dark) {\n                body:not([data-theme=\"light\"]) {\n                    --color-code-background: #202020;\n                    --color-code-foreground: #d0d0d0;\n\n                }\n            }\n        }\n    </style>\n</head>\n<body>\n\n<script>\n    document.body.dataset.theme = localStorage.getItem(\"theme\") || \"auto\";\n</script>\n\n\n<svg style=\"display: none;\" xmlns=\"http://www.w3.org/2000/svg\">\n    <symbol id=\"svg-toc\" viewBox=\"0 0 24 24\">\n        <title>Contents</title>\n        <svg fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 1024 1024\">\n            <path d=\"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-menu\" viewBox=\"0 0 24 24\">\n        <title>Menu</title>\n        <svg class=\"feather-menu\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-arrow-right\" viewBox=\"0 0 24 24\">\n        <title>Expand</title>\n        <svg class=\"feather-chevron-right\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <polyline points=\"9 18 15 12 9 6\"></polyline>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun\" viewBox=\"0 0 24 24\">\n        <title>Light mode</title>\n        <svg class=\"feather-sun\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\n            <line x1=\"12\" x2=\"12\" y1=\"1\" y2=\"3\"></line>\n            <line x1=\"12\" x2=\"12\" y1=\"21\" y2=\"23\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"4.22\" y2=\"5.64\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"18.36\" y2=\"19.78\"></line>\n            <line x1=\"1\" x2=\"3\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"21\" x2=\"23\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"19.78\" y2=\"18.36\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"5.64\" y2=\"4.22\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon\" viewBox=\"0 0 24 24\">\n        <title>Dark mode</title>\n        <svg class=\"icon-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun-with-moon\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in light mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 5.411 14.504 C 5.471 14.504 5.532 14.504 5.591 14.504 C 3.639 16.319 4.383 19.569 6.931 20.352 C 7.693 20.586 8.512 20.551 9.25 20.252 C 8.023 23.207 4.056 23.725 2.11 21.184 C 0.166 18.642 1.702 14.949 4.874 14.536 C 5.051 14.512 5.231 14.5 5.411 14.5 L 5.411 14.504 Z\"\n                  style=\"opacity: 50%\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"3.25\" y2=\"1.25\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"15.85\" y2=\"17.85\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"5.094\" y2=\"3.68\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"14.05\" y2=\"15.464\"/>\n            <line x1=\"8.2\" x2=\"6.2\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"20.8\" x2=\"22.8\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"14.006\" y2=\"15.42\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"5.05\" y2=\"3.636\"/>\n            <circle cx=\"14.5\" cy=\"9.55\" r=\"3.6\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon-with-sun\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in dark mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 8.282 7.007 C 8.385 7.007 8.494 7.007 8.595 7.007 C 5.18 10.184 6.481 15.869 10.942 17.24 C 12.275 17.648 13.706 17.589 15 17.066 C 12.851 22.236 5.91 23.143 2.505 18.696 C -0.897 14.249 1.791 7.786 7.342 7.063 C 7.652 7.021 7.965 7 8.282 7 L 8.282 7.007 Z\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"3.705\" y2=\"2.5\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"11.295\" y2=\"12.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"4.816\" y2=\"3.964\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"10.212\" y2=\"11.063\"/>\n            <line style=\"opacity: 50%\" x1=\"14.205\" x2=\"13.001\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"21.795\" x2=\"23\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"10.184\" y2=\"11.036\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"4.789\" y2=\"3.937\"/>\n            <circle cx=\"18\" cy=\"7.5\" r=\"2.169\" style=\"opacity: 50%\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-pencil\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-pencil-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4\"/>\n            <path d=\"M13.5 6.5l4 4\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-eye\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-eye-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\"/>\n            <path\n                    d=\"M11.11 17.958c-3.209 -.307 -5.91 -2.293 -8.11 -5.958c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6c-.21 .352 -.427 .688 -.647 1.008\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n</svg>\n\n<input class=\"sidebar-toggle\" id=\"__navigation\" name=\"__navigation\" type=\"checkbox\">\n<input class=\"sidebar-toggle\" id=\"__toc\" name=\"__toc\" type=\"checkbox\">\n<label class=\"overlay sidebar-overlay\" for=\"__navigation\">\n    <div class=\"visually-hidden\">Hide navigation sidebar</div>\n</label>\n<label class=\"overlay toc-overlay\" for=\"__toc\">\n    <div class=\"visually-hidden\">Hide table of contents sidebar</div>\n</label>\n\n<a class=\"skip-to-content muted-link\" href=\"#furo-main-content\">Skip to content</a>\n\n\n\n<div class=\"page\">\n    <header class=\"mobile-header\">\n        <div class=\"header-left\">\n            <label class=\"nav-overlay-icon\" for=\"__navigation\">\n                <div class=\"visually-hidden\">Toggle site navigation sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-menu\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n        <div class=\"header-center\">\n            <a href=\"../../../index.html\">\n                <div class=\"brand\">XYBotV2</div>\n            </a>\n        </div>\n        <div class=\"header-right\">\n            <div class=\"theme-toggle-container theme-toggle-header\">\n                <button class=\"theme-toggle\">\n                    <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                    <svg class=\"theme-icon-when-auto-light\">\n                        <use href=\"#svg-sun-with-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-auto-dark\">\n                        <use href=\"#svg-moon-with-sun\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-dark\">\n                        <use href=\"#svg-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-light\">\n                        <use href=\"#svg-sun\"></use>\n                    </svg>\n                </button>\n            </div>\n            <label class=\"toc-overlay-icon toc-header-icon no-toc\" for=\"__toc\">\n                <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-toc\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n    </header>\n    <aside class=\"sidebar-drawer\">\n        <div class=\"sidebar-container\">\n\n            <div class=\"sidebar-sticky\">\n                <div class=\"sidebar-scroll\"><a class=\"sidebar-brand\" href=\"../../../index.html\">\n\n\n                    <span class=\"sidebar-brand-text\">XYBotV2</span>\n\n                </a>\n                    <form action=\"../../../search.html\" class=\"sidebar-search-container\" method=\"get\" role=\"search\">\n                        <input aria-label=\"搜索\" class=\"sidebar-search\" name=\"q\" placeholder=\"搜索\">\n                        <input name=\"check_keywords\" type=\"hidden\" value=\"yes\">\n                        <input name=\"area\" type=\"hidden\" value=\"default\">\n                    </form>\n                    <div id=\"searchbox\"></div>\n                    <div class=\"sidebar-tree\">\n\n                    </div>\n                    <div class=\"sidebar-tree\">\n                        <p class=\"caption\"><span class=\"caption-text\">重要函数导航</span></p>\n                        <ul>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">登录</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.is_running\">检查WechatAPI是否在运行</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_qr_code\">获取登录二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.awaken_login\">二次登录(唤醒登录)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.check_login_uuid\">检查登录的UUID状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_cached_info\">获取登录缓存信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.log_out\">登出当前账号</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.heartbeat\">发送心跳包</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\">开始自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\">停止自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\">获取自动心跳状态</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">消息</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.sync_message\">同步消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_text_message\">发送文本消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_image_message\">发送图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_voice_message\">发送语音消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_video_message\">发送视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_link_message\">发送链接消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_card_message\">发送名片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_app_message\">发送应用(xml)消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_emoji_message\">发送表情消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\">转发图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\">转发视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\">转发文件消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.revoke_message\">撤回消息</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">用户</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_my_qrcode\">获取个人二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_profile\">获取用户信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.is_logged_in\">检查是否登录</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">群聊</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\">获取群聊信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\">获取群聊公告</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\">获取群聊成员列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\">获取群聊二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\">添加群成员(群聊最多40人)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\">邀请群聊成员(群聊大于40人)</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">好友</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contact\">获取联系人信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_detail\">获取联系人详情</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_list\">获取联系人列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_nickname\">获取用户昵称</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.accept_friend\">接受好友请求</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">红包</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\">获取红包详情</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">工具</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.check_database\">检查数据库状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.set_step\">设置步数</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_image\">下载高清图片</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_video\">下载视频</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_voice\">下载语音文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_attach\">下载附件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_byte\">base64转字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_file\">base64转文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.byte_to_base64\">字节转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.file_to_base64\">文件转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\">silk的base64转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\">silk字节转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\">WAV字节转AMR的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\">WAV字节转AMR字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\">WAV字节转silk的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\">WAV字节转silk字节</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                        </ul>\n                    </div>\n                </div>\n            </div>\n\n        </div>\n    </aside>\n    <div class=\"main\">\n        <div class=\"content\">\n            <div class=\"article-container\">\n                <a class=\"back-to-top muted-link\" href=\"#\">\n                    <svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n                        <path d=\"M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z\"></path>\n                    </svg>\n                    <span>Back to top</span>\n                </a>\n                <div class=\"content-icon-container\">\n                    <div class=\"theme-toggle-container theme-toggle-content\">\n                        <button class=\"theme-toggle\">\n                            <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                            <svg class=\"theme-icon-when-auto-light\">\n                                <use href=\"#svg-sun-with-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-auto-dark\">\n                                <use href=\"#svg-moon-with-sun\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-dark\">\n                                <use href=\"#svg-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-light\">\n                                <use href=\"#svg-sun\"></use>\n                            </svg>\n                        </button>\n                    </div>\n                    <label class=\"toc-overlay-icon toc-content-icon no-toc\" for=\"__toc\">\n                        <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                        <i class=\"icon\">\n                            <svg>\n                                <use href=\"#svg-toc\"></use>\n                            </svg>\n                        </i>\n                    </label>\n                </div>\n                <article id=\"furo-main-content\" role=\"main\">\n                    <h1>WechatAPI.Client.message 源代码</h1>\n                    <div class=\"highlight\"><pre>\n<span></span><span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">asyncio</span>\n<span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">base64</span>\n<span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">os</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">asyncio</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"n\">Future</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">asyncio</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"n\">Queue</span><span class=\"p\">,</span> <span\n                            class=\"n\">sleep</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">io</span><span class=\"w\"> </span><span class=\"kn\">import</span> <span\n                            class=\"n\">BytesIO</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">pathlib</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"n\">Path</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">typing</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"n\">Union</span>\n\n<span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">aiohttp</span>\n<span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">pysilk</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">loguru</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"n\">logger</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">pydub</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"n\">AudioSegment</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">pymediainfo</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"n\">MediaInfo</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.base</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"o\">*</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.protect</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"n\">protector</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">..errors</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"o\">*</span>\n\n\n<div class=\"viewcode-block\" id=\"MessageMixin\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.message.MessageMixin\">[文档]</a>\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">MessageMixin</span><span class=\"p\">(</span><span\n        class=\"n\">WechatAPIClientBase</span><span class=\"p\">):</span>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"fm\">__init__</span><span class=\"p\">(</span><span\n        class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">ip</span><span class=\"p\">:</span> <span\n        class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"n\">port</span><span class=\"p\">:</span> <span\n        class=\"nb\">int</span><span class=\"p\">):</span>\n        <span class=\"c1\"># 初始化消息队列</span>\n        <span class=\"nb\">super</span><span class=\"p\">()</span><span class=\"o\">.</span><span\n        class=\"fm\">__init__</span><span class=\"p\">(</span><span class=\"n\">ip</span><span class=\"p\">,</span> <span\n        class=\"n\">port</span><span class=\"p\">)</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">_message_queue</span> <span\n        class=\"o\">=</span> <span class=\"n\">Queue</span><span class=\"p\">()</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">_is_processing</span> <span\n        class=\"o\">=</span> <span class=\"kc\">False</span>\n\n<div class=\"viewcode-block\" id=\"MessageMixin._process_message_queue\">\n<a class=\"viewcode-back\"\n   href=\"../../../index.html#WechatAPI.Client.message.MessageMixin._process_message_queue\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">_process_message_queue</span><span\n        class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">        处理消息队列的异步方法</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">_is_processing</span><span class=\"p\">:</span>\n            <span class=\"k\">return</span>\n\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">_is_processing</span> <span\n        class=\"o\">=</span> <span class=\"kc\">True</span>\n        <span class=\"k\">while</span> <span class=\"kc\">True</span><span class=\"p\">:</span>\n            <span class=\"k\">if</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">_message_queue</span><span class=\"o\">.</span><span class=\"n\">empty</span><span class=\"p\">():</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">_is_processing</span> <span\n        class=\"o\">=</span> <span class=\"kc\">False</span>\n                <span class=\"k\">break</span>\n\n            <span class=\"n\">func</span><span class=\"p\">,</span> <span class=\"n\">args</span><span\n        class=\"p\">,</span> <span class=\"n\">kwargs</span><span class=\"p\">,</span> <span class=\"n\">future</span> <span\n        class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">_message_queue</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">()</span>\n            <span class=\"k\">try</span><span class=\"p\">:</span>\n                <span class=\"n\">result</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span\n        class=\"n\">func</span><span class=\"p\">(</span><span class=\"o\">*</span><span class=\"n\">args</span><span class=\"p\">,</span> <span\n        class=\"o\">**</span><span class=\"n\">kwargs</span><span class=\"p\">)</span>\n                <span class=\"n\">future</span><span class=\"o\">.</span><span class=\"n\">set_result</span><span\n        class=\"p\">(</span><span class=\"n\">result</span><span class=\"p\">)</span>\n            <span class=\"k\">except</span> <span class=\"ne\">Exception</span> <span class=\"k\">as</span> <span\n        class=\"n\">e</span><span class=\"p\">:</span>\n                <span class=\"n\">future</span><span class=\"o\">.</span><span class=\"n\">set_exception</span><span\n        class=\"p\">(</span><span class=\"n\">e</span><span class=\"p\">)</span>\n            <span class=\"k\">finally</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">_message_queue</span><span\n        class=\"o\">.</span><span class=\"n\">task_done</span><span class=\"p\">()</span>\n                <span class=\"k\">await</span> <span class=\"n\">sleep</span><span class=\"p\">(</span><span\n        class=\"mi\">1</span><span class=\"p\">)</span>  <span class=\"c1\"># 消息发送间隔1秒</span></div>\n\n\n<div class=\"viewcode-block\" id=\"MessageMixin._queue_message\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.message.MessageMixin._queue_message\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">_queue_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">,</span> <span class=\"n\">func</span><span class=\"p\">,</span> <span class=\"o\">*</span><span class=\"n\">args</span><span\n        class=\"p\">,</span> <span class=\"o\">**</span><span class=\"n\">kwargs</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">        将消息添加到队列</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"n\">future</span> <span class=\"o\">=</span> <span class=\"n\">Future</span><span class=\"p\">()</span>\n        <span class=\"k\">await</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">_message_queue</span><span class=\"o\">.</span><span class=\"n\">put</span><span class=\"p\">((</span><span\n        class=\"n\">func</span><span class=\"p\">,</span> <span class=\"n\">args</span><span class=\"p\">,</span> <span\n        class=\"n\">kwargs</span><span class=\"p\">,</span> <span class=\"n\">future</span><span class=\"p\">))</span>\n\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">_is_processing</span><span class=\"p\">:</span>\n            <span class=\"n\">asyncio</span><span class=\"o\">.</span><span class=\"n\">create_task</span><span\n        class=\"p\">(</span><span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">_process_message_queue</span><span class=\"p\">())</span>\n\n        <span class=\"k\">return</span> <span class=\"k\">await</span> <span class=\"n\">future</span></div>\n\n\n<div class=\"viewcode-block\" id=\"MessageMixin.revoke_message\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.message.MessageMixin.revoke_message\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">revoke_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">,</span> <span class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"n\">client_msg_id</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span\n        class=\"p\">,</span> <span class=\"n\">create_time</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span\n        class=\"p\">,</span> <span class=\"n\">new_msg_id</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">bool</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;撤回消息。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid (str): 接收人wxid</span>\n<span class=\"sd\">            client_msg_id (int): 发送消息的返回值</span>\n<span class=\"sd\">            create_time (int): 发送消息的返回值</span>\n<span class=\"sd\">            new_msg_id (int): 发送消息的返回值</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bool: 成功返回True，失败返回False</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            BanProtection: 登录新设备后4小时内操作</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span class=\"ow\">and</span> <span\n        class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span class=\"p\">(</span><span\n        class=\"mi\">14400</span><span class=\"p\">):</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;风控保护: 新设备登录后4小时内请挂机&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;ToWxid&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"s2\">&quot;ClientMsgId&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">client_msg_id</span><span class=\"p\">,</span> <span class=\"s2\">&quot;CreateTime&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">create_time</span><span class=\"p\">,</span>\n                          <span class=\"s2\">&quot;NewMsgId&quot;</span><span class=\"p\">:</span> <span class=\"n\">new_msg_id</span><span\n        class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/RevokeMsg&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"n\">logger</span><span class=\"o\">.</span><span class=\"n\">info</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;消息撤回成功: 对方wxid:</span><span class=\"si\">{}</span><span\n        class=\"s2\"> ClientMsgId:</span><span class=\"si\">{}</span><span class=\"s2\"> CreateTime:</span><span\n        class=\"si\">{}</span><span class=\"s2\"> NewMsgId:</span><span class=\"si\">{}</span><span\n        class=\"s2\">&quot;</span><span class=\"p\">,</span>\n                            <span class=\"n\">wxid</span><span class=\"p\">,</span>\n                            <span class=\"n\">client_msg_id</span><span class=\"p\">,</span>\n                            <span class=\"n\">new_msg_id</span><span class=\"p\">)</span>\n                <span class=\"k\">return</span> <span class=\"kc\">True</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"MessageMixin.send_text_message\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.message.MessageMixin.send_text_message\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">send_text_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">content</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">at</span><span class=\"p\">:</span> <span class=\"n\">Union</span><span class=\"p\">[</span><span\n        class=\"nb\">list</span><span class=\"p\">,</span> <span class=\"nb\">str</span><span class=\"p\">]</span> <span\n        class=\"o\">=</span> <span class=\"s2\">&quot;&quot;</span><span class=\"p\">)</span> <span\n        class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span><span class=\"nb\">int</span><span\n        class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span\n        class=\"p\">]:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;发送文本消息。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid (str): 接收人wxid</span>\n<span class=\"sd\">            content (str): 消息内容</span>\n<span class=\"sd\">            at (list, str, optional): 要@的用户</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            tuple[int, int, int]: 返回(ClientMsgid, CreateTime, NewMsgId)</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            BanProtection: 登录新设备后4小时内操作</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"k\">await</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_queue_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_send_text_message</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"n\">content</span><span class=\"p\">,</span> <span\n        class=\"n\">at</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"MessageMixin._send_text_message\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.message.MessageMixin._send_text_message\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">_send_text_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">content</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">at</span><span class=\"p\">:</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span\n        class=\"nb\">str</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"kc\">None</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span><span\n        class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span\n        class=\"nb\">int</span><span class=\"p\">]:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;</span>\n<span class=\"sd\">        实际发送文本消息的方法</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span class=\"ow\">and</span> <span\n        class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span class=\"p\">(</span><span\n        class=\"mi\">14400</span><span class=\"p\">):</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;风控保护: 新设备登录后4小时内请挂机&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">if</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span\n        class=\"n\">at</span><span class=\"p\">,</span> <span class=\"nb\">str</span><span class=\"p\">):</span>\n            <span class=\"n\">at_str</span> <span class=\"o\">=</span> <span class=\"n\">at</span>\n        <span class=\"k\">elif</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span\n        class=\"n\">at</span><span class=\"p\">,</span> <span class=\"nb\">list</span><span class=\"p\">):</span>\n            <span class=\"k\">if</span> <span class=\"n\">at</span> <span class=\"ow\">is</span> <span\n        class=\"kc\">None</span><span class=\"p\">:</span>\n                <span class=\"n\">at</span> <span class=\"o\">=</span> <span class=\"p\">[]</span>\n            <span class=\"n\">at_str</span> <span class=\"o\">=</span> <span class=\"s2\">&quot;,&quot;</span><span class=\"o\">.</span><span\n        class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">at</span><span class=\"p\">)</span>\n        <span class=\"k\">else</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"ne\">ValueError</span><span class=\"p\">(</span><span class=\"s2\">&quot;Argument &#39;at&#39; should be str or list&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;ToWxid&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Content&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">content</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;Type&quot;</span><span class=\"p\">:</span> <span class=\"mi\">1</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;At&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">at_str</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/SendTextMsg&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"n\">logger</span><span class=\"o\">.</span><span class=\"n\">info</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;发送文字消息: 对方wxid:</span><span class=\"si\">{}</span><span class=\"s2\"> at:</span><span\n        class=\"si\">{}</span><span class=\"s2\"> 内容:</span><span class=\"si\">{}</span><span class=\"s2\">&quot;</span><span\n        class=\"p\">,</span> <span class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"n\">at</span><span class=\"p\">,</span> <span\n        class=\"n\">content</span><span class=\"p\">)</span>\n                <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span>\n                <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;List&quot;</span><span\n        class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">]</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;ClientMsgid&quot;</span><span\n        class=\"p\">),</span> <span class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;List&quot;</span><span class=\"p\">)[</span><span\n        class=\"mi\">0</span><span class=\"p\">]</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Createtime&quot;</span><span class=\"p\">),</span> <span\n        class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;List&quot;</span><span\n        class=\"p\">)[</span>\n                    <span class=\"mi\">0</span><span class=\"p\">]</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;NewMsgId&quot;</span><span class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"MessageMixin.send_image_message\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.message.MessageMixin.send_image_message\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">send_image_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">image</span><span class=\"p\">:</span> <span class=\"n\">Union</span><span class=\"p\">[</span><span\n        class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"nb\">bytes</span><span class=\"p\">,</span> <span\n        class=\"n\">os</span><span class=\"o\">.</span><span class=\"n\">PathLike</span><span class=\"p\">])</span> <span\n        class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span><span class=\"nb\">int</span><span\n        class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span\n        class=\"p\">]:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;发送图片消息。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid (str): 接收人wxid</span>\n<span class=\"sd\">            image (str, byte, os.PathLike): 图片，支持base64字符串，图片byte，图片路径</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            tuple[int, int, int]: 返回(ClientImgId, CreateTime, NewMsgId)</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            BanProtection: 登录新设备后4小时内操作</span>\n<span class=\"sd\">            ValueError: image_path和image_base64都为空或都不为空时</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"k\">await</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_queue_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_send_image_message</span><span class=\"p\">,</span> <span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"n\">image</span><span class=\"p\">)</span></div>\n\n\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">_send_image_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">,</span> <span class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"n\">image</span><span class=\"p\">:</span> <span class=\"n\">Union</span><span\n        class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"nb\">bytes</span><span\n        class=\"p\">,</span> <span class=\"n\">os</span><span class=\"o\">.</span><span class=\"n\">PathLike</span><span\n        class=\"p\">])</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span>\n        <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span\n        class=\"nb\">int</span><span class=\"p\">]:</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span class=\"ow\">and</span> <span\n        class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span class=\"p\">(</span><span\n        class=\"mi\">14400</span><span class=\"p\">):</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;风控保护: 新设备登录后4小时内请挂机&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">if</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span class=\"n\">image</span><span\n        class=\"p\">,</span> <span class=\"nb\">str</span><span class=\"p\">):</span>\n            <span class=\"k\">pass</span>\n        <span class=\"k\">elif</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span\n        class=\"n\">image</span><span class=\"p\">,</span> <span class=\"nb\">bytes</span><span class=\"p\">):</span>\n            <span class=\"n\">image</span> <span class=\"o\">=</span> <span class=\"n\">base64</span><span\n        class=\"o\">.</span><span class=\"n\">b64encode</span><span class=\"p\">(</span><span class=\"n\">image</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">decode</span><span class=\"p\">()</span>\n        <span class=\"k\">elif</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span\n        class=\"n\">image</span><span class=\"p\">,</span> <span class=\"n\">os</span><span class=\"o\">.</span><span class=\"n\">PathLike</span><span\n        class=\"p\">):</span>\n            <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">image</span><span\n        class=\"p\">,</span> <span class=\"s1\">&#39;rb&#39;</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span\n        class=\"n\">f</span><span class=\"p\">:</span>\n                <span class=\"n\">image</span> <span class=\"o\">=</span> <span class=\"n\">base64</span><span\n        class=\"o\">.</span><span class=\"n\">b64encode</span><span class=\"p\">(</span><span class=\"n\">f</span><span\n        class=\"o\">.</span><span class=\"n\">read</span><span class=\"p\">())</span><span class=\"o\">.</span><span class=\"n\">decode</span><span\n        class=\"p\">()</span>\n        <span class=\"k\">else</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"ne\">ValueError</span><span class=\"p\">(</span><span class=\"s2\">&quot;Argument &#39;image&#39; can only be str, bytes, or os.PathLike&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;ToWxid&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Base64&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">image</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span class=\"s1\">/SendImageMsg&#39;</span><span\n        class=\"p\">,</span> <span class=\"n\">json</span><span class=\"o\">=</span><span class=\"n\">json_param</span><span\n        class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"n\">json_param</span><span class=\"o\">.</span><span class=\"n\">pop</span><span\n        class=\"p\">(</span><span class=\"s1\">&#39;Base64&#39;</span><span class=\"p\">)</span>\n                <span class=\"n\">logger</span><span class=\"o\">.</span><span class=\"n\">info</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;发送图片消息: 对方wxid:</span><span class=\"si\">{}</span><span\n        class=\"s2\"> 图片base64略&quot;</span><span class=\"p\">,</span> <span class=\"n\">wxid</span><span\n        class=\"p\">)</span>\n                <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span>\n                <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;ClientImgId&quot;</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;string&quot;</span><span\n        class=\"p\">),</span> <span class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;CreateTime&quot;</span><span class=\"p\">),</span> <span\n        class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;Newmsgid&quot;</span><span\n        class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span>\n\n<div class=\"viewcode-block\" id=\"MessageMixin.send_video_message\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.message.MessageMixin.send_video_message\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">send_video_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">video</span><span class=\"p\">:</span> <span class=\"n\">Union</span><span class=\"p\">[</span><span\n        class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"nb\">bytes</span><span class=\"p\">,</span> <span\n        class=\"n\">os</span><span class=\"o\">.</span><span class=\"n\">PathLike</span><span class=\"p\">],</span>\n                                 <span class=\"n\">image</span><span class=\"p\">:</span> <span class=\"p\">[</span><span\n        class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"nb\">bytes</span><span class=\"p\">,</span> <span\n        class=\"n\">os</span><span class=\"o\">.</span><span class=\"n\">PathLike</span><span class=\"p\">]</span> <span\n        class=\"o\">=</span> <span class=\"kc\">None</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span\n        class=\"sd\">&quot;&quot;&quot;发送视频消息。不推荐使用，上传速度很慢300KB/s。如要使用，可压缩视频，或者发送链接卡片而不是视频。</span>\n\n<span class=\"sd\">                Args:</span>\n<span class=\"sd\">                    wxid (str): 接收人wxid</span>\n<span class=\"sd\">                    video (str, bytes, os.PathLike): 视频 接受base64字符串，字节，文件路径</span>\n<span class=\"sd\">                    image (str, bytes, os.PathLike): 视频封面图片 接受base64字符串，字节，文件路径</span>\n\n<span class=\"sd\">                Returns:</span>\n<span class=\"sd\">                    tuple[int, int]: 返回(ClientMsgid, NewMsgId)</span>\n\n<span class=\"sd\">                Raises:</span>\n<span class=\"sd\">                    UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">                    BanProtection: 登录新设备后4小时内操作</span>\n<span class=\"sd\">                    ValueError: 视频或图片参数都为空或都不为空时</span>\n<span class=\"sd\">                    根据error_handler处理错误</span>\n<span class=\"sd\">                &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">image</span><span class=\"p\">:</span>\n            <span class=\"n\">image</span> <span class=\"o\">=</span> <span class=\"n\">Path</span><span\n        class=\"p\">(</span><span class=\"n\">os</span><span class=\"o\">.</span><span class=\"n\">path</span><span\n        class=\"o\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">Path</span><span class=\"p\">(</span><span\n        class=\"vm\">__file__</span><span class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">resolve</span><span\n        class=\"p\">()</span><span class=\"o\">.</span><span class=\"n\">parent</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;fallback.png&quot;</span><span class=\"p\">))</span>\n        <span class=\"c1\"># get video base64 and duration</span>\n        <span class=\"k\">if</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span class=\"n\">video</span><span\n        class=\"p\">,</span> <span class=\"nb\">str</span><span class=\"p\">):</span>\n            <span class=\"n\">vid_base64</span> <span class=\"o\">=</span> <span class=\"n\">video</span>\n            <span class=\"n\">video</span> <span class=\"o\">=</span> <span class=\"n\">base64</span><span\n        class=\"o\">.</span><span class=\"n\">b64decode</span><span class=\"p\">(</span><span class=\"n\">video</span><span\n        class=\"p\">)</span>\n            <span class=\"n\">file_len</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span\n        class=\"n\">video</span><span class=\"p\">)</span>\n            <span class=\"n\">media_info</span> <span class=\"o\">=</span> <span class=\"n\">MediaInfo</span><span\n        class=\"o\">.</span><span class=\"n\">parse</span><span class=\"p\">(</span><span class=\"n\">BytesIO</span><span\n        class=\"p\">(</span><span class=\"n\">video</span><span class=\"p\">))</span>\n        <span class=\"k\">elif</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span\n        class=\"n\">video</span><span class=\"p\">,</span> <span class=\"nb\">bytes</span><span class=\"p\">):</span>\n            <span class=\"n\">vid_base64</span> <span class=\"o\">=</span> <span class=\"n\">base64</span><span\n        class=\"o\">.</span><span class=\"n\">b64encode</span><span class=\"p\">(</span><span class=\"n\">video</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">decode</span><span class=\"p\">()</span>\n            <span class=\"n\">file_len</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span class=\"p\">(</span><span\n        class=\"n\">video</span><span class=\"p\">)</span>\n            <span class=\"n\">media_info</span> <span class=\"o\">=</span> <span class=\"n\">MediaInfo</span><span\n        class=\"o\">.</span><span class=\"n\">parse</span><span class=\"p\">(</span><span class=\"n\">BytesIO</span><span\n        class=\"p\">(</span><span class=\"n\">video</span><span class=\"p\">))</span>\n        <span class=\"k\">elif</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span\n        class=\"n\">video</span><span class=\"p\">,</span> <span class=\"n\">os</span><span class=\"o\">.</span><span class=\"n\">PathLike</span><span\n        class=\"p\">):</span>\n            <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">video</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;rb&quot;</span><span class=\"p\">)</span> <span\n        class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n                <span class=\"n\">file_len</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span\n        class=\"p\">(</span><span class=\"n\">f</span><span class=\"o\">.</span><span class=\"n\">read</span><span\n        class=\"p\">())</span>\n                <span class=\"n\">vid_base64</span> <span class=\"o\">=</span> <span class=\"n\">base64</span><span class=\"o\">.</span><span\n        class=\"n\">b64encode</span><span class=\"p\">(</span><span class=\"n\">f</span><span class=\"o\">.</span><span\n        class=\"n\">read</span><span class=\"p\">())</span><span class=\"o\">.</span><span class=\"n\">decode</span><span\n        class=\"p\">()</span>\n            <span class=\"n\">media_info</span> <span class=\"o\">=</span> <span class=\"n\">MediaInfo</span><span\n        class=\"o\">.</span><span class=\"n\">parse</span><span class=\"p\">(</span><span class=\"n\">video</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">else</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"ne\">ValueError</span><span class=\"p\">(</span><span class=\"s2\">&quot;video should be str, bytes, or path&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"n\">duration</span> <span class=\"o\">=</span> <span class=\"n\">media_info</span><span\n        class=\"o\">.</span><span class=\"n\">tracks</span><span class=\"p\">[</span><span class=\"mi\">0</span><span class=\"p\">]</span><span\n        class=\"o\">.</span><span class=\"n\">duration</span>\n\n        <span class=\"c1\"># get image base64</span>\n        <span class=\"k\">if</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span class=\"n\">image</span><span\n        class=\"p\">,</span> <span class=\"nb\">str</span><span class=\"p\">):</span>\n            <span class=\"n\">image_base64</span> <span class=\"o\">=</span> <span class=\"n\">image</span>\n        <span class=\"k\">elif</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span\n        class=\"n\">image</span><span class=\"p\">,</span> <span class=\"nb\">bytes</span><span class=\"p\">):</span>\n            <span class=\"n\">image_base64</span> <span class=\"o\">=</span> <span class=\"n\">base64</span><span\n        class=\"o\">.</span><span class=\"n\">b64encode</span><span class=\"p\">(</span><span class=\"n\">image</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">decode</span><span class=\"p\">()</span>\n        <span class=\"k\">elif</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span\n        class=\"n\">image</span><span class=\"p\">,</span> <span class=\"n\">os</span><span class=\"o\">.</span><span class=\"n\">PathLike</span><span\n        class=\"p\">):</span>\n            <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">image</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;rb&quot;</span><span class=\"p\">)</span> <span\n        class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n                <span class=\"n\">image_base64</span> <span class=\"o\">=</span> <span class=\"n\">base64</span><span\n        class=\"o\">.</span><span class=\"n\">b64encode</span><span class=\"p\">(</span><span class=\"n\">f</span><span\n        class=\"o\">.</span><span class=\"n\">read</span><span class=\"p\">())</span><span class=\"o\">.</span><span class=\"n\">decode</span><span\n        class=\"p\">()</span>\n        <span class=\"k\">else</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"ne\">ValueError</span><span class=\"p\">(</span><span class=\"s2\">&quot;image should be str, bytes, or path&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"c1\"># 打印预估时间，300KB/s</span>\n        <span class=\"n\">predict_time</span> <span class=\"o\">=</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span\n        class=\"n\">file_len</span> <span class=\"o\">/</span> <span class=\"mi\">1024</span> <span class=\"o\">/</span> <span\n        class=\"mi\">300</span><span class=\"p\">)</span>\n        <span class=\"n\">logger</span><span class=\"o\">.</span><span class=\"n\">info</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;开始发送视频: 对方wxid:</span><span class=\"si\">{}</span><span class=\"s2\"> 视频base64略 图片base64略 预计耗时:</span><span\n        class=\"si\">{}</span><span class=\"s2\">秒&quot;</span><span class=\"p\">,</span> <span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"n\">predict_time</span><span class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;ToWxid&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Base64&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">vid_base64</span><span class=\"p\">,</span> <span class=\"s2\">&quot;ImageBase64&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">image_base64</span><span class=\"p\">,</span>\n                          <span class=\"s2\">&quot;PlayLength&quot;</span><span class=\"p\">:</span> <span class=\"n\">duration</span><span\n        class=\"p\">}</span>\n            <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span class=\"s1\">/SendVideoMsg&#39;</span><span\n        class=\"p\">,</span> <span class=\"n\">json</span><span class=\"o\">=</span><span class=\"n\">json_param</span><span\n        class=\"p\">)</span> <span class=\"k\">as</span> <span class=\"n\">resp</span><span class=\"p\">:</span>\n                <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">resp</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n        <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n            <span class=\"n\">json_param</span><span class=\"o\">.</span><span class=\"n\">pop</span><span\n        class=\"p\">(</span><span class=\"s1\">&#39;Base64&#39;</span><span class=\"p\">)</span>\n            <span class=\"n\">json_param</span><span class=\"o\">.</span><span class=\"n\">pop</span><span\n        class=\"p\">(</span><span class=\"s1\">&#39;ImageBase64&#39;</span><span class=\"p\">)</span>\n            <span class=\"n\">logger</span><span class=\"o\">.</span><span class=\"n\">info</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;发送视频成功: 对方wxid:</span><span class=\"si\">{}</span><span\n        class=\"s2\"> 时长:</span><span class=\"si\">{}</span><span class=\"s2\"> 视频base64略 图片base64略&quot;</span><span\n        class=\"p\">,</span> <span class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"n\">duration</span><span\n        class=\"p\">)</span>\n            <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span>\n            <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;clientMsgId&quot;</span><span\n        class=\"p\">),</span> <span class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;newMsgId&quot;</span><span class=\"p\">)</span>\n        <span class=\"k\">else</span><span class=\"p\">:</span>\n            <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span\n        class=\"p\">(</span><span class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"MessageMixin.send_voice_message\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.message.MessageMixin.send_voice_message\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">send_voice_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">voice</span><span class=\"p\">:</span> <span class=\"n\">Union</span><span class=\"p\">[</span><span\n        class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"nb\">bytes</span><span class=\"p\">,</span> <span\n        class=\"n\">os</span><span class=\"o\">.</span><span class=\"n\">PathLike</span><span class=\"p\">],</span> <span\n        class=\"nb\">format</span><span class=\"p\">:</span> <span class=\"nb\">str</span> <span class=\"o\">=</span> <span\n        class=\"s2\">&quot;amr&quot;</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> \\\n            <span class=\"nb\">tuple</span><span class=\"p\">[</span><span class=\"nb\">int</span><span\n        class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span\n        class=\"p\">]:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;发送语音消息。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid (str): 接收人wxid</span>\n<span class=\"sd\">            voice (str, bytes, os.PathLike): 语音 接受base64字符串，字节，文件路径</span>\n<span class=\"sd\">            format (str, optional): 语音格式，支持amr/wav/mp3. Defaults to &quot;amr&quot;.</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            tuple[int, int, int]: 返回(ClientMsgid, CreateTime, NewMsgId)</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            BanProtection: 登录新设备后4小时内操作</span>\n<span class=\"sd\">            ValueError: voice_path和voice_base64都为空或都不为空时，或format不支持时</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"k\">await</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_queue_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_send_voice_message</span><span class=\"p\">,</span> <span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"n\">voice</span><span class=\"p\">,</span> <span class=\"nb\">format</span><span\n        class=\"p\">)</span></div>\n\n\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">_send_voice_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">,</span> <span class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"n\">voice</span><span class=\"p\">:</span> <span class=\"n\">Union</span><span\n        class=\"p\">[</span><span class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"nb\">bytes</span><span\n        class=\"p\">,</span> <span class=\"n\">os</span><span class=\"o\">.</span><span class=\"n\">PathLike</span><span\n        class=\"p\">],</span> <span class=\"nb\">format</span><span class=\"p\">:</span> <span class=\"nb\">str</span> <span\n        class=\"o\">=</span> <span class=\"s2\">&quot;amr&quot;</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> \\\n            <span class=\"nb\">tuple</span><span class=\"p\">[</span><span class=\"nb\">int</span><span\n        class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span\n        class=\"p\">]:</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span class=\"ow\">and</span> <span\n        class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span class=\"p\">(</span><span\n        class=\"mi\">14400</span><span class=\"p\">):</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;风控保护: 新设备登录后4小时内请挂机&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"nb\">format</span> <span class=\"ow\">not</span> <span\n        class=\"ow\">in</span> <span class=\"p\">[</span><span class=\"s2\">&quot;amr&quot;</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;wav&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;mp3&quot;</span><span\n        class=\"p\">]:</span>\n            <span class=\"k\">raise</span> <span class=\"ne\">ValueError</span><span class=\"p\">(</span><span class=\"s2\">&quot;format must be one of amr, wav, mp3&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"c1\"># read voice to byte</span>\n        <span class=\"k\">if</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span class=\"n\">voice</span><span\n        class=\"p\">,</span> <span class=\"nb\">str</span><span class=\"p\">):</span>\n            <span class=\"n\">voice_byte</span> <span class=\"o\">=</span> <span class=\"n\">base64</span><span\n        class=\"o\">.</span><span class=\"n\">b64decode</span><span class=\"p\">(</span><span class=\"n\">voice</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span\n        class=\"n\">voice</span><span class=\"p\">,</span> <span class=\"nb\">bytes</span><span class=\"p\">):</span>\n            <span class=\"n\">voice_byte</span> <span class=\"o\">=</span> <span class=\"n\">voice</span>\n        <span class=\"k\">elif</span> <span class=\"nb\">isinstance</span><span class=\"p\">(</span><span\n        class=\"n\">voice</span><span class=\"p\">,</span> <span class=\"n\">os</span><span class=\"o\">.</span><span class=\"n\">PathLike</span><span\n        class=\"p\">):</span>\n            <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">voice</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;rb&quot;</span><span class=\"p\">)</span> <span\n        class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n                <span class=\"n\">voice_byte</span> <span class=\"o\">=</span> <span class=\"n\">f</span><span\n        class=\"o\">.</span><span class=\"n\">read</span><span class=\"p\">()</span>\n        <span class=\"k\">else</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"ne\">ValueError</span><span class=\"p\">(</span><span class=\"s2\">&quot;voice should be str, bytes, or path&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"c1\"># get voice duration and b64</span>\n        <span class=\"k\">if</span> <span class=\"nb\">format</span><span class=\"o\">.</span><span\n        class=\"n\">lower</span><span class=\"p\">()</span> <span class=\"o\">==</span> <span\n        class=\"s2\">&quot;amr&quot;</span><span class=\"p\">:</span>\n            <span class=\"n\">audio</span> <span class=\"o\">=</span> <span class=\"n\">AudioSegment</span><span\n        class=\"o\">.</span><span class=\"n\">from_file</span><span class=\"p\">(</span><span class=\"n\">BytesIO</span><span\n        class=\"p\">(</span><span class=\"n\">voice_byte</span><span class=\"p\">),</span> <span class=\"nb\">format</span><span\n        class=\"o\">=</span><span class=\"s2\">&quot;amr&quot;</span><span class=\"p\">)</span>\n            <span class=\"n\">voice_base64</span> <span class=\"o\">=</span> <span class=\"n\">base64</span><span\n        class=\"o\">.</span><span class=\"n\">b64encode</span><span class=\"p\">(</span><span class=\"n\">voice_byte</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">decode</span><span class=\"p\">()</span>\n        <span class=\"k\">elif</span> <span class=\"nb\">format</span><span class=\"o\">.</span><span\n        class=\"n\">lower</span><span class=\"p\">()</span> <span class=\"o\">==</span> <span\n        class=\"s2\">&quot;wav&quot;</span><span class=\"p\">:</span>\n            <span class=\"n\">audio</span> <span class=\"o\">=</span> <span class=\"n\">AudioSegment</span><span\n        class=\"o\">.</span><span class=\"n\">from_file</span><span class=\"p\">(</span><span class=\"n\">BytesIO</span><span\n        class=\"p\">(</span><span class=\"n\">voice_byte</span><span class=\"p\">),</span> <span class=\"nb\">format</span><span\n        class=\"o\">=</span><span class=\"s2\">&quot;wav&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">set_channels</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n            <span class=\"n\">audio</span> <span class=\"o\">=</span> <span class=\"n\">audio</span><span\n        class=\"o\">.</span><span class=\"n\">set_frame_rate</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_get_closest_frame_rate</span><span class=\"p\">(</span><span\n        class=\"n\">audio</span><span class=\"o\">.</span><span class=\"n\">frame_rate</span><span class=\"p\">))</span>\n            <span class=\"n\">voice_base64</span> <span class=\"o\">=</span> <span class=\"n\">base64</span><span\n        class=\"o\">.</span><span class=\"n\">b64encode</span><span class=\"p\">(</span>\n                <span class=\"k\">await</span> <span class=\"n\">pysilk</span><span class=\"o\">.</span><span class=\"n\">async_encode</span><span\n        class=\"p\">(</span><span class=\"n\">audio</span><span class=\"o\">.</span><span class=\"n\">raw_data</span><span\n        class=\"p\">,</span> <span class=\"n\">sample_rate</span><span class=\"o\">=</span><span class=\"n\">audio</span><span\n        class=\"o\">.</span><span class=\"n\">frame_rate</span><span class=\"p\">))</span><span class=\"o\">.</span><span\n        class=\"n\">decode</span><span class=\"p\">()</span>\n        <span class=\"k\">elif</span> <span class=\"nb\">format</span><span class=\"o\">.</span><span\n        class=\"n\">lower</span><span class=\"p\">()</span> <span class=\"o\">==</span> <span\n        class=\"s2\">&quot;mp3&quot;</span><span class=\"p\">:</span>\n            <span class=\"n\">audio</span> <span class=\"o\">=</span> <span class=\"n\">AudioSegment</span><span\n        class=\"o\">.</span><span class=\"n\">from_file</span><span class=\"p\">(</span><span class=\"n\">BytesIO</span><span\n        class=\"p\">(</span><span class=\"n\">voice_byte</span><span class=\"p\">),</span> <span class=\"nb\">format</span><span\n        class=\"o\">=</span><span class=\"s2\">&quot;mp3&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">set_channels</span><span class=\"p\">(</span><span class=\"mi\">1</span><span class=\"p\">)</span>\n            <span class=\"n\">audio</span> <span class=\"o\">=</span> <span class=\"n\">audio</span><span\n        class=\"o\">.</span><span class=\"n\">set_frame_rate</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_get_closest_frame_rate</span><span class=\"p\">(</span><span\n        class=\"n\">audio</span><span class=\"o\">.</span><span class=\"n\">frame_rate</span><span class=\"p\">))</span>\n            <span class=\"n\">voice_base64</span> <span class=\"o\">=</span> <span class=\"n\">base64</span><span\n        class=\"o\">.</span><span class=\"n\">b64encode</span><span class=\"p\">(</span>\n                <span class=\"k\">await</span> <span class=\"n\">pysilk</span><span class=\"o\">.</span><span class=\"n\">async_encode</span><span\n        class=\"p\">(</span><span class=\"n\">audio</span><span class=\"o\">.</span><span class=\"n\">raw_data</span><span\n        class=\"p\">,</span> <span class=\"n\">sample_rate</span><span class=\"o\">=</span><span class=\"n\">audio</span><span\n        class=\"o\">.</span><span class=\"n\">frame_rate</span><span class=\"p\">))</span><span class=\"o\">.</span><span\n        class=\"n\">decode</span><span class=\"p\">()</span>\n        <span class=\"k\">else</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"ne\">ValueError</span><span class=\"p\">(</span><span class=\"s2\">&quot;format must be one of amr, wav, mp3&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"n\">duration</span> <span class=\"o\">=</span> <span class=\"nb\">len</span><span\n        class=\"p\">(</span><span class=\"n\">audio</span><span class=\"p\">)</span>\n\n        <span class=\"n\">format_dict</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;amr&quot;</span><span\n        class=\"p\">:</span> <span class=\"mi\">0</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;wav&quot;</span><span class=\"p\">:</span> <span class=\"mi\">4</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;mp3&quot;</span><span class=\"p\">:</span> <span\n        class=\"mi\">4</span><span class=\"p\">}</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;ToWxid&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Base64&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">voice_base64</span><span class=\"p\">,</span> <span class=\"s2\">&quot;VoiceTime&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">duration</span><span class=\"p\">,</span>\n                          <span class=\"s2\">&quot;Type&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">format_dict</span><span class=\"p\">[</span><span class=\"nb\">format</span><span class=\"p\">]}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span class=\"s1\">/SendVoiceMsg&#39;</span><span\n        class=\"p\">,</span> <span class=\"n\">json</span><span class=\"o\">=</span><span class=\"n\">json_param</span><span\n        class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"n\">json_param</span><span class=\"o\">.</span><span class=\"n\">pop</span><span\n        class=\"p\">(</span><span class=\"s1\">&#39;Base64&#39;</span><span class=\"p\">)</span>\n                <span class=\"n\">logger</span><span class=\"o\">.</span><span class=\"n\">info</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;发送语音消息: 对方wxid:</span><span class=\"si\">{}</span><span class=\"s2\"> 时长:</span><span\n        class=\"si\">{}</span><span class=\"s2\"> 格式:</span><span class=\"si\">{}</span><span\n        class=\"s2\"> 音频base64略&quot;</span><span class=\"p\">,</span> <span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"n\">duration</span><span class=\"p\">,</span> <span class=\"nb\">format</span><span\n        class=\"p\">)</span>\n                <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span>\n                <span class=\"k\">return</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span\n        class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;ClientMsgId&quot;</span><span\n        class=\"p\">)),</span> <span class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;CreateTime&quot;</span><span class=\"p\">),</span> <span\n        class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;NewMsgId&quot;</span><span\n        class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span>\n\n    <span class=\"nd\">@staticmethod</span>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">_get_closest_frame_rate</span><span\n        class=\"p\">(</span><span class=\"n\">frame_rate</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">int</span><span class=\"p\">:</span>\n        <span class=\"n\">supported</span> <span class=\"o\">=</span> <span class=\"p\">[</span><span\n        class=\"mi\">8000</span><span class=\"p\">,</span> <span class=\"mi\">12000</span><span class=\"p\">,</span> <span\n        class=\"mi\">16000</span><span class=\"p\">,</span> <span class=\"mi\">24000</span><span class=\"p\">]</span>\n        <span class=\"n\">closest_rate</span> <span class=\"o\">=</span> <span class=\"kc\">None</span>\n        <span class=\"n\">smallest_diff</span> <span class=\"o\">=</span> <span class=\"nb\">float</span><span\n        class=\"p\">(</span><span class=\"s1\">&#39;inf&#39;</span><span class=\"p\">)</span>\n        <span class=\"k\">for</span> <span class=\"n\">num</span> <span class=\"ow\">in</span> <span\n        class=\"n\">supported</span><span class=\"p\">:</span>\n            <span class=\"n\">diff</span> <span class=\"o\">=</span> <span class=\"nb\">abs</span><span\n        class=\"p\">(</span><span class=\"n\">frame_rate</span> <span class=\"o\">-</span> <span class=\"n\">num</span><span\n        class=\"p\">)</span>\n            <span class=\"k\">if</span> <span class=\"n\">diff</span> <span class=\"o\">&lt;</span> <span class=\"n\">smallest_diff</span><span\n        class=\"p\">:</span>\n                <span class=\"n\">smallest_diff</span> <span class=\"o\">=</span> <span class=\"n\">diff</span>\n                <span class=\"n\">closest_rate</span> <span class=\"o\">=</span> <span class=\"n\">num</span>\n\n        <span class=\"k\">return</span> <span class=\"n\">closest_rate</span>\n\n<div class=\"viewcode-block\" id=\"MessageMixin.send_link_message\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.message.MessageMixin.send_link_message\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">send_link_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">url</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">title</span><span class=\"p\">:</span> <span class=\"nb\">str</span> <span class=\"o\">=</span> <span\n        class=\"s2\">&quot;&quot;</span><span class=\"p\">,</span> <span class=\"n\">description</span><span\n        class=\"p\">:</span> <span class=\"nb\">str</span> <span class=\"o\">=</span> <span\n        class=\"s2\">&quot;&quot;</span><span class=\"p\">,</span>\n                                <span class=\"n\">thumb_url</span><span class=\"p\">:</span> <span\n        class=\"nb\">str</span> <span class=\"o\">=</span> <span class=\"s2\">&quot;&quot;</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span><span\n        class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span\n        class=\"nb\">int</span><span class=\"p\">]:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;发送链接消息。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid (str): 接收人wxid</span>\n<span class=\"sd\">            url (str): 跳转链接</span>\n<span class=\"sd\">            title (str, optional): 标题. Defaults to &quot;&quot;.</span>\n<span class=\"sd\">            description (str, optional): 描述. Defaults to &quot;&quot;.</span>\n<span class=\"sd\">            thumb_url (str, optional): 缩略图链接. Defaults to &quot;&quot;.</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            tuple[str, int, int]: 返回(ClientMsgid, CreateTime, NewMsgId)</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            BanProtection: 登录新设备后4小时内操作</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"k\">await</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_queue_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_send_link_message</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"n\">url</span><span class=\"p\">,</span> <span\n        class=\"n\">title</span><span class=\"p\">,</span> <span class=\"n\">description</span><span class=\"p\">,</span> <span\n        class=\"n\">thumb_url</span><span class=\"p\">)</span></div>\n\n\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">_send_link_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">url</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">title</span><span class=\"p\">:</span> <span class=\"nb\">str</span> <span class=\"o\">=</span> <span\n        class=\"s2\">&quot;&quot;</span><span class=\"p\">,</span> <span class=\"n\">description</span><span\n        class=\"p\">:</span> <span class=\"nb\">str</span> <span class=\"o\">=</span> <span\n        class=\"s2\">&quot;&quot;</span><span class=\"p\">,</span>\n                                 <span class=\"n\">thumb_url</span><span class=\"p\">:</span> <span\n        class=\"nb\">str</span> <span class=\"o\">=</span> <span class=\"s2\">&quot;&quot;</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span><span\n        class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span\n        class=\"nb\">int</span><span class=\"p\">]:</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span class=\"ow\">and</span> <span\n        class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span class=\"p\">(</span><span\n        class=\"mi\">14400</span><span class=\"p\">):</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;风控保护: 新设备登录后4小时内请挂机&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;ToWxid&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Url&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">url</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;Title&quot;</span><span class=\"p\">:</span> <span class=\"n\">title</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;Desc&quot;</span><span class=\"p\">:</span> <span class=\"n\">description</span><span\n        class=\"p\">,</span>\n                          <span class=\"s2\">&quot;ThumbUrl&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">thumb_url</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/SendShareLink&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"n\">logger</span><span class=\"o\">.</span><span class=\"n\">info</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;发送链接消息: 对方wxid:</span><span class=\"si\">{}</span><span class=\"s2\"> 链接:</span><span\n        class=\"si\">{}</span><span class=\"s2\"> 标题:</span><span class=\"si\">{}</span><span class=\"s2\"> 描述:</span><span\n        class=\"si\">{}</span><span class=\"s2\"> 缩略图链接:</span><span class=\"si\">{}</span><span class=\"s2\">&quot;</span><span\n        class=\"p\">,</span>\n                            <span class=\"n\">wxid</span><span class=\"p\">,</span>\n                            <span class=\"n\">url</span><span class=\"p\">,</span>\n                            <span class=\"n\">title</span><span class=\"p\">,</span>\n                            <span class=\"n\">description</span><span class=\"p\">,</span>\n                            <span class=\"n\">thumb_url</span><span class=\"p\">)</span>\n                <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span>\n                <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;clientMsgId&quot;</span><span\n        class=\"p\">),</span> <span class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;createTime&quot;</span><span class=\"p\">),</span> <span\n        class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;newMsgId&quot;</span><span\n        class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span>\n\n<div class=\"viewcode-block\" id=\"MessageMixin.send_emoji_message\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.message.MessageMixin.send_emoji_message\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">send_emoji_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">md5</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">total_length</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">)</span> <span\n        class=\"o\">-&gt;</span> <span class=\"nb\">list</span><span class=\"p\">[</span><span class=\"nb\">dict</span><span\n        class=\"p\">]:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;发送表情消息。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid (str): 接收人wxid</span>\n<span class=\"sd\">            md5 (str): 表情md5值</span>\n<span class=\"sd\">            total_length (int): 表情总长度</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            list[dict]: 返回表情项列表(list of emojiItem)</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            BanProtection: 登录新设备后4小时内操作</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"k\">await</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_queue_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_send_emoji_message</span><span class=\"p\">,</span> <span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"n\">md5</span><span class=\"p\">,</span> <span class=\"n\">total_length</span><span\n        class=\"p\">)</span></div>\n\n\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">_send_emoji_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">,</span> <span class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"n\">md5</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"n\">total_length</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span><span\n        class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span\n        class=\"nb\">int</span><span class=\"p\">]:</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span class=\"ow\">and</span> <span\n        class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span class=\"p\">(</span><span\n        class=\"mi\">14400</span><span class=\"p\">):</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;风控保护: 新设备登录后4小时内请挂机&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;ToWxid&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Md5&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">md5</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;TotalLen&quot;</span><span class=\"p\">:</span> <span class=\"n\">total_length</span><span\n        class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span class=\"s1\">/SendEmojiMsg&#39;</span><span\n        class=\"p\">,</span> <span class=\"n\">json</span><span class=\"o\">=</span><span class=\"n\">json_param</span><span\n        class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"n\">logger</span><span class=\"o\">.</span><span class=\"n\">info</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;发送表情消息: 对方wxid:</span><span class=\"si\">{}</span><span class=\"s2\"> md5:</span><span\n        class=\"si\">{}</span><span class=\"s2\"> 总长度:</span><span class=\"si\">{}</span><span\n        class=\"s2\">&quot;</span><span class=\"p\">,</span> <span class=\"n\">wxid</span><span class=\"p\">,</span> <span\n        class=\"n\">md5</span><span class=\"p\">,</span> <span class=\"n\">total_length</span><span class=\"p\">)</span>\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;emojiItem&quot;</span><span\n        class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span>\n\n<div class=\"viewcode-block\" id=\"MessageMixin.send_card_message\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.message.MessageMixin.send_card_message\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">send_card_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">card_wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">card_nickname</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">card_alias</span><span class=\"p\">:</span> <span class=\"nb\">str</span> <span class=\"o\">=</span> <span\n        class=\"s2\">&quot;&quot;</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span\n        class=\"nb\">tuple</span><span class=\"p\">[</span>\n        <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span\n        class=\"nb\">int</span><span class=\"p\">]:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;发送名片消息。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid (str): 接收人wxid</span>\n<span class=\"sd\">            card_wxid (str): 名片用户的wxid</span>\n<span class=\"sd\">            card_nickname (str): 名片用户的昵称</span>\n<span class=\"sd\">            card_alias (str, optional): 名片用户的备注. Defaults to &quot;&quot;.</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            tuple[int, int, int]: 返回(ClientMsgid, CreateTime, NewMsgId)</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            BanProtection: 登录新设备后4小时内操作</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"k\">await</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_queue_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_send_card_message</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"n\">card_wxid</span><span class=\"p\">,</span> <span\n        class=\"n\">card_nickname</span><span class=\"p\">,</span> <span class=\"n\">card_alias</span><span class=\"p\">)</span></div>\n\n\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">_send_card_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">card_wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">card_nickname</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">card_alias</span><span class=\"p\">:</span> <span class=\"nb\">str</span> <span class=\"o\">=</span> <span\n        class=\"s2\">&quot;&quot;</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span\n        class=\"nb\">tuple</span><span class=\"p\">[</span>\n        <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span\n        class=\"nb\">int</span><span class=\"p\">]:</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span class=\"ow\">and</span> <span\n        class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span class=\"p\">(</span><span\n        class=\"mi\">14400</span><span class=\"p\">):</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;风控保护: 新设备登录后4小时内请挂机&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;ToWxid&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"s2\">&quot;CardWxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">card_wxid</span><span class=\"p\">,</span> <span class=\"s2\">&quot;CardAlias&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">card_alias</span><span class=\"p\">,</span>\n                          <span class=\"s2\">&quot;CardNickname&quot;</span><span class=\"p\">:</span> <span class=\"n\">card_nickname</span><span\n        class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/SendCardMsg&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"n\">logger</span><span class=\"o\">.</span><span class=\"n\">info</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;发送名片消息: 对方wxid:</span><span class=\"si\">{}</span><span class=\"s2\"> 名片wxid:</span><span\n        class=\"si\">{}</span><span class=\"s2\"> 名片备注:</span><span class=\"si\">{}</span><span\n        class=\"s2\"> 名片昵称:</span><span class=\"si\">{}</span><span class=\"s2\">&quot;</span><span\n        class=\"p\">,</span> <span class=\"n\">wxid</span><span class=\"p\">,</span>\n                            <span class=\"n\">card_wxid</span><span class=\"p\">,</span>\n                            <span class=\"n\">card_alias</span><span class=\"p\">,</span>\n                            <span class=\"n\">card_nickname</span><span class=\"p\">)</span>\n                <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span>\n                <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;List&quot;</span><span\n        class=\"p\">)[</span><span class=\"mi\">0</span><span class=\"p\">]</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;ClientMsgid&quot;</span><span\n        class=\"p\">),</span> <span class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;List&quot;</span><span class=\"p\">)[</span><span\n        class=\"mi\">0</span><span class=\"p\">]</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Createtime&quot;</span><span class=\"p\">),</span> <span\n        class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;List&quot;</span><span\n        class=\"p\">)[</span>\n                    <span class=\"mi\">0</span><span class=\"p\">]</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;NewMsgId&quot;</span><span class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span>\n\n<div class=\"viewcode-block\" id=\"MessageMixin.send_app_message\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.message.MessageMixin.send_app_message\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">send_app_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">,</span> <span class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"n\">xml</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"nb\">type</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span><span\n        class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span\n        class=\"nb\">int</span><span class=\"p\">]:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;发送应用消息。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid (str): 接收人wxid</span>\n<span class=\"sd\">            xml (str): 应用消息的xml内容</span>\n<span class=\"sd\">            type (int): 应用消息类型</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            tuple[str, int, int]: 返回(ClientMsgid, CreateTime, NewMsgId)</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            BanProtection: 登录新设备后4小时内操作</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"k\">await</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_queue_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_send_app_message</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"n\">xml</span><span class=\"p\">,</span> <span\n        class=\"nb\">type</span><span class=\"p\">)</span></div>\n\n\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">_send_app_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">xml</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"nb\">type</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">)</span> <span\n        class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span><span class=\"nb\">int</span><span\n        class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span\n        class=\"p\">]:</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span class=\"ow\">and</span> <span\n        class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span class=\"p\">(</span><span\n        class=\"mi\">14400</span><span class=\"p\">):</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;风控保护: 新设备登录后4小时内请挂机&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;ToWxid&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Xml&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">xml</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;Type&quot;</span><span class=\"p\">:</span> <span class=\"nb\">type</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/SendAppMsg&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"n\">json_param</span><span class=\"p\">[</span><span class=\"s2\">&quot;Xml&quot;</span><span\n        class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"n\">json_param</span><span class=\"p\">[</span><span\n        class=\"s2\">&quot;Xml&quot;</span><span class=\"p\">]</span><span class=\"o\">.</span><span\n        class=\"n\">replace</span><span class=\"p\">(</span><span class=\"s2\">&quot;</span><span class=\"se\">\\n</span><span\n        class=\"s2\">&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;&quot;</span><span class=\"p\">)</span>\n                <span class=\"n\">logger</span><span class=\"o\">.</span><span class=\"n\">info</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;发送app消息: 对方wxid:</span><span class=\"si\">{}</span><span class=\"s2\"> 类型:</span><span\n        class=\"si\">{}</span><span class=\"s2\"> xml:</span><span class=\"si\">{}</span><span class=\"s2\">&quot;</span><span\n        class=\"p\">,</span> <span class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"nb\">type</span><span\n        class=\"p\">,</span> <span class=\"n\">json_param</span><span class=\"p\">[</span><span\n        class=\"s2\">&quot;Xml&quot;</span><span class=\"p\">])</span>\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;clientMsgId&quot;</span><span\n        class=\"p\">),</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span>\n                    <span class=\"s2\">&quot;createTime&quot;</span><span class=\"p\">),</span> <span\n        class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;newMsgId&quot;</span><span class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span>\n\n<div class=\"viewcode-block\" id=\"MessageMixin.send_cdn_file_msg\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">send_cdn_file_msg</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">xml</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span\n        class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span><span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span\n        class=\"p\">]:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;转发文件消息。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid (str): 接收人wxid</span>\n<span class=\"sd\">            xml (str): 要转发的文件消息xml内容</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            tuple[str, int, int]: 返回(ClientMsgid, CreateTime, NewMsgId)</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            BanProtection: 登录新设备后4小时内操作</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"k\">await</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_queue_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_send_cdn_file_msg</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"n\">xml</span><span class=\"p\">)</span></div>\n\n\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">_send_cdn_file_msg</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">xml</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span\n        class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span><span class=\"nb\">int</span><span\n        class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span\n        class=\"p\">]:</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span class=\"ow\">and</span> <span\n        class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span class=\"p\">(</span><span\n        class=\"mi\">14400</span><span class=\"p\">):</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;风控保护: 新设备登录后4小时内请挂机&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;ToWxid&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Content&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">xml</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/SendCDNFileMsg&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"n\">logger</span><span class=\"o\">.</span><span class=\"n\">info</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;转发文件消息: 对方wxid:</span><span class=\"si\">{}</span><span class=\"s2\"> xml:</span><span\n        class=\"si\">{}</span><span class=\"s2\">&quot;</span><span class=\"p\">,</span> <span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"n\">xml</span><span class=\"p\">)</span>\n                <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span>\n                <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;clientMsgId&quot;</span><span\n        class=\"p\">),</span> <span class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;createTime&quot;</span><span class=\"p\">),</span> <span\n        class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;newMsgId&quot;</span><span\n        class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span>\n\n<div class=\"viewcode-block\" id=\"MessageMixin.send_cdn_img_msg\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">send_cdn_img_msg</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">,</span> <span class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"n\">xml</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span><span\n        class=\"nb\">str</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span\n        class=\"nb\">int</span><span class=\"p\">]:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;转发图片消息。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid (str): 接收人wxid</span>\n<span class=\"sd\">            xml (str): 要转发的图片消息xml内容</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            tuple[str, int, int]: 返回(ClientImgId, CreateTime, NewMsgId)</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            BanProtection: 登录新设备后4小时内操作</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"k\">await</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_queue_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_send_cdn_img_msg</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"n\">xml</span><span class=\"p\">)</span></div>\n\n\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">_send_cdn_img_msg</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">xml</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span\n        class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span><span class=\"nb\">int</span><span\n        class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span\n        class=\"p\">]:</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span class=\"ow\">and</span> <span\n        class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span class=\"p\">(</span><span\n        class=\"mi\">14400</span><span class=\"p\">):</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;风控保护: 新设备登录后4小时内请挂机&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;ToWxid&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Content&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">xml</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/SendCDNImgMsg&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"n\">logger</span><span class=\"o\">.</span><span class=\"n\">info</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;转发图片消息: 对方wxid:</span><span class=\"si\">{}</span><span class=\"s2\"> xml:</span><span\n        class=\"si\">{}</span><span class=\"s2\">&quot;</span><span class=\"p\">,</span> <span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"n\">xml</span><span class=\"p\">)</span>\n                <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span>\n                <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;ClientImgId&quot;</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;string&quot;</span><span\n        class=\"p\">),</span> <span class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;CreateTime&quot;</span><span class=\"p\">),</span> <span\n        class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;Newmsgid&quot;</span><span\n        class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span>\n\n<div class=\"viewcode-block\" id=\"MessageMixin.send_cdn_video_msg\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">send_cdn_video_msg</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">,</span> <span\n        class=\"n\">xml</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span class=\"p\">)</span> <span\n        class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span><span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">]:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;转发视频消息。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid (str): 接收人wxid</span>\n<span class=\"sd\">            xml (str): 要转发的视频消息xml内容</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            tuple[str, int]: 返回(ClientMsgid, NewMsgId)</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            BanProtection: 登录新设备后4小时内操作</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"k\">await</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_queue_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">_send_cdn_video_msg</span><span class=\"p\">,</span> <span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"n\">xml</span><span class=\"p\">)</span></div>\n\n\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">_send_cdn_video_msg</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">,</span> <span class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"n\">xml</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">tuple</span><span class=\"p\">[</span><span\n        class=\"nb\">int</span><span class=\"p\">,</span> <span class=\"nb\">int</span><span class=\"p\">]:</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span class=\"ow\">and</span> <span\n        class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span class=\"p\">(</span><span\n        class=\"mi\">14400</span><span class=\"p\">):</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;风控保护: 新设备登录后4小时内请挂机&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;ToWxid&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">wxid</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Content&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">xml</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/SendCDNVideoMsg&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"n\">logger</span><span class=\"o\">.</span><span class=\"n\">info</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;转发视频消息: 对方wxid:</span><span class=\"si\">{}</span><span class=\"s2\"> xml:</span><span\n        class=\"si\">{}</span><span class=\"s2\">&quot;</span><span class=\"p\">,</span> <span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"n\">xml</span><span class=\"p\">)</span>\n                <span class=\"n\">data</span> <span class=\"o\">=</span> <span class=\"n\">json_resp</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span>\n                <span class=\"k\">return</span> <span class=\"n\">data</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;clientMsgId&quot;</span><span\n        class=\"p\">),</span> <span class=\"n\">data</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;newMsgId&quot;</span><span class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span>\n\n<div class=\"viewcode-block\" id=\"MessageMixin.sync_message\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.message.MessageMixin.sync_message\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">sync_message</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">)</span> <span\n        class=\"o\">-&gt;</span> <span class=\"nb\">dict</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;同步消息。</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            dict: 返回同步到的消息数据</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">(</span><span\n        class=\"n\">timeout</span><span class=\"o\">=</span><span class=\"n\">aiohttp</span><span class=\"o\">.</span><span\n        class=\"n\">ClientTimeout</span><span class=\"p\">(</span><span class=\"n\">total</span><span class=\"o\">=</span><span\n        class=\"mi\">10</span><span class=\"p\">))</span> <span class=\"k\">as</span> <span class=\"n\">session</span><span\n        class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Scene&quot;</span><span class=\"p\">:</span> <span\n        class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Synckey&quot;</span><span class=\"p\">:</span> <span\n        class=\"s2\">&quot;&quot;</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span class=\"s1\">/Sync&#39;</span><span\n        class=\"p\">,</span> <span class=\"n\">json</span><span class=\"o\">=</span><span class=\"n\">json_param</span><span\n        class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n</div>\n\n</pre>\n                    </div>\n                </article>\n            </div>\n            <footer>\n\n                <div class=\"related-pages\">\n\n\n                </div>\n                <div class=\"bottom-of-page\">\n                    <div class=\"left-details\">\n                        <div class=\"copyright\">\n                            Copyright &#169; 2025, HenryXiaoYang\n                        </div>\n                        Made with <a href=\"https://www.sphinx-doc.org/\">Sphinx</a> and <a class=\"muted-link\"\n                                                                                          href=\"https://pradyunsg.me\">@pradyunsg</a>'s\n\n                        <a href=\"https://github.com/pradyunsg/furo\">Furo</a>\n\n                    </div>\n                    <div class=\"right-details\">\n\n                    </div>\n                </div>\n\n            </footer>\n        </div>\n        <aside class=\"toc-drawer no-toc\">\n\n\n        </aside>\n    </div>\n</div>\n<script src=\"../../../_static/documentation_options.js?v=91bfbbb6\"></script>\n<script src=\"../../../_static/doctools.js?v=9bcbadda\"></script>\n<script src=\"../../../_static/sphinx_highlight.js?v=dc90522c\"></script>\n<script src=\"../../../_static/scripts/furo.js?v=5fa4622c\"></script>\n<script src=\"../../../_static/translations.js?v=beaddf03\"></script>\n</body>\n</html>"
  },
  {
    "path": "docs/WechatAPIClient/_modules/WechatAPI/Client/protect.html",
    "content": "<!doctype html>\n<html class=\"no-js\" data-content_root=\"../../../\" lang=\"zh-CN\">\n<head>\n    <meta charset=\"utf-8\"/>\n    <meta content=\"width=device-width,initial-scale=1\" name=\"viewport\"/>\n    <meta content=\"light dark\" name=\"color-scheme\">\n    <link href=\"../../../genindex.html\" rel=\"index\" title=\"索引\"/>\n    <link href=\"../../../search.html\" rel=\"search\" title=\"搜索\"/>\n\n    <!-- Generated with Sphinx 8.1.3 and Furo 2024.08.06 -->\n    <title>WechatAPI.Client.protect - XYBotV2</title>\n    <link href=\"../../../_static/pygments.css?v=8f2a1f02\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo.css?v=354aac6f\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo-extensions.css?v=302659d7\" rel=\"stylesheet\" type=\"text/css\"/>\n\n\n    <style>\n        body {\n            --color-code-background: #f8f8f8;\n            --color-code-foreground: black;\n            --color-brand-primary: #2962ff;\n            --color-brand-content: #2962ff;\n\n        }\n\n        @media not print {\n            body[data-theme=\"dark\"] {\n                --color-code-background: #202020;\n                --color-code-foreground: #d0d0d0;\n\n            }\n\n            @media (prefers-color-scheme: dark) {\n                body:not([data-theme=\"light\"]) {\n                    --color-code-background: #202020;\n                    --color-code-foreground: #d0d0d0;\n\n                }\n            }\n        }\n    </style>\n</head>\n<body>\n\n<script>\n    document.body.dataset.theme = localStorage.getItem(\"theme\") || \"auto\";\n</script>\n\n\n<svg style=\"display: none;\" xmlns=\"http://www.w3.org/2000/svg\">\n    <symbol id=\"svg-toc\" viewBox=\"0 0 24 24\">\n        <title>Contents</title>\n        <svg fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 1024 1024\">\n            <path d=\"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-menu\" viewBox=\"0 0 24 24\">\n        <title>Menu</title>\n        <svg class=\"feather-menu\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-arrow-right\" viewBox=\"0 0 24 24\">\n        <title>Expand</title>\n        <svg class=\"feather-chevron-right\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <polyline points=\"9 18 15 12 9 6\"></polyline>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun\" viewBox=\"0 0 24 24\">\n        <title>Light mode</title>\n        <svg class=\"feather-sun\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\n            <line x1=\"12\" x2=\"12\" y1=\"1\" y2=\"3\"></line>\n            <line x1=\"12\" x2=\"12\" y1=\"21\" y2=\"23\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"4.22\" y2=\"5.64\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"18.36\" y2=\"19.78\"></line>\n            <line x1=\"1\" x2=\"3\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"21\" x2=\"23\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"19.78\" y2=\"18.36\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"5.64\" y2=\"4.22\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon\" viewBox=\"0 0 24 24\">\n        <title>Dark mode</title>\n        <svg class=\"icon-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun-with-moon\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in light mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 5.411 14.504 C 5.471 14.504 5.532 14.504 5.591 14.504 C 3.639 16.319 4.383 19.569 6.931 20.352 C 7.693 20.586 8.512 20.551 9.25 20.252 C 8.023 23.207 4.056 23.725 2.11 21.184 C 0.166 18.642 1.702 14.949 4.874 14.536 C 5.051 14.512 5.231 14.5 5.411 14.5 L 5.411 14.504 Z\"\n                  style=\"opacity: 50%\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"3.25\" y2=\"1.25\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"15.85\" y2=\"17.85\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"5.094\" y2=\"3.68\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"14.05\" y2=\"15.464\"/>\n            <line x1=\"8.2\" x2=\"6.2\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"20.8\" x2=\"22.8\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"14.006\" y2=\"15.42\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"5.05\" y2=\"3.636\"/>\n            <circle cx=\"14.5\" cy=\"9.55\" r=\"3.6\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon-with-sun\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in dark mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 8.282 7.007 C 8.385 7.007 8.494 7.007 8.595 7.007 C 5.18 10.184 6.481 15.869 10.942 17.24 C 12.275 17.648 13.706 17.589 15 17.066 C 12.851 22.236 5.91 23.143 2.505 18.696 C -0.897 14.249 1.791 7.786 7.342 7.063 C 7.652 7.021 7.965 7 8.282 7 L 8.282 7.007 Z\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"3.705\" y2=\"2.5\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"11.295\" y2=\"12.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"4.816\" y2=\"3.964\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"10.212\" y2=\"11.063\"/>\n            <line style=\"opacity: 50%\" x1=\"14.205\" x2=\"13.001\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"21.795\" x2=\"23\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"10.184\" y2=\"11.036\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"4.789\" y2=\"3.937\"/>\n            <circle cx=\"18\" cy=\"7.5\" r=\"2.169\" style=\"opacity: 50%\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-pencil\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-pencil-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4\"/>\n            <path d=\"M13.5 6.5l4 4\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-eye\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-eye-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\"/>\n            <path\n                    d=\"M11.11 17.958c-3.209 -.307 -5.91 -2.293 -8.11 -5.958c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6c-.21 .352 -.427 .688 -.647 1.008\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n</svg>\n\n<input class=\"sidebar-toggle\" id=\"__navigation\" name=\"__navigation\" type=\"checkbox\">\n<input class=\"sidebar-toggle\" id=\"__toc\" name=\"__toc\" type=\"checkbox\">\n<label class=\"overlay sidebar-overlay\" for=\"__navigation\">\n    <div class=\"visually-hidden\">Hide navigation sidebar</div>\n</label>\n<label class=\"overlay toc-overlay\" for=\"__toc\">\n    <div class=\"visually-hidden\">Hide table of contents sidebar</div>\n</label>\n\n<a class=\"skip-to-content muted-link\" href=\"#furo-main-content\">Skip to content</a>\n\n\n\n<div class=\"page\">\n    <header class=\"mobile-header\">\n        <div class=\"header-left\">\n            <label class=\"nav-overlay-icon\" for=\"__navigation\">\n                <div class=\"visually-hidden\">Toggle site navigation sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-menu\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n        <div class=\"header-center\">\n            <a href=\"../../../index.html\">\n                <div class=\"brand\">XYBotV2</div>\n            </a>\n        </div>\n        <div class=\"header-right\">\n            <div class=\"theme-toggle-container theme-toggle-header\">\n                <button class=\"theme-toggle\">\n                    <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                    <svg class=\"theme-icon-when-auto-light\">\n                        <use href=\"#svg-sun-with-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-auto-dark\">\n                        <use href=\"#svg-moon-with-sun\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-dark\">\n                        <use href=\"#svg-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-light\">\n                        <use href=\"#svg-sun\"></use>\n                    </svg>\n                </button>\n            </div>\n            <label class=\"toc-overlay-icon toc-header-icon no-toc\" for=\"__toc\">\n                <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-toc\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n    </header>\n    <aside class=\"sidebar-drawer\">\n        <div class=\"sidebar-container\">\n\n            <div class=\"sidebar-sticky\">\n                <div class=\"sidebar-scroll\"><a class=\"sidebar-brand\" href=\"../../../index.html\">\n\n\n                    <span class=\"sidebar-brand-text\">XYBotV2</span>\n\n                </a>\n                    <form action=\"../../../search.html\" class=\"sidebar-search-container\" method=\"get\" role=\"search\">\n                        <input aria-label=\"搜索\" class=\"sidebar-search\" name=\"q\" placeholder=\"搜索\">\n                        <input name=\"check_keywords\" type=\"hidden\" value=\"yes\">\n                        <input name=\"area\" type=\"hidden\" value=\"default\">\n                    </form>\n                    <div id=\"searchbox\"></div>\n                    <div class=\"sidebar-tree\">\n\n                    </div>\n                    <div class=\"sidebar-tree\">\n                        <p class=\"caption\"><span class=\"caption-text\">重要函数导航</span></p>\n                        <ul>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">登录</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.is_running\">检查WechatAPI是否在运行</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_qr_code\">获取登录二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.awaken_login\">二次登录(唤醒登录)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.check_login_uuid\">检查登录的UUID状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_cached_info\">获取登录缓存信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.log_out\">登出当前账号</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.heartbeat\">发送心跳包</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\">开始自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\">停止自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\">获取自动心跳状态</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">消息</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.sync_message\">同步消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_text_message\">发送文本消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_image_message\">发送图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_voice_message\">发送语音消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_video_message\">发送视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_link_message\">发送链接消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_card_message\">发送名片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_app_message\">发送应用(xml)消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_emoji_message\">发送表情消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\">转发图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\">转发视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\">转发文件消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.revoke_message\">撤回消息</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">用户</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_my_qrcode\">获取个人二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_profile\">获取用户信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.is_logged_in\">检查是否登录</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">群聊</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\">获取群聊信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\">获取群聊公告</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\">获取群聊成员列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\">获取群聊二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\">添加群成员(群聊最多40人)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\">邀请群聊成员(群聊大于40人)</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">好友</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contact\">获取联系人信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_detail\">获取联系人详情</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_list\">获取联系人列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_nickname\">获取用户昵称</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.accept_friend\">接受好友请求</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">红包</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\">获取红包详情</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">工具</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.check_database\">检查数据库状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.set_step\">设置步数</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_image\">下载高清图片</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_video\">下载视频</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_voice\">下载语音文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_attach\">下载附件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_byte\">base64转字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_file\">base64转文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.byte_to_base64\">字节转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.file_to_base64\">文件转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\">silk的base64转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\">silk字节转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\">WAV字节转AMR的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\">WAV字节转AMR字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\">WAV字节转silk的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\">WAV字节转silk字节</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                        </ul>\n                    </div>\n                </div>\n            </div>\n\n        </div>\n    </aside>\n    <div class=\"main\">\n        <div class=\"content\">\n            <div class=\"article-container\">\n                <a class=\"back-to-top muted-link\" href=\"#\">\n                    <svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n                        <path d=\"M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z\"></path>\n                    </svg>\n                    <span>Back to top</span>\n                </a>\n                <div class=\"content-icon-container\">\n                    <div class=\"theme-toggle-container theme-toggle-content\">\n                        <button class=\"theme-toggle\">\n                            <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                            <svg class=\"theme-icon-when-auto-light\">\n                                <use href=\"#svg-sun-with-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-auto-dark\">\n                                <use href=\"#svg-moon-with-sun\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-dark\">\n                                <use href=\"#svg-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-light\">\n                                <use href=\"#svg-sun\"></use>\n                            </svg>\n                        </button>\n                    </div>\n                    <label class=\"toc-overlay-icon toc-content-icon no-toc\" for=\"__toc\">\n                        <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                        <i class=\"icon\">\n                            <svg>\n                                <use href=\"#svg-toc\"></use>\n                            </svg>\n                        </i>\n                    </label>\n                </div>\n                <article id=\"furo-main-content\" role=\"main\">\n                    <h1>WechatAPI.Client.protect 源代码</h1>\n                    <div class=\"highlight\"><pre>\n<span></span><span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">json</span>\n<span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">os</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">datetime</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"n\">datetime</span>\n\n\n<div class=\"viewcode-block\" id=\"Singleton\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.protect.Singleton\">[文档]</a>\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">Singleton</span><span class=\"p\">(</span><span\n        class=\"nb\">type</span><span class=\"p\">):</span>\n<span class=\"w\">    </span><span class=\"sd\">&quot;&quot;&quot;单例模式的元类。</span>\n\n<span class=\"sd\">    用于确保一个类只有一个实例。</span>\n\n<span class=\"sd\">    Attributes:</span>\n<span class=\"sd\">        _instances (dict): 存储类的实例的字典</span>\n<span class=\"sd\">    &quot;&quot;&quot;</span>\n    <span class=\"n\">_instances</span> <span class=\"o\">=</span> <span class=\"p\">{}</span>\n\n<div class=\"viewcode-block\" id=\"Singleton.__call__\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.protect.Singleton.__call__\">[文档]</a>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"fm\">__call__</span><span class=\"p\">(</span><span\n        class=\"bp\">cls</span><span class=\"p\">,</span> <span class=\"o\">*</span><span class=\"n\">args</span><span\n        class=\"p\">,</span> <span class=\"o\">**</span><span class=\"n\">kwargs</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;创建或返回类的单例实例。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            *args: 位置参数</span>\n<span class=\"sd\">            **kwargs: 关键字参数</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            object: 类的单例实例</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"bp\">cls</span> <span class=\"ow\">not</span> <span\n        class=\"ow\">in</span> <span class=\"bp\">cls</span><span class=\"o\">.</span><span class=\"n\">_instances</span><span\n        class=\"p\">:</span>\n            <span class=\"bp\">cls</span><span class=\"o\">.</span><span class=\"n\">_instances</span><span class=\"p\">[</span><span\n        class=\"bp\">cls</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span class=\"nb\">super</span><span\n        class=\"p\">(</span><span class=\"n\">Singleton</span><span class=\"p\">,</span> <span class=\"bp\">cls</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"fm\">__call__</span><span class=\"p\">(</span><span\n        class=\"o\">*</span><span class=\"n\">args</span><span class=\"p\">,</span> <span class=\"o\">**</span><span class=\"n\">kwargs</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">return</span> <span class=\"bp\">cls</span><span class=\"o\">.</span><span\n        class=\"n\">_instances</span><span class=\"p\">[</span><span class=\"bp\">cls</span><span class=\"p\">]</span></div>\n</div>\n\n\n\n<div class=\"viewcode-block\" id=\"Protect\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.protect.Protect\">[文档]</a>\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">Protect</span><span class=\"p\">(</span><span\n        class=\"n\">metaclass</span><span class=\"o\">=</span><span class=\"n\">Singleton</span><span class=\"p\">):</span>\n<span class=\"w\">    </span><span class=\"sd\">&quot;&quot;&quot;保护类，风控保护机制。</span>\n\n<span class=\"sd\">    使用单例模式确保全局只有一个实例。</span>\n\n<span class=\"sd\">    Attributes:</span>\n<span class=\"sd\">        login_stat_path (str): 登录状态文件的路径</span>\n<span class=\"sd\">        login_stat (dict): 登录状态信息</span>\n<span class=\"sd\">        login_time (int): 最后登录时间戳</span>\n<span class=\"sd\">        login_device_id (str): 最后登录的设备ID</span>\n<span class=\"sd\">    &quot;&quot;&quot;</span>\n\n<div class=\"viewcode-block\" id=\"Protect.__init__\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.protect.Protect.__init__\">[文档]</a>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"fm\">__init__</span><span class=\"p\">(</span><span\n        class=\"bp\">self</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;初始化保护类实例。</span>\n\n<span class=\"sd\">        创建或加载登录状态文件，初始化登录时间和设备ID。</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">login_stat_path</span> <span\n        class=\"o\">=</span> <span class=\"n\">os</span><span class=\"o\">.</span><span class=\"n\">path</span><span\n        class=\"o\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span class=\"n\">os</span><span\n        class=\"o\">.</span><span class=\"n\">path</span><span class=\"o\">.</span><span class=\"n\">dirname</span><span\n        class=\"p\">(</span><span class=\"vm\">__file__</span><span class=\"p\">),</span> <span class=\"s2\">&quot;login_stat.json&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">os</span><span class=\"o\">.</span><span\n        class=\"n\">path</span><span class=\"o\">.</span><span class=\"n\">exists</span><span class=\"p\">(</span><span\n        class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">login_stat_path</span><span class=\"p\">):</span>\n            <span class=\"n\">default_config</span> <span class=\"o\">=</span> <span class=\"p\">{</span>\n                <span class=\"s2\">&quot;login_time&quot;</span><span class=\"p\">:</span> <span class=\"mi\">0</span><span\n        class=\"p\">,</span>\n                <span class=\"s2\">&quot;device_id&quot;</span><span class=\"p\">:</span> <span\n        class=\"s2\">&quot;&quot;</span>\n            <span class=\"p\">}</span>\n            <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">login_stat_path</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;w&quot;</span><span class=\"p\">,</span> <span class=\"n\">encoding</span><span\n        class=\"o\">=</span><span class=\"s2\">&quot;utf-8&quot;</span><span class=\"p\">)</span> <span\n        class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n                <span class=\"n\">f</span><span class=\"o\">.</span><span class=\"n\">write</span><span\n        class=\"p\">(</span><span class=\"n\">json</span><span class=\"o\">.</span><span class=\"n\">dumps</span><span\n        class=\"p\">(</span><span class=\"n\">default_config</span><span class=\"p\">,</span> <span\n        class=\"n\">indent</span><span class=\"o\">=</span><span class=\"mi\">4</span><span class=\"p\">,</span> <span\n        class=\"n\">ensure_ascii</span><span class=\"o\">=</span><span class=\"kc\">False</span><span class=\"p\">))</span>\n            <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">login_stat</span> <span\n        class=\"o\">=</span> <span class=\"n\">default_config</span>\n        <span class=\"k\">else</span><span class=\"p\">:</span>\n            <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">login_stat_path</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;r&quot;</span><span class=\"p\">,</span> <span class=\"n\">encoding</span><span\n        class=\"o\">=</span><span class=\"s2\">&quot;utf-8&quot;</span><span class=\"p\">)</span> <span\n        class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">login_stat</span> <span\n        class=\"o\">=</span> <span class=\"n\">json</span><span class=\"o\">.</span><span class=\"n\">loads</span><span\n        class=\"p\">(</span><span class=\"n\">f</span><span class=\"o\">.</span><span class=\"n\">read</span><span\n        class=\"p\">())</span>\n\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">login_time</span> <span\n        class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">login_stat</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;login_time&quot;</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span\n        class=\"p\">)</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">login_device_id</span> <span\n        class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">login_stat</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span\n        class=\"s2\">&quot;device_id&quot;</span><span class=\"p\">,</span> <span class=\"s2\">&quot;&quot;</span><span\n        class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"Protect.check\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.protect.Protect.check\">[文档]</a>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">check</span><span class=\"p\">(</span><span\n        class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">second</span><span class=\"p\">:</span> <span\n        class=\"nb\">int</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">bool</span><span\n        class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;检查是否在指定时间内，风控保护。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            second (int): 指定的秒数</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bool: 如果当前时间与上次登录时间的差小于指定秒数，返回True；否则返回False</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"n\">now</span> <span class=\"o\">=</span> <span class=\"n\">datetime</span><span class=\"o\">.</span><span\n        class=\"n\">now</span><span class=\"p\">()</span><span class=\"o\">.</span><span class=\"n\">timestamp</span><span\n        class=\"p\">()</span>\n        <span class=\"k\">return</span> <span class=\"n\">now</span> <span class=\"o\">-</span> <span\n        class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">login_time</span> <span\n        class=\"o\">&lt;</span> <span class=\"n\">second</span></div>\n\n\n<div class=\"viewcode-block\" id=\"Protect.update_login_status\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.protect.Protect.update_login_status\">[文档]</a>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">update_login_status</span><span\n        class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">device_id</span><span\n        class=\"p\">:</span> <span class=\"nb\">str</span> <span class=\"o\">=</span> <span\n        class=\"s2\">&quot;&quot;</span><span class=\"p\">):</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;更新登录状态。</span>\n\n<span class=\"sd\">        如果设备ID发生变化，更新登录时间和设备ID，并保存到文件。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            device_id (str, optional): 设备ID. Defaults to &quot;&quot;.</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"n\">device_id</span> <span class=\"o\">==</span> <span\n        class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">login_device_id</span><span class=\"p\">:</span>\n            <span class=\"k\">return</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">login_time</span> <span\n        class=\"o\">=</span> <span class=\"nb\">int</span><span class=\"p\">(</span><span class=\"n\">datetime</span><span\n        class=\"o\">.</span><span class=\"n\">now</span><span class=\"p\">()</span><span class=\"o\">.</span><span class=\"n\">timestamp</span><span\n        class=\"p\">())</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">login_stat</span><span\n        class=\"p\">[</span><span class=\"s2\">&quot;login_time&quot;</span><span class=\"p\">]</span> <span\n        class=\"o\">=</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">login_time</span>\n        <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">login_stat</span><span\n        class=\"p\">[</span><span class=\"s2\">&quot;device_id&quot;</span><span class=\"p\">]</span> <span class=\"o\">=</span> <span\n        class=\"n\">device_id</span>\n        <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span\n        class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">login_stat_path</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;w&quot;</span><span class=\"p\">,</span> <span class=\"n\">encoding</span><span\n        class=\"o\">=</span><span class=\"s2\">&quot;utf-8&quot;</span><span class=\"p\">)</span> <span\n        class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n            <span class=\"n\">f</span><span class=\"o\">.</span><span class=\"n\">write</span><span class=\"p\">(</span><span\n        class=\"n\">json</span><span class=\"o\">.</span><span class=\"n\">dumps</span><span class=\"p\">(</span><span\n        class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">login_stat</span><span class=\"p\">,</span> <span\n        class=\"n\">indent</span><span class=\"o\">=</span><span class=\"mi\">4</span><span class=\"p\">,</span> <span\n        class=\"n\">ensure_ascii</span><span class=\"o\">=</span><span class=\"kc\">False</span><span\n        class=\"p\">))</span></div>\n</div>\n\n\n\n<span class=\"n\">protector</span> <span class=\"o\">=</span> <span class=\"n\">Protect</span><span class=\"p\">()</span>\n</pre>\n                    </div>\n                </article>\n            </div>\n            <footer>\n\n                <div class=\"related-pages\">\n\n\n                </div>\n                <div class=\"bottom-of-page\">\n                    <div class=\"left-details\">\n                        <div class=\"copyright\">\n                            Copyright &#169; 2025, HenryXiaoYang\n                        </div>\n                        Made with <a href=\"https://www.sphinx-doc.org/\">Sphinx</a> and <a class=\"muted-link\"\n                                                                                          href=\"https://pradyunsg.me\">@pradyunsg</a>'s\n\n                        <a href=\"https://github.com/pradyunsg/furo\">Furo</a>\n\n                    </div>\n                    <div class=\"right-details\">\n\n                    </div>\n                </div>\n\n            </footer>\n        </div>\n        <aside class=\"toc-drawer no-toc\">\n\n\n        </aside>\n    </div>\n</div>\n<script src=\"../../../_static/documentation_options.js?v=91bfbbb6\"></script>\n<script src=\"../../../_static/doctools.js?v=9bcbadda\"></script>\n<script src=\"../../../_static/sphinx_highlight.js?v=dc90522c\"></script>\n<script src=\"../../../_static/scripts/furo.js?v=5fa4622c\"></script>\n<script src=\"../../../_static/translations.js?v=beaddf03\"></script>\n</body>\n</html>"
  },
  {
    "path": "docs/WechatAPIClient/_modules/WechatAPI/Client/tool.html",
    "content": "<!doctype html>\n<html class=\"no-js\" data-content_root=\"../../../\" lang=\"zh-CN\">\n<head>\n    <meta charset=\"utf-8\"/>\n    <meta content=\"width=device-width,initial-scale=1\" name=\"viewport\"/>\n    <meta content=\"light dark\" name=\"color-scheme\">\n    <link href=\"../../../genindex.html\" rel=\"index\" title=\"索引\"/>\n    <link href=\"../../../search.html\" rel=\"search\" title=\"搜索\"/>\n\n    <!-- Generated with Sphinx 8.1.3 and Furo 2024.08.06 -->\n    <title>WechatAPI.Client.tool - XYBotV2</title>\n    <link href=\"../../../_static/pygments.css?v=8f2a1f02\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo.css?v=354aac6f\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo-extensions.css?v=302659d7\" rel=\"stylesheet\" type=\"text/css\"/>\n\n\n    <style>\n        body {\n            --color-code-background: #f8f8f8;\n            --color-code-foreground: black;\n            --color-brand-primary: #2962ff;\n            --color-brand-content: #2962ff;\n\n        }\n\n        @media not print {\n            body[data-theme=\"dark\"] {\n                --color-code-background: #202020;\n                --color-code-foreground: #d0d0d0;\n\n            }\n\n            @media (prefers-color-scheme: dark) {\n                body:not([data-theme=\"light\"]) {\n                    --color-code-background: #202020;\n                    --color-code-foreground: #d0d0d0;\n\n                }\n            }\n        }\n    </style>\n</head>\n<body>\n\n<script>\n    document.body.dataset.theme = localStorage.getItem(\"theme\") || \"auto\";\n</script>\n\n\n<svg style=\"display: none;\" xmlns=\"http://www.w3.org/2000/svg\">\n    <symbol id=\"svg-toc\" viewBox=\"0 0 24 24\">\n        <title>Contents</title>\n        <svg fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 1024 1024\">\n            <path d=\"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-menu\" viewBox=\"0 0 24 24\">\n        <title>Menu</title>\n        <svg class=\"feather-menu\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-arrow-right\" viewBox=\"0 0 24 24\">\n        <title>Expand</title>\n        <svg class=\"feather-chevron-right\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <polyline points=\"9 18 15 12 9 6\"></polyline>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun\" viewBox=\"0 0 24 24\">\n        <title>Light mode</title>\n        <svg class=\"feather-sun\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\n            <line x1=\"12\" x2=\"12\" y1=\"1\" y2=\"3\"></line>\n            <line x1=\"12\" x2=\"12\" y1=\"21\" y2=\"23\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"4.22\" y2=\"5.64\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"18.36\" y2=\"19.78\"></line>\n            <line x1=\"1\" x2=\"3\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"21\" x2=\"23\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"19.78\" y2=\"18.36\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"5.64\" y2=\"4.22\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon\" viewBox=\"0 0 24 24\">\n        <title>Dark mode</title>\n        <svg class=\"icon-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun-with-moon\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in light mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 5.411 14.504 C 5.471 14.504 5.532 14.504 5.591 14.504 C 3.639 16.319 4.383 19.569 6.931 20.352 C 7.693 20.586 8.512 20.551 9.25 20.252 C 8.023 23.207 4.056 23.725 2.11 21.184 C 0.166 18.642 1.702 14.949 4.874 14.536 C 5.051 14.512 5.231 14.5 5.411 14.5 L 5.411 14.504 Z\"\n                  style=\"opacity: 50%\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"3.25\" y2=\"1.25\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"15.85\" y2=\"17.85\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"5.094\" y2=\"3.68\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"14.05\" y2=\"15.464\"/>\n            <line x1=\"8.2\" x2=\"6.2\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"20.8\" x2=\"22.8\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"14.006\" y2=\"15.42\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"5.05\" y2=\"3.636\"/>\n            <circle cx=\"14.5\" cy=\"9.55\" r=\"3.6\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon-with-sun\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in dark mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 8.282 7.007 C 8.385 7.007 8.494 7.007 8.595 7.007 C 5.18 10.184 6.481 15.869 10.942 17.24 C 12.275 17.648 13.706 17.589 15 17.066 C 12.851 22.236 5.91 23.143 2.505 18.696 C -0.897 14.249 1.791 7.786 7.342 7.063 C 7.652 7.021 7.965 7 8.282 7 L 8.282 7.007 Z\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"3.705\" y2=\"2.5\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"11.295\" y2=\"12.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"4.816\" y2=\"3.964\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"10.212\" y2=\"11.063\"/>\n            <line style=\"opacity: 50%\" x1=\"14.205\" x2=\"13.001\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"21.795\" x2=\"23\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"10.184\" y2=\"11.036\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"4.789\" y2=\"3.937\"/>\n            <circle cx=\"18\" cy=\"7.5\" r=\"2.169\" style=\"opacity: 50%\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-pencil\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-pencil-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4\"/>\n            <path d=\"M13.5 6.5l4 4\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-eye\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-eye-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\"/>\n            <path\n                    d=\"M11.11 17.958c-3.209 -.307 -5.91 -2.293 -8.11 -5.958c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6c-.21 .352 -.427 .688 -.647 1.008\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n</svg>\n\n<input class=\"sidebar-toggle\" id=\"__navigation\" name=\"__navigation\" type=\"checkbox\">\n<input class=\"sidebar-toggle\" id=\"__toc\" name=\"__toc\" type=\"checkbox\">\n<label class=\"overlay sidebar-overlay\" for=\"__navigation\">\n    <div class=\"visually-hidden\">Hide navigation sidebar</div>\n</label>\n<label class=\"overlay toc-overlay\" for=\"__toc\">\n    <div class=\"visually-hidden\">Hide table of contents sidebar</div>\n</label>\n\n<a class=\"skip-to-content muted-link\" href=\"#furo-main-content\">Skip to content</a>\n\n\n\n<div class=\"page\">\n    <header class=\"mobile-header\">\n        <div class=\"header-left\">\n            <label class=\"nav-overlay-icon\" for=\"__navigation\">\n                <div class=\"visually-hidden\">Toggle site navigation sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-menu\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n        <div class=\"header-center\">\n            <a href=\"../../../index.html\">\n                <div class=\"brand\">XYBotV2</div>\n            </a>\n        </div>\n        <div class=\"header-right\">\n            <div class=\"theme-toggle-container theme-toggle-header\">\n                <button class=\"theme-toggle\">\n                    <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                    <svg class=\"theme-icon-when-auto-light\">\n                        <use href=\"#svg-sun-with-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-auto-dark\">\n                        <use href=\"#svg-moon-with-sun\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-dark\">\n                        <use href=\"#svg-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-light\">\n                        <use href=\"#svg-sun\"></use>\n                    </svg>\n                </button>\n            </div>\n            <label class=\"toc-overlay-icon toc-header-icon no-toc\" for=\"__toc\">\n                <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-toc\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n    </header>\n    <aside class=\"sidebar-drawer\">\n        <div class=\"sidebar-container\">\n\n            <div class=\"sidebar-sticky\">\n                <div class=\"sidebar-scroll\"><a class=\"sidebar-brand\" href=\"../../../index.html\">\n\n\n                    <span class=\"sidebar-brand-text\">XYBotV2</span>\n\n                </a>\n                    <form action=\"../../../search.html\" class=\"sidebar-search-container\" method=\"get\" role=\"search\">\n                        <input aria-label=\"搜索\" class=\"sidebar-search\" name=\"q\" placeholder=\"搜索\">\n                        <input name=\"check_keywords\" type=\"hidden\" value=\"yes\">\n                        <input name=\"area\" type=\"hidden\" value=\"default\">\n                    </form>\n                    <div id=\"searchbox\"></div>\n                    <div class=\"sidebar-tree\">\n\n                    </div>\n                    <div class=\"sidebar-tree\">\n                        <p class=\"caption\"><span class=\"caption-text\">重要函数导航</span></p>\n                        <ul>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">登录</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.is_running\">检查WechatAPI是否在运行</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_qr_code\">获取登录二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.awaken_login\">二次登录(唤醒登录)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.check_login_uuid\">检查登录的UUID状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_cached_info\">获取登录缓存信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.log_out\">登出当前账号</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.heartbeat\">发送心跳包</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\">开始自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\">停止自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\">获取自动心跳状态</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">消息</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.sync_message\">同步消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_text_message\">发送文本消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_image_message\">发送图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_voice_message\">发送语音消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_video_message\">发送视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_link_message\">发送链接消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_card_message\">发送名片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_app_message\">发送应用(xml)消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_emoji_message\">发送表情消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\">转发图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\">转发视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\">转发文件消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.revoke_message\">撤回消息</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">用户</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_my_qrcode\">获取个人二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_profile\">获取用户信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.is_logged_in\">检查是否登录</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">群聊</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\">获取群聊信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\">获取群聊公告</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\">获取群聊成员列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\">获取群聊二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\">添加群成员(群聊最多40人)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\">邀请群聊成员(群聊大于40人)</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">好友</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contact\">获取联系人信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_detail\">获取联系人详情</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_list\">获取联系人列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_nickname\">获取用户昵称</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.accept_friend\">接受好友请求</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">红包</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\">获取红包详情</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">工具</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.check_database\">检查数据库状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.set_step\">设置步数</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_image\">下载高清图片</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_video\">下载视频</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_voice\">下载语音文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_attach\">下载附件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_byte\">base64转字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_file\">base64转文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.byte_to_base64\">字节转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.file_to_base64\">文件转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\">silk的base64转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\">silk字节转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\">WAV字节转AMR的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\">WAV字节转AMR字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\">WAV字节转silk的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\">WAV字节转silk字节</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                        </ul>\n                    </div>\n                </div>\n            </div>\n\n        </div>\n    </aside>\n    <div class=\"main\">\n        <div class=\"content\">\n            <div class=\"article-container\">\n                <a class=\"back-to-top muted-link\" href=\"#\">\n                    <svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n                        <path d=\"M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z\"></path>\n                    </svg>\n                    <span>Back to top</span>\n                </a>\n                <div class=\"content-icon-container\">\n                    <div class=\"theme-toggle-container theme-toggle-content\">\n                        <button class=\"theme-toggle\">\n                            <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                            <svg class=\"theme-icon-when-auto-light\">\n                                <use href=\"#svg-sun-with-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-auto-dark\">\n                                <use href=\"#svg-moon-with-sun\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-dark\">\n                                <use href=\"#svg-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-light\">\n                                <use href=\"#svg-sun\"></use>\n                            </svg>\n                        </button>\n                    </div>\n                    <label class=\"toc-overlay-icon toc-content-icon no-toc\" for=\"__toc\">\n                        <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                        <i class=\"icon\">\n                            <svg>\n                                <use href=\"#svg-toc\"></use>\n                            </svg>\n                        </i>\n                    </label>\n                </div>\n                <article id=\"furo-main-content\" role=\"main\">\n                    <h1>WechatAPI.Client.tool 源代码</h1>\n                    <div class=\"highlight\"><pre>\n<span></span><span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">base64</span>\n<span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">io</span>\n<span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">os</span>\n\n<span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">aiohttp</span>\n<span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">pysilk</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">pydub</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"n\">AudioSegment</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.base</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"o\">*</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.protect</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"n\">protector</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">..errors</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"o\">*</span>\n\n\n<div class=\"viewcode-block\" id=\"ToolMixin\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin\">[文档]</a>\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">ToolMixin</span><span class=\"p\">(</span><span\n        class=\"n\">WechatAPIClientBase</span><span class=\"p\">):</span>\n<div class=\"viewcode-block\" id=\"ToolMixin.download_image\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin.download_image\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">download_image</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">,</span> <span class=\"n\">aeskey</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"n\">cdnmidimgurl</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;CDN下载高清图片。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            aeskey (str): 图片的AES密钥</span>\n<span class=\"sd\">            cdnmidimgurl (str): 图片的CDN URL</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            str: 图片的base64编码字符串</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;AesKey&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">aeskey</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Cdnmidimgurl&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">cdnmidimgurl</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/CdnDownloadImg&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ToolMixin.download_voice\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin.download_voice\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">download_voice</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">,</span> <span class=\"n\">msg_id</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"n\">voiceurl</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"n\">length</span><span class=\"p\">:</span> <span class=\"nb\">int</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;下载语音文件。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            msg_id (str): 消息的msgid</span>\n<span class=\"sd\">            voiceurl (str): 语音的url，从xml获取</span>\n<span class=\"sd\">            length (int): 语音长度，从xml获取</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            str: 语音的base64编码字符串</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;MsgId&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">msg_id</span><span class=\"p\">,</span> <span class=\"s2\">&quot;Voiceurl&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">voiceurl</span><span class=\"p\">,</span> <span\n        class=\"s2\">&quot;Length&quot;</span><span class=\"p\">:</span> <span class=\"n\">length</span><span\n        class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/DownloadVoice&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;data&quot;</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;buffer&quot;</span><span\n        class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ToolMixin.download_attach\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin.download_attach\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">download_attach</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">,</span> <span class=\"n\">attach_id</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">dict</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;下载附件。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            attach_id (str): 附件ID</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            dict: 附件数据</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;AttachId&quot;</span><span class=\"p\">:</span> <span class=\"n\">attach_id</span><span\n        class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/DownloadAttach&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;data&quot;</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;buffer&quot;</span><span\n        class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ToolMixin.download_video\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin.download_video\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">download_video</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">,</span> <span class=\"n\">msg_id</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span\n        class=\"nb\">str</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;下载视频。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            msg_id (str): 消息的msg_id</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            str: 视频的base64编码字符串</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;MsgId&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">msg_id</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/DownloadVideo&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;data&quot;</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;buffer&quot;</span><span\n        class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ToolMixin.set_step\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin.set_step\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">set_step</span><span\n        class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">count</span><span\n        class=\"p\">:</span> <span class=\"nb\">int</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span\n        class=\"nb\">bool</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;设置步数。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            count (int): 要设置的步数</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bool: 成功返回True，失败返回False</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            BanProtection: 风控保护: 新设备登录后4小时内请挂机</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">ignore_protect</span> <span class=\"ow\">and</span> <span\n        class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span class=\"p\">(</span><span\n        class=\"mi\">14400</span><span class=\"p\">):</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;风控保护: 新设备登录后4小时内请挂机&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;StepCount&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">count</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span class=\"s1\">/SetStep&#39;</span><span\n        class=\"p\">,</span> <span class=\"n\">json</span><span class=\"o\">=</span><span class=\"n\">json_param</span><span\n        class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"kc\">True</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ToolMixin.set_proxy\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin.set_proxy\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">set_proxy</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">proxy</span><span class=\"p\">:</span> <span class=\"n\">Proxy</span><span class=\"p\">)</span> <span\n        class=\"o\">-&gt;</span> <span class=\"nb\">bool</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;设置代理。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            proxy (Proxy): 代理配置对象</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bool: 成功返回True，失败返回False</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span>\n                          <span class=\"s2\">&quot;Proxy&quot;</span><span class=\"p\">:</span> <span\n        class=\"p\">{</span><span class=\"s2\">&quot;ProxyIp&quot;</span><span class=\"p\">:</span> <span\n        class=\"sa\">f</span><span class=\"s2\">&quot;</span><span class=\"si\">{</span><span class=\"n\">proxy</span><span\n        class=\"o\">.</span><span class=\"n\">ip</span><span class=\"si\">}</span><span class=\"s2\">:</span><span\n        class=\"si\">{</span><span class=\"n\">proxy</span><span class=\"o\">.</span><span class=\"n\">port</span><span\n        class=\"si\">}</span><span class=\"s2\">&quot;</span><span class=\"p\">,</span>\n                                    <span class=\"s2\">&quot;ProxyUser&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">proxy</span><span class=\"o\">.</span><span class=\"n\">username</span><span class=\"p\">,</span>\n                                    <span class=\"s2\">&quot;ProxyPassword&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">proxy</span><span class=\"o\">.</span><span class=\"n\">password</span><span class=\"p\">}}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/SetProxy&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"kc\">True</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ToolMixin.check_database\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin.check_database\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">check_database</span><span class=\"p\">(</span><span class=\"bp\">self</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">bool</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;检查数据库状态。</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bool: 数据库正常返回True，否则返回False</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/CheckDatabaseOK&#39;</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Running&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"kc\">True</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"k\">return</span> <span class=\"kc\">False</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ToolMixin.base64_to_file\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin.base64_to_file\">[文档]</a>\n    <span class=\"nd\">@staticmethod</span>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">base64_to_file</span><span\n        class=\"p\">(</span><span class=\"n\">base64_str</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"n\">file_name</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">,</span> <span class=\"n\">file_path</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">bool</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;将base64字符串转换为文件并保存。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            base64_str (str): base64编码的字符串</span>\n<span class=\"sd\">            file_name (str): 要保存的文件名</span>\n<span class=\"sd\">            file_path (str): 文件保存路径</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bool: 转换成功返回True，失败返回False</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">try</span><span class=\"p\">:</span>\n            <span class=\"n\">os</span><span class=\"o\">.</span><span class=\"n\">makedirs</span><span\n        class=\"p\">(</span><span class=\"n\">file_path</span><span class=\"p\">,</span> <span class=\"n\">exist_ok</span><span\n        class=\"o\">=</span><span class=\"kc\">True</span><span class=\"p\">)</span>\n\n            <span class=\"c1\"># 拼接完整的文件路径</span>\n            <span class=\"n\">full_path</span> <span class=\"o\">=</span> <span class=\"n\">os</span><span\n        class=\"o\">.</span><span class=\"n\">path</span><span class=\"o\">.</span><span class=\"n\">join</span><span class=\"p\">(</span><span\n        class=\"n\">file_path</span><span class=\"p\">,</span> <span class=\"n\">file_name</span><span class=\"p\">)</span>\n\n            <span class=\"c1\"># 移除可能存在的 base64 头部信息</span>\n            <span class=\"k\">if</span> <span class=\"s1\">&#39;,&#39;</span> <span class=\"ow\">in</span> <span class=\"n\">base64_str</span><span\n        class=\"p\">:</span>\n                <span class=\"n\">base64_str</span> <span class=\"o\">=</span> <span class=\"n\">base64_str</span><span\n        class=\"o\">.</span><span class=\"n\">split</span><span class=\"p\">(</span><span class=\"s1\">&#39;,&#39;</span><span\n        class=\"p\">)[</span><span class=\"mi\">1</span><span class=\"p\">]</span>\n\n            <span class=\"c1\"># 解码 base64 并写入文件</span>\n            <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span\n        class=\"n\">full_path</span><span class=\"p\">,</span> <span class=\"s1\">&#39;wb&#39;</span><span class=\"p\">)</span> <span\n        class=\"k\">as</span> <span class=\"n\">f</span><span class=\"p\">:</span>\n                <span class=\"n\">f</span><span class=\"o\">.</span><span class=\"n\">write</span><span\n        class=\"p\">(</span><span class=\"n\">base64</span><span class=\"o\">.</span><span class=\"n\">b64decode</span><span\n        class=\"p\">(</span><span class=\"n\">base64_str</span><span class=\"p\">))</span>\n\n            <span class=\"k\">return</span> <span class=\"kc\">True</span>\n\n        <span class=\"k\">except</span> <span class=\"ne\">Exception</span> <span class=\"k\">as</span> <span\n        class=\"n\">e</span><span class=\"p\">:</span>\n            <span class=\"k\">return</span> <span class=\"kc\">False</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ToolMixin.file_to_base64\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin.file_to_base64\">[文档]</a>\n    <span class=\"nd\">@staticmethod</span>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">file_to_base64</span><span\n        class=\"p\">(</span><span class=\"n\">file_path</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;将文件转换为base64字符串。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            file_path (str): 文件路径</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            str: base64编码的字符串</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">with</span> <span class=\"nb\">open</span><span class=\"p\">(</span><span class=\"n\">file_path</span><span\n        class=\"p\">,</span> <span class=\"s1\">&#39;rb&#39;</span><span class=\"p\">)</span> <span class=\"k\">as</span> <span\n        class=\"n\">f</span><span class=\"p\">:</span>\n            <span class=\"k\">return</span> <span class=\"n\">base64</span><span class=\"o\">.</span><span\n        class=\"n\">b64encode</span><span class=\"p\">(</span><span class=\"n\">f</span><span class=\"o\">.</span><span\n        class=\"n\">read</span><span class=\"p\">())</span><span class=\"o\">.</span><span class=\"n\">decode</span><span\n        class=\"p\">()</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ToolMixin.base64_to_byte\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin.base64_to_byte\">[文档]</a>\n    <span class=\"nd\">@staticmethod</span>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">base64_to_byte</span><span\n        class=\"p\">(</span><span class=\"n\">base64_str</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">bytes</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;将base64字符串转换为bytes。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            base64_str (str): base64编码的字符串</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bytes: 解码后的字节数据</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"c1\"># 移除可能存在的 base64 头部信息</span>\n        <span class=\"k\">if</span> <span class=\"s1\">&#39;,&#39;</span> <span class=\"ow\">in</span> <span class=\"n\">base64_str</span><span\n        class=\"p\">:</span>\n            <span class=\"n\">base64_str</span> <span class=\"o\">=</span> <span class=\"n\">base64_str</span><span class=\"o\">.</span><span\n        class=\"n\">split</span><span class=\"p\">(</span><span class=\"s1\">&#39;,&#39;</span><span class=\"p\">)[</span><span\n        class=\"mi\">1</span><span class=\"p\">]</span>\n\n        <span class=\"k\">return</span> <span class=\"n\">base64</span><span class=\"o\">.</span><span\n        class=\"n\">b64decode</span><span class=\"p\">(</span><span class=\"n\">base64_str</span><span\n        class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ToolMixin.byte_to_base64\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin.byte_to_base64\">[文档]</a>\n    <span class=\"nd\">@staticmethod</span>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">byte_to_base64</span><span\n        class=\"p\">(</span><span class=\"n\">byte</span><span class=\"p\">:</span> <span class=\"nb\">bytes</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;将bytes转换为base64字符串。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            byte (bytes): 字节数据</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            str: base64编码的字符串</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"n\">base64</span><span class=\"o\">.</span><span\n        class=\"n\">b64encode</span><span class=\"p\">(</span><span class=\"n\">byte</span><span class=\"p\">)</span><span\n        class=\"o\">.</span><span class=\"n\">decode</span><span class=\"p\">(</span><span class=\"s2\">&quot;utf-8&quot;</span><span\n        class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ToolMixin.silk_byte_to_byte_wav_byte\">\n<a class=\"viewcode-back\"\n   href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\">[文档]</a>\n    <span class=\"nd\">@staticmethod</span>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">silk_byte_to_byte_wav_byte</span><span\n        class=\"p\">(</span><span class=\"n\">silk_byte</span><span class=\"p\">:</span> <span class=\"nb\">bytes</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">bytes</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;将silk字节转换为wav字节。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            silk_byte (bytes): silk格式的字节数据</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bytes: wav格式的字节数据</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"k\">await</span> <span class=\"n\">pysilk</span><span class=\"o\">.</span><span\n        class=\"n\">async_decode</span><span class=\"p\">(</span><span class=\"n\">silk_byte</span><span\n        class=\"p\">,</span> <span class=\"n\">to_wav</span><span class=\"o\">=</span><span class=\"kc\">True</span><span\n        class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ToolMixin.wav_byte_to_amr_byte\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\">[文档]</a>\n    <span class=\"nd\">@staticmethod</span>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">wav_byte_to_amr_byte</span><span\n        class=\"p\">(</span><span class=\"n\">wav_byte</span><span class=\"p\">:</span> <span class=\"nb\">bytes</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">bytes</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;将WAV字节数据转换为AMR格式。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wav_byte (bytes): WAV格式的字节数据</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bytes: AMR格式的字节数据</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            Exception: 转换失败时抛出异常</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">try</span><span class=\"p\">:</span>\n            <span class=\"c1\"># 从字节数据创建 AudioSegment 对象</span>\n            <span class=\"n\">audio</span> <span class=\"o\">=</span> <span class=\"n\">AudioSegment</span><span\n        class=\"o\">.</span><span class=\"n\">from_wav</span><span class=\"p\">(</span><span class=\"n\">io</span><span\n        class=\"o\">.</span><span class=\"n\">BytesIO</span><span class=\"p\">(</span><span class=\"n\">wav_byte</span><span\n        class=\"p\">))</span>\n\n            <span class=\"c1\"># 设置 AMR 编码的标准参数</span>\n            <span class=\"n\">audio</span> <span class=\"o\">=</span> <span class=\"n\">audio</span><span\n        class=\"o\">.</span><span class=\"n\">set_frame_rate</span><span class=\"p\">(</span><span class=\"mi\">8000</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">set_channels</span><span class=\"p\">(</span><span\n        class=\"mi\">1</span><span class=\"p\">)</span>\n\n            <span class=\"c1\"># 创建一个字节缓冲区来存储 AMR 数据</span>\n            <span class=\"n\">output</span> <span class=\"o\">=</span> <span class=\"n\">io</span><span\n        class=\"o\">.</span><span class=\"n\">BytesIO</span><span class=\"p\">()</span>\n\n            <span class=\"c1\"># 导出为 AMR 格式</span>\n            <span class=\"n\">audio</span><span class=\"o\">.</span><span class=\"n\">export</span><span\n        class=\"p\">(</span><span class=\"n\">output</span><span class=\"p\">,</span> <span class=\"nb\">format</span><span\n        class=\"o\">=</span><span class=\"s2\">&quot;amr&quot;</span><span class=\"p\">)</span>\n\n            <span class=\"c1\"># 获取字节数据</span>\n            <span class=\"k\">return</span> <span class=\"n\">output</span><span class=\"o\">.</span><span\n        class=\"n\">getvalue</span><span class=\"p\">()</span>\n\n        <span class=\"k\">except</span> <span class=\"ne\">Exception</span> <span class=\"k\">as</span> <span\n        class=\"n\">e</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"ne\">Exception</span><span class=\"p\">(</span><span\n        class=\"sa\">f</span><span class=\"s2\">&quot;转换WAV到AMR失败: </span><span class=\"si\">{</span><span\n        class=\"nb\">str</span><span class=\"p\">(</span><span class=\"n\">e</span><span class=\"p\">)</span><span\n        class=\"si\">}</span><span class=\"s2\">&quot;</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ToolMixin.wav_byte_to_amr_base64\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\">[文档]</a>\n    <span class=\"nd\">@staticmethod</span>\n    <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">wav_byte_to_amr_base64</span><span\n        class=\"p\">(</span><span class=\"n\">wav_byte</span><span class=\"p\">:</span> <span class=\"nb\">bytes</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;将WAV字节数据转换为AMR格式的base64字符串。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wav_byte (bytes): WAV格式的字节数据</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            str: AMR格式的base64编码字符串</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"n\">base64</span><span class=\"o\">.</span><span\n        class=\"n\">b64encode</span><span class=\"p\">(</span><span class=\"n\">ToolMixin</span><span class=\"o\">.</span><span\n        class=\"n\">wav_byte_to_amr_byte</span><span class=\"p\">(</span><span class=\"n\">wav_byte</span><span\n        class=\"p\">))</span><span class=\"o\">.</span><span class=\"n\">decode</span><span class=\"p\">()</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ToolMixin.wav_byte_to_silk_byte\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\">[文档]</a>\n    <span class=\"nd\">@staticmethod</span>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">wav_byte_to_silk_byte</span><span\n        class=\"p\">(</span><span class=\"n\">wav_byte</span><span class=\"p\">:</span> <span class=\"nb\">bytes</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">bytes</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;将WAV字节数据转换为silk格式。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wav_byte (bytes): WAV格式的字节数据</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bytes: silk格式的字节数据</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"c1\"># get pcm data</span>\n        <span class=\"n\">audio</span> <span class=\"o\">=</span> <span class=\"n\">AudioSegment</span><span\n        class=\"o\">.</span><span class=\"n\">from_wav</span><span class=\"p\">(</span><span class=\"n\">io</span><span\n        class=\"o\">.</span><span class=\"n\">BytesIO</span><span class=\"p\">(</span><span class=\"n\">wav_byte</span><span\n        class=\"p\">))</span>\n        <span class=\"n\">pcm</span> <span class=\"o\">=</span> <span class=\"n\">audio</span><span class=\"o\">.</span><span\n        class=\"n\">raw_data</span>\n        <span class=\"k\">return</span> <span class=\"k\">await</span> <span class=\"n\">pysilk</span><span class=\"o\">.</span><span\n        class=\"n\">async_encode</span><span class=\"p\">(</span><span class=\"n\">pcm</span><span class=\"p\">,</span> <span\n        class=\"n\">data_rate</span><span class=\"o\">=</span><span class=\"n\">audio</span><span class=\"o\">.</span><span\n        class=\"n\">frame_rate</span><span class=\"p\">,</span> <span class=\"n\">sample_rate</span><span\n        class=\"o\">=</span><span class=\"n\">audio</span><span class=\"o\">.</span><span class=\"n\">frame_rate</span><span\n        class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ToolMixin.wav_byte_to_silk_base64\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\">[文档]</a>\n    <span class=\"nd\">@staticmethod</span>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">wav_byte_to_silk_base64</span><span\n        class=\"p\">(</span><span class=\"n\">wav_byte</span><span class=\"p\">:</span> <span class=\"nb\">bytes</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;将WAV字节数据转换为silk格式的base64字符串。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wav_byte (bytes): WAV格式的字节数据</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            str: silk格式的base64编码字符串</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"n\">base64</span><span class=\"o\">.</span><span\n        class=\"n\">b64encode</span><span class=\"p\">(</span><span class=\"k\">await</span> <span\n        class=\"n\">ToolMixin</span><span class=\"o\">.</span><span class=\"n\">wav_byte_to_silk_byte</span><span\n        class=\"p\">(</span><span class=\"n\">wav_byte</span><span class=\"p\">))</span><span class=\"o\">.</span><span\n        class=\"n\">decode</span><span class=\"p\">()</span></div>\n\n\n<div class=\"viewcode-block\" id=\"ToolMixin.silk_base64_to_wav_byte\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\">[文档]</a>\n    <span class=\"nd\">@staticmethod</span>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">silk_base64_to_wav_byte</span><span\n        class=\"p\">(</span><span class=\"n\">silk_base64</span><span class=\"p\">:</span> <span class=\"nb\">str</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">bytes</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;将silk格式的base64字符串转换为WAV字节数据。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            silk_base64 (str): silk格式的base64编码字符串</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bytes: WAV格式的字节数据</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">return</span> <span class=\"k\">await</span> <span class=\"n\">ToolMixin</span><span\n        class=\"o\">.</span><span class=\"n\">silk_byte_to_byte_wav_byte</span><span class=\"p\">(</span><span class=\"n\">base64</span><span\n        class=\"o\">.</span><span class=\"n\">b64decode</span><span class=\"p\">(</span><span\n        class=\"n\">silk_base64</span><span class=\"p\">))</span></div>\n</div>\n\n</pre>\n                    </div>\n                </article>\n            </div>\n            <footer>\n\n                <div class=\"related-pages\">\n\n\n                </div>\n                <div class=\"bottom-of-page\">\n                    <div class=\"left-details\">\n                        <div class=\"copyright\">\n                            Copyright &#169; 2025, HenryXiaoYang\n                        </div>\n                        Made with <a href=\"https://www.sphinx-doc.org/\">Sphinx</a> and <a class=\"muted-link\"\n                                                                                          href=\"https://pradyunsg.me\">@pradyunsg</a>'s\n\n                        <a href=\"https://github.com/pradyunsg/furo\">Furo</a>\n\n                    </div>\n                    <div class=\"right-details\">\n\n                    </div>\n                </div>\n\n            </footer>\n        </div>\n        <aside class=\"toc-drawer no-toc\">\n\n\n        </aside>\n    </div>\n</div>\n<script src=\"../../../_static/documentation_options.js?v=91bfbbb6\"></script>\n<script src=\"../../../_static/doctools.js?v=9bcbadda\"></script>\n<script src=\"../../../_static/sphinx_highlight.js?v=dc90522c\"></script>\n<script src=\"../../../_static/scripts/furo.js?v=5fa4622c\"></script>\n<script src=\"../../../_static/translations.js?v=beaddf03\"></script>\n</body>\n</html>"
  },
  {
    "path": "docs/WechatAPIClient/_modules/WechatAPI/Client/user.html",
    "content": "<!doctype html>\n<html class=\"no-js\" data-content_root=\"../../../\" lang=\"zh-CN\">\n<head>\n    <meta charset=\"utf-8\"/>\n    <meta content=\"width=device-width,initial-scale=1\" name=\"viewport\"/>\n    <meta content=\"light dark\" name=\"color-scheme\">\n    <link href=\"../../../genindex.html\" rel=\"index\" title=\"索引\"/>\n    <link href=\"../../../search.html\" rel=\"search\" title=\"搜索\"/>\n\n    <!-- Generated with Sphinx 8.1.3 and Furo 2024.08.06 -->\n    <title>WechatAPI.Client.user - XYBotV2</title>\n    <link href=\"../../../_static/pygments.css?v=8f2a1f02\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo.css?v=354aac6f\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../../../_static/styles/furo-extensions.css?v=302659d7\" rel=\"stylesheet\" type=\"text/css\"/>\n\n\n    <style>\n        body {\n            --color-code-background: #f8f8f8;\n            --color-code-foreground: black;\n            --color-brand-primary: #2962ff;\n            --color-brand-content: #2962ff;\n\n        }\n\n        @media not print {\n            body[data-theme=\"dark\"] {\n                --color-code-background: #202020;\n                --color-code-foreground: #d0d0d0;\n\n            }\n\n            @media (prefers-color-scheme: dark) {\n                body:not([data-theme=\"light\"]) {\n                    --color-code-background: #202020;\n                    --color-code-foreground: #d0d0d0;\n\n                }\n            }\n        }\n    </style>\n</head>\n<body>\n\n<script>\n    document.body.dataset.theme = localStorage.getItem(\"theme\") || \"auto\";\n</script>\n\n\n<svg style=\"display: none;\" xmlns=\"http://www.w3.org/2000/svg\">\n    <symbol id=\"svg-toc\" viewBox=\"0 0 24 24\">\n        <title>Contents</title>\n        <svg fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 1024 1024\">\n            <path d=\"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-menu\" viewBox=\"0 0 24 24\">\n        <title>Menu</title>\n        <svg class=\"feather-menu\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-arrow-right\" viewBox=\"0 0 24 24\">\n        <title>Expand</title>\n        <svg class=\"feather-chevron-right\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <polyline points=\"9 18 15 12 9 6\"></polyline>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun\" viewBox=\"0 0 24 24\">\n        <title>Light mode</title>\n        <svg class=\"feather-sun\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\n            <line x1=\"12\" x2=\"12\" y1=\"1\" y2=\"3\"></line>\n            <line x1=\"12\" x2=\"12\" y1=\"21\" y2=\"23\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"4.22\" y2=\"5.64\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"18.36\" y2=\"19.78\"></line>\n            <line x1=\"1\" x2=\"3\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"21\" x2=\"23\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"19.78\" y2=\"18.36\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"5.64\" y2=\"4.22\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon\" viewBox=\"0 0 24 24\">\n        <title>Dark mode</title>\n        <svg class=\"icon-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun-with-moon\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in light mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 5.411 14.504 C 5.471 14.504 5.532 14.504 5.591 14.504 C 3.639 16.319 4.383 19.569 6.931 20.352 C 7.693 20.586 8.512 20.551 9.25 20.252 C 8.023 23.207 4.056 23.725 2.11 21.184 C 0.166 18.642 1.702 14.949 4.874 14.536 C 5.051 14.512 5.231 14.5 5.411 14.5 L 5.411 14.504 Z\"\n                  style=\"opacity: 50%\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"3.25\" y2=\"1.25\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"15.85\" y2=\"17.85\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"5.094\" y2=\"3.68\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"14.05\" y2=\"15.464\"/>\n            <line x1=\"8.2\" x2=\"6.2\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"20.8\" x2=\"22.8\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"14.006\" y2=\"15.42\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"5.05\" y2=\"3.636\"/>\n            <circle cx=\"14.5\" cy=\"9.55\" r=\"3.6\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon-with-sun\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in dark mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 8.282 7.007 C 8.385 7.007 8.494 7.007 8.595 7.007 C 5.18 10.184 6.481 15.869 10.942 17.24 C 12.275 17.648 13.706 17.589 15 17.066 C 12.851 22.236 5.91 23.143 2.505 18.696 C -0.897 14.249 1.791 7.786 7.342 7.063 C 7.652 7.021 7.965 7 8.282 7 L 8.282 7.007 Z\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"3.705\" y2=\"2.5\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"11.295\" y2=\"12.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"4.816\" y2=\"3.964\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"10.212\" y2=\"11.063\"/>\n            <line style=\"opacity: 50%\" x1=\"14.205\" x2=\"13.001\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"21.795\" x2=\"23\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"10.184\" y2=\"11.036\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"4.789\" y2=\"3.937\"/>\n            <circle cx=\"18\" cy=\"7.5\" r=\"2.169\" style=\"opacity: 50%\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-pencil\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-pencil-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4\"/>\n            <path d=\"M13.5 6.5l4 4\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-eye\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-eye-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\"/>\n            <path\n                    d=\"M11.11 17.958c-3.209 -.307 -5.91 -2.293 -8.11 -5.958c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6c-.21 .352 -.427 .688 -.647 1.008\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n</svg>\n\n<input class=\"sidebar-toggle\" id=\"__navigation\" name=\"__navigation\" type=\"checkbox\">\n<input class=\"sidebar-toggle\" id=\"__toc\" name=\"__toc\" type=\"checkbox\">\n<label class=\"overlay sidebar-overlay\" for=\"__navigation\">\n    <div class=\"visually-hidden\">Hide navigation sidebar</div>\n</label>\n<label class=\"overlay toc-overlay\" for=\"__toc\">\n    <div class=\"visually-hidden\">Hide table of contents sidebar</div>\n</label>\n\n<a class=\"skip-to-content muted-link\" href=\"#furo-main-content\">Skip to content</a>\n\n\n\n<div class=\"page\">\n    <header class=\"mobile-header\">\n        <div class=\"header-left\">\n            <label class=\"nav-overlay-icon\" for=\"__navigation\">\n                <div class=\"visually-hidden\">Toggle site navigation sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-menu\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n        <div class=\"header-center\">\n            <a href=\"../../../index.html\">\n                <div class=\"brand\">XYBotV2</div>\n            </a>\n        </div>\n        <div class=\"header-right\">\n            <div class=\"theme-toggle-container theme-toggle-header\">\n                <button class=\"theme-toggle\">\n                    <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                    <svg class=\"theme-icon-when-auto-light\">\n                        <use href=\"#svg-sun-with-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-auto-dark\">\n                        <use href=\"#svg-moon-with-sun\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-dark\">\n                        <use href=\"#svg-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-light\">\n                        <use href=\"#svg-sun\"></use>\n                    </svg>\n                </button>\n            </div>\n            <label class=\"toc-overlay-icon toc-header-icon no-toc\" for=\"__toc\">\n                <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-toc\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n    </header>\n    <aside class=\"sidebar-drawer\">\n        <div class=\"sidebar-container\">\n\n            <div class=\"sidebar-sticky\">\n                <div class=\"sidebar-scroll\"><a class=\"sidebar-brand\" href=\"../../../index.html\">\n\n\n                    <span class=\"sidebar-brand-text\">XYBotV2</span>\n\n                </a>\n                    <form action=\"../../../search.html\" class=\"sidebar-search-container\" method=\"get\" role=\"search\">\n                        <input aria-label=\"搜索\" class=\"sidebar-search\" name=\"q\" placeholder=\"搜索\">\n                        <input name=\"check_keywords\" type=\"hidden\" value=\"yes\">\n                        <input name=\"area\" type=\"hidden\" value=\"default\">\n                    </form>\n                    <div id=\"searchbox\"></div>\n                    <div class=\"sidebar-tree\">\n\n                    </div>\n                    <div class=\"sidebar-tree\">\n                        <p class=\"caption\"><span class=\"caption-text\">重要函数导航</span></p>\n                        <ul>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">登录</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.is_running\">检查WechatAPI是否在运行</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_qr_code\">获取登录二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.awaken_login\">二次登录(唤醒登录)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.check_login_uuid\">检查登录的UUID状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_cached_info\">获取登录缓存信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.log_out\">登出当前账号</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.heartbeat\">发送心跳包</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\">开始自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\">停止自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\">获取自动心跳状态</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">消息</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.sync_message\">同步消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_text_message\">发送文本消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_image_message\">发送图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_voice_message\">发送语音消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_video_message\">发送视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_link_message\">发送链接消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_card_message\">发送名片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_app_message\">发送应用(xml)消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_emoji_message\">发送表情消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\">转发图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\">转发视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\">转发文件消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.revoke_message\">撤回消息</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">用户</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_my_qrcode\">获取个人二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_profile\">获取用户信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.is_logged_in\">检查是否登录</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">群聊</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\">获取群聊信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\">获取群聊公告</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\">获取群聊成员列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\">获取群聊二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\">添加群成员(群聊最多40人)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\">邀请群聊成员(群聊大于40人)</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">好友</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contact\">获取联系人信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_detail\">获取联系人详情</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_list\">获取联系人列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_nickname\">获取用户昵称</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.accept_friend\">接受好友请求</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">红包</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\">获取红包详情</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">工具</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.check_database\">检查数据库状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.set_step\">设置步数</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_image\">下载高清图片</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_video\">下载视频</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_voice\">下载语音文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_attach\">下载附件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_byte\">base64转字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_file\">base64转文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.byte_to_base64\">字节转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.file_to_base64\">文件转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\">silk的base64转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\">silk字节转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\">WAV字节转AMR的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\">WAV字节转AMR字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\">WAV字节转silk的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\">WAV字节转silk字节</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                        </ul>\n                    </div>\n                </div>\n            </div>\n\n        </div>\n    </aside>\n    <div class=\"main\">\n        <div class=\"content\">\n            <div class=\"article-container\">\n                <a class=\"back-to-top muted-link\" href=\"#\">\n                    <svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n                        <path d=\"M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z\"></path>\n                    </svg>\n                    <span>Back to top</span>\n                </a>\n                <div class=\"content-icon-container\">\n                    <div class=\"theme-toggle-container theme-toggle-content\">\n                        <button class=\"theme-toggle\">\n                            <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                            <svg class=\"theme-icon-when-auto-light\">\n                                <use href=\"#svg-sun-with-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-auto-dark\">\n                                <use href=\"#svg-moon-with-sun\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-dark\">\n                                <use href=\"#svg-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-light\">\n                                <use href=\"#svg-sun\"></use>\n                            </svg>\n                        </button>\n                    </div>\n                    <label class=\"toc-overlay-icon toc-content-icon no-toc\" for=\"__toc\">\n                        <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                        <i class=\"icon\">\n                            <svg>\n                                <use href=\"#svg-toc\"></use>\n                            </svg>\n                        </i>\n                    </label>\n                </div>\n                <article id=\"furo-main-content\" role=\"main\">\n                    <h1>WechatAPI.Client.user 源代码</h1>\n                    <div class=\"highlight\"><pre>\n<span></span><span class=\"kn\">import</span><span class=\"w\"> </span><span class=\"nn\">aiohttp</span>\n\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.base</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"o\">*</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">.protect</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"n\">protector</span>\n<span class=\"kn\">from</span><span class=\"w\"> </span><span class=\"nn\">..errors</span><span class=\"w\"> </span><span\n                            class=\"kn\">import</span> <span class=\"o\">*</span>\n\n\n<div class=\"viewcode-block\" id=\"UserMixin\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.user.UserMixin\">[文档]</a>\n<span class=\"k\">class</span><span class=\"w\"> </span><span class=\"nc\">UserMixin</span><span class=\"p\">(</span><span\n        class=\"n\">WechatAPIClientBase</span><span class=\"p\">):</span>\n<div class=\"viewcode-block\" id=\"UserMixin.get_profile\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.user.UserMixin.get_profile\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">get_profile</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span> <span class=\"o\">=</span> <span\n        class=\"kc\">None</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">dict</span><span\n        class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;获取用户信息。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid (str, optional): 用户wxid. Defaults to None.</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            dict: 用户信息字典</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span> <span class=\"ow\">and</span> <span class=\"ow\">not</span> <span class=\"n\">wxid</span><span\n        class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"n\">wxid</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">wxid</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"n\">wxid</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/GetProfile&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;userInfo&quot;</span><span class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"UserMixin.get_my_qrcode\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.user.UserMixin.get_my_qrcode\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span class=\"nf\">get_my_qrcode</span><span\n        class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span class=\"n\">style</span><span\n        class=\"p\">:</span> <span class=\"nb\">int</span> <span class=\"o\">=</span> <span class=\"mi\">0</span><span\n        class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">str</span><span class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;获取个人二维码。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            style (int, optional): 二维码样式. Defaults to 0.</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            str: 图片的base64编码字符串</span>\n\n<span class=\"sd\">        Raises:</span>\n<span class=\"sd\">            UserLoggedOut: 未登录时调用</span>\n<span class=\"sd\">            BanProtection: 风控保护: 新设备登录后4小时内请挂机</span>\n<span class=\"sd\">            根据error_handler处理错误</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">UserLoggedOut</span><span class=\"p\">(</span><span class=\"s2\">&quot;请先登录&quot;</span><span\n        class=\"p\">)</span>\n        <span class=\"k\">elif</span> <span class=\"n\">protector</span><span class=\"o\">.</span><span class=\"n\">check</span><span\n        class=\"p\">(</span><span class=\"mi\">14400</span><span class=\"p\">)</span> <span class=\"ow\">and</span> <span\n        class=\"ow\">not</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">ignore_protect</span><span class=\"p\">:</span>\n            <span class=\"k\">raise</span> <span class=\"n\">BanProtection</span><span class=\"p\">(</span><span class=\"s2\">&quot;风控保护: 新设备登录后4小时内请挂机&quot;</span><span\n        class=\"p\">)</span>\n\n        <span class=\"k\">async</span> <span class=\"k\">with</span> <span class=\"n\">aiohttp</span><span\n        class=\"o\">.</span><span class=\"n\">ClientSession</span><span class=\"p\">()</span> <span class=\"k\">as</span> <span\n        class=\"n\">session</span><span class=\"p\">:</span>\n            <span class=\"n\">json_param</span> <span class=\"o\">=</span> <span class=\"p\">{</span><span class=\"s2\">&quot;Wxid&quot;</span><span\n        class=\"p\">:</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">wxid</span><span\n        class=\"p\">,</span> <span class=\"s2\">&quot;Style&quot;</span><span class=\"p\">:</span> <span\n        class=\"n\">style</span><span class=\"p\">}</span>\n            <span class=\"n\">response</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">session</span><span\n        class=\"o\">.</span><span class=\"n\">post</span><span class=\"p\">(</span><span class=\"sa\">f</span><span class=\"s1\">&#39;http://</span><span\n        class=\"si\">{</span><span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">ip</span><span\n        class=\"si\">}</span><span class=\"s1\">:</span><span class=\"si\">{</span><span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">port</span><span class=\"si\">}</span><span\n        class=\"s1\">/GetMyQRCode&#39;</span><span class=\"p\">,</span> <span class=\"n\">json</span><span\n        class=\"o\">=</span><span class=\"n\">json_param</span><span class=\"p\">)</span>\n            <span class=\"n\">json_resp</span> <span class=\"o\">=</span> <span class=\"k\">await</span> <span class=\"n\">response</span><span\n        class=\"o\">.</span><span class=\"n\">json</span><span class=\"p\">()</span>\n\n            <span class=\"k\">if</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Success&quot;</span><span class=\"p\">):</span>\n                <span class=\"k\">return</span> <span class=\"n\">json_resp</span><span class=\"o\">.</span><span class=\"n\">get</span><span\n        class=\"p\">(</span><span class=\"s2\">&quot;Data&quot;</span><span class=\"p\">)</span><span class=\"o\">.</span><span\n        class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;qrcode&quot;</span><span\n        class=\"p\">)</span><span class=\"o\">.</span><span class=\"n\">get</span><span class=\"p\">(</span><span class=\"s2\">&quot;buffer&quot;</span><span\n        class=\"p\">)</span>\n            <span class=\"k\">else</span><span class=\"p\">:</span>\n                <span class=\"bp\">self</span><span class=\"o\">.</span><span class=\"n\">error_handler</span><span class=\"p\">(</span><span\n        class=\"n\">json_resp</span><span class=\"p\">)</span></div>\n\n\n<div class=\"viewcode-block\" id=\"UserMixin.is_logged_in\">\n<a class=\"viewcode-back\" href=\"../../../index.html#WechatAPI.Client.user.UserMixin.is_logged_in\">[文档]</a>\n    <span class=\"k\">async</span> <span class=\"k\">def</span><span class=\"w\"> </span><span\n        class=\"nf\">is_logged_in</span><span class=\"p\">(</span><span class=\"bp\">self</span><span class=\"p\">,</span> <span\n        class=\"n\">wxid</span><span class=\"p\">:</span> <span class=\"nb\">str</span> <span class=\"o\">=</span> <span\n        class=\"kc\">None</span><span class=\"p\">)</span> <span class=\"o\">-&gt;</span> <span class=\"nb\">bool</span><span\n        class=\"p\">:</span>\n<span class=\"w\">        </span><span class=\"sd\">&quot;&quot;&quot;检查是否登录。</span>\n\n<span class=\"sd\">        Args:</span>\n<span class=\"sd\">            wxid (str, optional): 用户wxid. Defaults to None.</span>\n\n<span class=\"sd\">        Returns:</span>\n<span class=\"sd\">            bool: 已登录返回True，未登录返回False</span>\n<span class=\"sd\">        &quot;&quot;&quot;</span>\n        <span class=\"k\">if</span> <span class=\"ow\">not</span> <span class=\"n\">wxid</span><span class=\"p\">:</span>\n            <span class=\"n\">wxid</span> <span class=\"o\">=</span> <span class=\"bp\">self</span><span\n        class=\"o\">.</span><span class=\"n\">wxid</span>\n        <span class=\"k\">try</span><span class=\"p\">:</span>\n            <span class=\"k\">await</span> <span class=\"bp\">self</span><span class=\"o\">.</span><span\n        class=\"n\">get_profile</span><span class=\"p\">(</span><span class=\"n\">wxid</span><span class=\"p\">)</span>\n            <span class=\"k\">return</span> <span class=\"kc\">True</span>\n        <span class=\"k\">except</span><span class=\"p\">:</span>\n            <span class=\"k\">return</span> <span class=\"kc\">False</span></div>\n</div>\n\n</pre>\n                    </div>\n                </article>\n            </div>\n            <footer>\n\n                <div class=\"related-pages\">\n\n\n                </div>\n                <div class=\"bottom-of-page\">\n                    <div class=\"left-details\">\n                        <div class=\"copyright\">\n                            Copyright &#169; 2025, HenryXiaoYang\n                        </div>\n                        Made with <a href=\"https://www.sphinx-doc.org/\">Sphinx</a> and <a class=\"muted-link\"\n                                                                                          href=\"https://pradyunsg.me\">@pradyunsg</a>'s\n\n                        <a href=\"https://github.com/pradyunsg/furo\">Furo</a>\n\n                    </div>\n                    <div class=\"right-details\">\n\n                    </div>\n                </div>\n\n            </footer>\n        </div>\n        <aside class=\"toc-drawer no-toc\">\n\n\n        </aside>\n    </div>\n</div>\n<script src=\"../../../_static/documentation_options.js?v=91bfbbb6\"></script>\n<script src=\"../../../_static/doctools.js?v=9bcbadda\"></script>\n<script src=\"../../../_static/sphinx_highlight.js?v=dc90522c\"></script>\n<script src=\"../../../_static/scripts/furo.js?v=5fa4622c\"></script>\n<script src=\"../../../_static/translations.js?v=beaddf03\"></script>\n</body>\n</html>"
  },
  {
    "path": "docs/WechatAPIClient/_modules/index.html",
    "content": "<!doctype html>\n<html class=\"no-js\" data-content_root=\"../\" lang=\"zh-CN\">\n<head>\n    <meta charset=\"utf-8\"/>\n    <meta content=\"width=device-width,initial-scale=1\" name=\"viewport\"/>\n    <meta content=\"light dark\" name=\"color-scheme\">\n    <link href=\"../genindex.html\" rel=\"index\" title=\"索引\"/>\n    <link href=\"../search.html\" rel=\"search\" title=\"搜索\"/>\n\n    <!-- Generated with Sphinx 8.1.3 and Furo 2024.08.06 -->\n    <title>概览：模块代码 - XYBotV2</title>\n    <link href=\"../_static/pygments.css?v=8f2a1f02\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../_static/styles/furo.css?v=354aac6f\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"../_static/styles/furo-extensions.css?v=302659d7\" rel=\"stylesheet\" type=\"text/css\"/>\n\n\n    <style>\n        body {\n            --color-code-background: #f8f8f8;\n            --color-code-foreground: black;\n            --color-brand-primary: #2962ff;\n            --color-brand-content: #2962ff;\n\n        }\n\n        @media not print {\n            body[data-theme=\"dark\"] {\n                --color-code-background: #202020;\n                --color-code-foreground: #d0d0d0;\n\n            }\n\n            @media (prefers-color-scheme: dark) {\n                body:not([data-theme=\"light\"]) {\n                    --color-code-background: #202020;\n                    --color-code-foreground: #d0d0d0;\n\n                }\n            }\n        }\n    </style>\n</head>\n<body>\n\n<script>\n    document.body.dataset.theme = localStorage.getItem(\"theme\") || \"auto\";\n</script>\n\n\n<svg style=\"display: none;\" xmlns=\"http://www.w3.org/2000/svg\">\n    <symbol id=\"svg-toc\" viewBox=\"0 0 24 24\">\n        <title>Contents</title>\n        <svg fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 1024 1024\">\n            <path d=\"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-menu\" viewBox=\"0 0 24 24\">\n        <title>Menu</title>\n        <svg class=\"feather-menu\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-arrow-right\" viewBox=\"0 0 24 24\">\n        <title>Expand</title>\n        <svg class=\"feather-chevron-right\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <polyline points=\"9 18 15 12 9 6\"></polyline>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun\" viewBox=\"0 0 24 24\">\n        <title>Light mode</title>\n        <svg class=\"feather-sun\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\n            <line x1=\"12\" x2=\"12\" y1=\"1\" y2=\"3\"></line>\n            <line x1=\"12\" x2=\"12\" y1=\"21\" y2=\"23\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"4.22\" y2=\"5.64\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"18.36\" y2=\"19.78\"></line>\n            <line x1=\"1\" x2=\"3\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"21\" x2=\"23\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"19.78\" y2=\"18.36\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"5.64\" y2=\"4.22\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon\" viewBox=\"0 0 24 24\">\n        <title>Dark mode</title>\n        <svg class=\"icon-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun-with-moon\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in light mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 5.411 14.504 C 5.471 14.504 5.532 14.504 5.591 14.504 C 3.639 16.319 4.383 19.569 6.931 20.352 C 7.693 20.586 8.512 20.551 9.25 20.252 C 8.023 23.207 4.056 23.725 2.11 21.184 C 0.166 18.642 1.702 14.949 4.874 14.536 C 5.051 14.512 5.231 14.5 5.411 14.5 L 5.411 14.504 Z\"\n                  style=\"opacity: 50%\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"3.25\" y2=\"1.25\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"15.85\" y2=\"17.85\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"5.094\" y2=\"3.68\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"14.05\" y2=\"15.464\"/>\n            <line x1=\"8.2\" x2=\"6.2\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"20.8\" x2=\"22.8\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"14.006\" y2=\"15.42\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"5.05\" y2=\"3.636\"/>\n            <circle cx=\"14.5\" cy=\"9.55\" r=\"3.6\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon-with-sun\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in dark mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 8.282 7.007 C 8.385 7.007 8.494 7.007 8.595 7.007 C 5.18 10.184 6.481 15.869 10.942 17.24 C 12.275 17.648 13.706 17.589 15 17.066 C 12.851 22.236 5.91 23.143 2.505 18.696 C -0.897 14.249 1.791 7.786 7.342 7.063 C 7.652 7.021 7.965 7 8.282 7 L 8.282 7.007 Z\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"3.705\" y2=\"2.5\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"11.295\" y2=\"12.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"4.816\" y2=\"3.964\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"10.212\" y2=\"11.063\"/>\n            <line style=\"opacity: 50%\" x1=\"14.205\" x2=\"13.001\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"21.795\" x2=\"23\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"10.184\" y2=\"11.036\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"4.789\" y2=\"3.937\"/>\n            <circle cx=\"18\" cy=\"7.5\" r=\"2.169\" style=\"opacity: 50%\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-pencil\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-pencil-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4\"/>\n            <path d=\"M13.5 6.5l4 4\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-eye\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-eye-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\"/>\n            <path\n                    d=\"M11.11 17.958c-3.209 -.307 -5.91 -2.293 -8.11 -5.958c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6c-.21 .352 -.427 .688 -.647 1.008\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n</svg>\n\n<input class=\"sidebar-toggle\" id=\"__navigation\" name=\"__navigation\" type=\"checkbox\">\n<input class=\"sidebar-toggle\" id=\"__toc\" name=\"__toc\" type=\"checkbox\">\n<label class=\"overlay sidebar-overlay\" for=\"__navigation\">\n    <div class=\"visually-hidden\">Hide navigation sidebar</div>\n</label>\n<label class=\"overlay toc-overlay\" for=\"__toc\">\n    <div class=\"visually-hidden\">Hide table of contents sidebar</div>\n</label>\n\n<a class=\"skip-to-content muted-link\" href=\"#furo-main-content\">Skip to content</a>\n\n\n\n<div class=\"page\">\n    <header class=\"mobile-header\">\n        <div class=\"header-left\">\n            <label class=\"nav-overlay-icon\" for=\"__navigation\">\n                <div class=\"visually-hidden\">Toggle site navigation sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-menu\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n        <div class=\"header-center\">\n            <a href=\"../index.html\">\n                <div class=\"brand\">XYBotV2</div>\n            </a>\n        </div>\n        <div class=\"header-right\">\n            <div class=\"theme-toggle-container theme-toggle-header\">\n                <button class=\"theme-toggle\">\n                    <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                    <svg class=\"theme-icon-when-auto-light\">\n                        <use href=\"#svg-sun-with-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-auto-dark\">\n                        <use href=\"#svg-moon-with-sun\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-dark\">\n                        <use href=\"#svg-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-light\">\n                        <use href=\"#svg-sun\"></use>\n                    </svg>\n                </button>\n            </div>\n            <label class=\"toc-overlay-icon toc-header-icon no-toc\" for=\"__toc\">\n                <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-toc\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n    </header>\n    <aside class=\"sidebar-drawer\">\n        <div class=\"sidebar-container\">\n\n            <div class=\"sidebar-sticky\">\n                <div class=\"sidebar-scroll\"><a class=\"sidebar-brand\" href=\"../index.html\">\n\n\n                    <span class=\"sidebar-brand-text\">XYBotV2</span>\n\n                </a>\n                    <form action=\"../search.html\" class=\"sidebar-search-container\" method=\"get\" role=\"search\">\n                        <input aria-label=\"搜索\" class=\"sidebar-search\" name=\"q\" placeholder=\"搜索\">\n                        <input name=\"check_keywords\" type=\"hidden\" value=\"yes\">\n                        <input name=\"area\" type=\"hidden\" value=\"default\">\n                    </form>\n                    <div id=\"searchbox\"></div>\n                    <div class=\"sidebar-tree\">\n\n                    </div>\n                    <div class=\"sidebar-tree\">\n                        <p class=\"caption\"><span class=\"caption-text\">重要函数导航</span></p>\n                        <ul>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">登录</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.is_running\">检查WechatAPI是否在运行</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_qr_code\">获取登录二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.awaken_login\">二次登录(唤醒登录)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.check_login_uuid\">检查登录的UUID状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_cached_info\">获取登录缓存信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.log_out\">登出当前账号</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.heartbeat\">发送心跳包</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\">开始自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\">停止自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\">获取自动心跳状态</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">消息</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.sync_message\">同步消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_text_message\">发送文本消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_image_message\">发送图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_voice_message\">发送语音消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_video_message\">发送视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_link_message\">发送链接消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_card_message\">发送名片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_app_message\">发送应用(xml)消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_emoji_message\">发送表情消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\">转发图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\">转发视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\">转发文件消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.revoke_message\">撤回消息</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">用户</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_my_qrcode\">获取个人二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_profile\">获取用户信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.is_logged_in\">检查是否登录</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">群聊</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\">获取群聊信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\">获取群聊公告</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\">获取群聊成员列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\">获取群聊二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\">添加群成员(群聊最多40人)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\">邀请群聊成员(群聊大于40人)</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">好友</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contact\">获取联系人信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_detail\">获取联系人详情</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_list\">获取联系人列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_nickname\">获取用户昵称</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.accept_friend\">接受好友请求</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">红包</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\">获取红包详情</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">工具</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.check_database\">检查数据库状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.set_step\">设置步数</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_image\">下载高清图片</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_video\">下载视频</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_voice\">下载语音文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_attach\">下载附件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_byte\">base64转字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_file\">base64转文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.byte_to_base64\">字节转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.file_to_base64\">文件转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\">silk的base64转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\">silk字节转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\">WAV字节转AMR的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\">WAV字节转AMR字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\">WAV字节转silk的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\">WAV字节转silk字节</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                        </ul>\n                    </div>\n                </div>\n            </div>\n\n        </div>\n    </aside>\n    <div class=\"main\">\n        <div class=\"content\">\n            <div class=\"article-container\">\n                <a class=\"back-to-top muted-link\" href=\"#\">\n                    <svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n                        <path d=\"M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z\"></path>\n                    </svg>\n                    <span>Back to top</span>\n                </a>\n                <div class=\"content-icon-container\">\n                    <div class=\"theme-toggle-container theme-toggle-content\">\n                        <button class=\"theme-toggle\">\n                            <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                            <svg class=\"theme-icon-when-auto-light\">\n                                <use href=\"#svg-sun-with-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-auto-dark\">\n                                <use href=\"#svg-moon-with-sun\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-dark\">\n                                <use href=\"#svg-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-light\">\n                                <use href=\"#svg-sun\"></use>\n                            </svg>\n                        </button>\n                    </div>\n                    <label class=\"toc-overlay-icon toc-content-icon no-toc\" for=\"__toc\">\n                        <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                        <i class=\"icon\">\n                            <svg>\n                                <use href=\"#svg-toc\"></use>\n                            </svg>\n                        </i>\n                    </label>\n                </div>\n                <article id=\"furo-main-content\" role=\"main\">\n                    <h1>代码可用的所有模块</h1>\n                    <ul>\n                        <li><a href=\"WechatAPI/Client/base.html\">WechatAPI.Client.base</a></li>\n                        <li><a href=\"WechatAPI/Client/chatroom.html\">WechatAPI.Client.chatroom</a></li>\n                        <li><a href=\"WechatAPI/Client/friend.html\">WechatAPI.Client.friend</a></li>\n                        <li><a href=\"WechatAPI/Client/hongbao.html\">WechatAPI.Client.hongbao</a></li>\n                        <li><a href=\"WechatAPI/Client/login.html\">WechatAPI.Client.login</a></li>\n                        <li><a href=\"WechatAPI/Client/message.html\">WechatAPI.Client.message</a></li>\n                        <li><a href=\"WechatAPI/Client/protect.html\">WechatAPI.Client.protect</a></li>\n                        <li><a href=\"WechatAPI/Client/tool.html\">WechatAPI.Client.tool</a></li>\n                        <li><a href=\"WechatAPI/Client/user.html\">WechatAPI.Client.user</a></li>\n                    </ul>\n                </article>\n            </div>\n            <footer>\n\n                <div class=\"related-pages\">\n\n\n                </div>\n                <div class=\"bottom-of-page\">\n                    <div class=\"left-details\">\n                        <div class=\"copyright\">\n                            Copyright &#169; 2025, HenryXiaoYang\n                        </div>\n                        Made with <a href=\"https://www.sphinx-doc.org/\">Sphinx</a> and <a class=\"muted-link\"\n                                                                                          href=\"https://pradyunsg.me\">@pradyunsg</a>'s\n\n                        <a href=\"https://github.com/pradyunsg/furo\">Furo</a>\n\n                    </div>\n                    <div class=\"right-details\">\n\n                    </div>\n                </div>\n\n            </footer>\n        </div>\n        <aside class=\"toc-drawer no-toc\">\n\n\n        </aside>\n    </div>\n</div>\n<script src=\"../_static/documentation_options.js?v=91bfbbb6\"></script>\n<script src=\"../_static/doctools.js?v=9bcbadda\"></script>\n<script src=\"../_static/sphinx_highlight.js?v=dc90522c\"></script>\n<script src=\"../_static/scripts/furo.js?v=5fa4622c\"></script>\n<script src=\"../_static/translations.js?v=beaddf03\"></script>\n</body>\n</html>"
  },
  {
    "path": "docs/WechatAPIClient/_sources/index.rst.txt",
    "content": "WechatAPIClient\n------------------------------\n\n基础\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.base\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n登录\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.login\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n消息\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.message\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n用户\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.user\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n群聊\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.chatroom\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n好友\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.friend\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n红包\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.hongbao\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n保护\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.protect\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n工具\n~~~~~~~\n\n.. automodule:: WechatAPI.Client.tool\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n索引\n====\n\n* :ref:`search`\n\n\n"
  },
  {
    "path": "docs/WechatAPIClient/_static/basic.css",
    "content": "/*\n * Sphinx stylesheet -- basic theme.\n */\n\n/* -- main layout ----------------------------------------------------------- */\n\ndiv.clearer {\n    clear: both;\n}\n\ndiv.section::after {\n    display: block;\n    content: '';\n    clear: left;\n}\n\n/* -- relbar ---------------------------------------------------------------- */\n\ndiv.related {\n    width: 100%;\n    font-size: 90%;\n}\n\ndiv.related h3 {\n    display: none;\n}\n\ndiv.related ul {\n    margin: 0;\n    padding: 0 0 0 10px;\n    list-style: none;\n}\n\ndiv.related li {\n    display: inline;\n}\n\ndiv.related li.right {\n    float: right;\n    margin-right: 5px;\n}\n\n/* -- sidebar --------------------------------------------------------------- */\n\ndiv.sphinxsidebarwrapper {\n    padding: 10px 5px 0 10px;\n}\n\ndiv.sphinxsidebar {\n    float: left;\n    width: 230px;\n    margin-left: -100%;\n    font-size: 90%;\n    word-wrap: break-word;\n    overflow-wrap: break-word;\n}\n\ndiv.sphinxsidebar ul {\n    list-style: none;\n}\n\ndiv.sphinxsidebar ul ul,\ndiv.sphinxsidebar ul.want-points {\n    margin-left: 20px;\n    list-style: square;\n}\n\ndiv.sphinxsidebar ul ul {\n    margin-top: 0;\n    margin-bottom: 0;\n}\n\ndiv.sphinxsidebar form {\n    margin-top: 10px;\n}\n\ndiv.sphinxsidebar input {\n    border: 1px solid #98dbcc;\n    font-family: sans-serif;\n    font-size: 1em;\n}\n\ndiv.sphinxsidebar #searchbox form.search {\n    overflow: hidden;\n}\n\ndiv.sphinxsidebar #searchbox input[type=\"text\"] {\n    float: left;\n    width: 80%;\n    padding: 0.25em;\n    box-sizing: border-box;\n}\n\ndiv.sphinxsidebar #searchbox input[type=\"submit\"] {\n    float: left;\n    width: 20%;\n    border-left: none;\n    padding: 0.25em;\n    box-sizing: border-box;\n}\n\n\nimg {\n    border: 0;\n    max-width: 100%;\n}\n\n/* -- search page ----------------------------------------------------------- */\n\nul.search {\n    margin-top: 10px;\n}\n\nul.search li {\n    padding: 5px 0;\n}\n\nul.search li a {\n    font-weight: bold;\n}\n\nul.search li p.context {\n    color: #888;\n    margin: 2px 0 0 30px;\n    text-align: left;\n}\n\nul.keywordmatches li.goodmatch a {\n    font-weight: bold;\n}\n\n/* -- index page ------------------------------------------------------------ */\n\ntable.contentstable {\n    width: 90%;\n    margin-left: auto;\n    margin-right: auto;\n}\n\ntable.contentstable p.biglink {\n    line-height: 150%;\n}\n\na.biglink {\n    font-size: 1.3em;\n}\n\nspan.linkdescr {\n    font-style: italic;\n    padding-top: 5px;\n    font-size: 90%;\n}\n\n/* -- general index --------------------------------------------------------- */\n\ntable.indextable {\n    width: 100%;\n}\n\ntable.indextable td {\n    text-align: left;\n    vertical-align: top;\n}\n\ntable.indextable ul {\n    margin-top: 0;\n    margin-bottom: 0;\n    list-style-type: none;\n}\n\ntable.indextable > tbody > tr > td > ul {\n    padding-left: 0em;\n}\n\ntable.indextable tr.pcap {\n    height: 10px;\n}\n\ntable.indextable tr.cap {\n    margin-top: 10px;\n    background-color: #f2f2f2;\n}\n\nimg.toggler {\n    margin-right: 3px;\n    margin-top: 3px;\n    cursor: pointer;\n}\n\ndiv.modindex-jumpbox {\n    border-top: 1px solid #ddd;\n    border-bottom: 1px solid #ddd;\n    margin: 1em 0 1em 0;\n    padding: 0.4em;\n}\n\ndiv.genindex-jumpbox {\n    border-top: 1px solid #ddd;\n    border-bottom: 1px solid #ddd;\n    margin: 1em 0 1em 0;\n    padding: 0.4em;\n}\n\n/* -- domain module index --------------------------------------------------- */\n\ntable.modindextable td {\n    padding: 2px;\n    border-collapse: collapse;\n}\n\n/* -- general body styles --------------------------------------------------- */\n\ndiv.body {\n    min-width: 360px;\n    max-width: 800px;\n}\n\ndiv.body p, div.body dd, div.body li, div.body blockquote {\n    -moz-hyphens: auto;\n    -ms-hyphens: auto;\n    -webkit-hyphens: auto;\n    hyphens: auto;\n}\n\na.headerlink {\n    visibility: hidden;\n}\n\na:visited {\n    color: #551A8B;\n}\n\nh1:hover > a.headerlink,\nh2:hover > a.headerlink,\nh3:hover > a.headerlink,\nh4:hover > a.headerlink,\nh5:hover > a.headerlink,\nh6:hover > a.headerlink,\ndt:hover > a.headerlink,\ncaption:hover > a.headerlink,\np.caption:hover > a.headerlink,\ndiv.code-block-caption:hover > a.headerlink {\n    visibility: visible;\n}\n\ndiv.body p.caption {\n    text-align: inherit;\n}\n\ndiv.body td {\n    text-align: left;\n}\n\n.first {\n    margin-top: 0 !important;\n}\n\np.rubric {\n    margin-top: 30px;\n    font-weight: bold;\n}\n\nimg.align-left, figure.align-left, .figure.align-left, object.align-left {\n    clear: left;\n    float: left;\n    margin-right: 1em;\n}\n\nimg.align-right, figure.align-right, .figure.align-right, object.align-right {\n    clear: right;\n    float: right;\n    margin-left: 1em;\n}\n\nimg.align-center, figure.align-center, .figure.align-center, object.align-center {\n    display: block;\n    margin-left: auto;\n    margin-right: auto;\n}\n\nimg.align-default, figure.align-default, .figure.align-default {\n    display: block;\n    margin-left: auto;\n    margin-right: auto;\n}\n\n.align-left {\n    text-align: left;\n}\n\n.align-center {\n    text-align: center;\n}\n\n.align-default {\n    text-align: center;\n}\n\n.align-right {\n    text-align: right;\n}\n\n/* -- sidebars -------------------------------------------------------------- */\n\ndiv.sidebar,\naside.sidebar {\n    margin: 0 0 0.5em 1em;\n    border: 1px solid #ddb;\n    padding: 7px;\n    background-color: #ffe;\n    width: 40%;\n    float: right;\n    clear: right;\n    overflow-x: auto;\n}\n\np.sidebar-title {\n    font-weight: bold;\n}\n\nnav.contents,\naside.topic,\ndiv.admonition, div.topic, blockquote {\n    clear: left;\n}\n\n/* -- topics ---------------------------------------------------------------- */\n\nnav.contents,\naside.topic,\ndiv.topic {\n    border: 1px solid #ccc;\n    padding: 7px;\n    margin: 10px 0 10px 0;\n}\n\np.topic-title {\n    font-size: 1.1em;\n    font-weight: bold;\n    margin-top: 10px;\n}\n\n/* -- admonitions ----------------------------------------------------------- */\n\ndiv.admonition {\n    margin-top: 10px;\n    margin-bottom: 10px;\n    padding: 7px;\n}\n\ndiv.admonition dt {\n    font-weight: bold;\n}\n\np.admonition-title {\n    margin: 0px 10px 5px 0px;\n    font-weight: bold;\n}\n\ndiv.body p.centered {\n    text-align: center;\n    margin-top: 25px;\n}\n\n/* -- content of sidebars/topics/admonitions -------------------------------- */\n\ndiv.sidebar > :last-child,\naside.sidebar > :last-child,\nnav.contents > :last-child,\naside.topic > :last-child,\ndiv.topic > :last-child,\ndiv.admonition > :last-child {\n    margin-bottom: 0;\n}\n\ndiv.sidebar::after,\naside.sidebar::after,\nnav.contents::after,\naside.topic::after,\ndiv.topic::after,\ndiv.admonition::after,\nblockquote::after {\n    display: block;\n    content: '';\n    clear: both;\n}\n\n/* -- tables ---------------------------------------------------------------- */\n\ntable.docutils {\n    margin-top: 10px;\n    margin-bottom: 10px;\n    border: 0;\n    border-collapse: collapse;\n}\n\ntable.align-center {\n    margin-left: auto;\n    margin-right: auto;\n}\n\ntable.align-default {\n    margin-left: auto;\n    margin-right: auto;\n}\n\ntable caption span.caption-number {\n    font-style: italic;\n}\n\ntable caption span.caption-text {\n}\n\ntable.docutils td, table.docutils th {\n    padding: 1px 8px 1px 5px;\n    border-top: 0;\n    border-left: 0;\n    border-right: 0;\n    border-bottom: 1px solid #aaa;\n}\n\nth {\n    text-align: left;\n    padding-right: 5px;\n}\n\ntable.citation {\n    border-left: solid 1px gray;\n    margin-left: 1px;\n}\n\ntable.citation td {\n    border-bottom: none;\n}\n\nth > :first-child,\ntd > :first-child {\n    margin-top: 0px;\n}\n\nth > :last-child,\ntd > :last-child {\n    margin-bottom: 0px;\n}\n\n/* -- figures --------------------------------------------------------------- */\n\ndiv.figure, figure {\n    margin: 0.5em;\n    padding: 0.5em;\n}\n\ndiv.figure p.caption, figcaption {\n    padding: 0.3em;\n}\n\ndiv.figure p.caption span.caption-number,\nfigcaption span.caption-number {\n    font-style: italic;\n}\n\ndiv.figure p.caption span.caption-text,\nfigcaption span.caption-text {\n}\n\n/* -- field list styles ----------------------------------------------------- */\n\ntable.field-list td, table.field-list th {\n    border: 0 !important;\n}\n\n.field-list ul {\n    margin: 0;\n    padding-left: 1em;\n}\n\n.field-list p {\n    margin: 0;\n}\n\n.field-name {\n    -moz-hyphens: manual;\n    -ms-hyphens: manual;\n    -webkit-hyphens: manual;\n    hyphens: manual;\n}\n\n/* -- hlist styles ---------------------------------------------------------- */\n\ntable.hlist {\n    margin: 1em 0;\n}\n\ntable.hlist td {\n    vertical-align: top;\n}\n\n/* -- object description styles --------------------------------------------- */\n\n.sig {\n    font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;\n}\n\n.sig-name, code.descname {\n    background-color: transparent;\n    font-weight: bold;\n}\n\n.sig-name {\n    font-size: 1.1em;\n}\n\ncode.descname {\n    font-size: 1.2em;\n}\n\n.sig-prename, code.descclassname {\n    background-color: transparent;\n}\n\n.optional {\n    font-size: 1.3em;\n}\n\n.sig-paren {\n    font-size: larger;\n}\n\n.sig-param.n {\n    font-style: italic;\n}\n\n/* C++ specific styling */\n\n.sig-inline.c-texpr,\n.sig-inline.cpp-texpr {\n    font-family: unset;\n}\n\n.sig.c .k, .sig.c .kt,\n.sig.cpp .k, .sig.cpp .kt {\n    color: #0033B3;\n}\n\n.sig.c .m,\n.sig.cpp .m {\n    color: #1750EB;\n}\n\n.sig.c .s, .sig.c .sc,\n.sig.cpp .s, .sig.cpp .sc {\n    color: #067D17;\n}\n\n\n/* -- other body styles ----------------------------------------------------- */\n\nol.arabic {\n    list-style: decimal;\n}\n\nol.loweralpha {\n    list-style: lower-alpha;\n}\n\nol.upperalpha {\n    list-style: upper-alpha;\n}\n\nol.lowerroman {\n    list-style: lower-roman;\n}\n\nol.upperroman {\n    list-style: upper-roman;\n}\n\n:not(li) > ol > li:first-child > :first-child,\n:not(li) > ul > li:first-child > :first-child {\n    margin-top: 0px;\n}\n\n:not(li) > ol > li:last-child > :last-child,\n:not(li) > ul > li:last-child > :last-child {\n    margin-bottom: 0px;\n}\n\nol.simple ol p,\nol.simple ul p,\nul.simple ol p,\nul.simple ul p {\n    margin-top: 0;\n}\n\nol.simple > li:not(:first-child) > p,\nul.simple > li:not(:first-child) > p {\n    margin-top: 0;\n}\n\nol.simple p,\nul.simple p {\n    margin-bottom: 0;\n}\n\naside.footnote > span,\ndiv.citation > span {\n    float: left;\n}\naside.footnote > span:last-of-type,\ndiv.citation > span:last-of-type {\n    padding-right: 0.5em;\n}\naside.footnote > p {\n    margin-left: 2em;\n}\ndiv.citation > p {\n    margin-left: 4em;\n}\naside.footnote > p:last-of-type,\ndiv.citation > p:last-of-type {\n    margin-bottom: 0em;\n}\naside.footnote > p:last-of-type:after,\ndiv.citation > p:last-of-type:after {\n    content: \"\";\n    clear: both;\n}\n\ndl.field-list {\n    display: grid;\n    grid-template-columns: fit-content(30%) auto;\n}\n\ndl.field-list > dt {\n    font-weight: bold;\n    word-break: break-word;\n    padding-left: 0.5em;\n    padding-right: 5px;\n}\n\ndl.field-list > dd {\n    padding-left: 0.5em;\n    margin-top: 0em;\n    margin-left: 0em;\n    margin-bottom: 0em;\n}\n\ndl {\n    margin-bottom: 15px;\n}\n\ndd > :first-child {\n    margin-top: 0px;\n}\n\ndd ul, dd table {\n    margin-bottom: 10px;\n}\n\ndd {\n    margin-top: 3px;\n    margin-bottom: 10px;\n    margin-left: 30px;\n}\n\n.sig dd {\n    margin-top: 0px;\n    margin-bottom: 0px;\n}\n\n.sig dl {\n    margin-top: 0px;\n    margin-bottom: 0px;\n}\n\ndl > dd:last-child,\ndl > dd:last-child > :last-child {\n    margin-bottom: 0;\n}\n\ndt:target, span.highlighted {\n    background-color: #fbe54e;\n}\n\nrect.highlighted {\n    fill: #fbe54e;\n}\n\ndl.glossary dt {\n    font-weight: bold;\n    font-size: 1.1em;\n}\n\n.versionmodified {\n    font-style: italic;\n}\n\n.system-message {\n    background-color: #fda;\n    padding: 5px;\n    border: 3px solid red;\n}\n\n.footnote:target {\n    background-color: #ffa;\n}\n\n.line-block {\n    display: block;\n    margin-top: 1em;\n    margin-bottom: 1em;\n}\n\n.line-block .line-block {\n    margin-top: 0;\n    margin-bottom: 0;\n    margin-left: 1.5em;\n}\n\n.guilabel, .menuselection {\n    font-family: sans-serif;\n}\n\n.accelerator {\n    text-decoration: underline;\n}\n\n.classifier {\n    font-style: oblique;\n}\n\n.classifier:before {\n    font-style: normal;\n    margin: 0 0.5em;\n    content: \":\";\n    display: inline-block;\n}\n\nabbr, acronym {\n    border-bottom: dotted 1px;\n    cursor: help;\n}\n\n.translated {\n    background-color: rgba(207, 255, 207, 0.2)\n}\n\n.untranslated {\n    background-color: rgba(255, 207, 207, 0.2)\n}\n\n/* -- code displays --------------------------------------------------------- */\n\npre {\n    overflow: auto;\n    overflow-y: hidden; /* fixes display issues on Chrome browsers */\n}\n\npre, div[class*=\"highlight-\"] {\n    clear: both;\n}\n\nspan.pre {\n    -moz-hyphens: none;\n    -ms-hyphens: none;\n    -webkit-hyphens: none;\n    hyphens: none;\n    white-space: nowrap;\n}\n\ndiv[class*=\"highlight-\"] {\n    margin: 1em 0;\n}\n\ntd.linenos pre {\n    border: 0;\n    background-color: transparent;\n    color: #aaa;\n}\n\ntable.highlighttable {\n    display: block;\n}\n\ntable.highlighttable tbody {\n    display: block;\n}\n\ntable.highlighttable tr {\n    display: flex;\n}\n\ntable.highlighttable td {\n    margin: 0;\n    padding: 0;\n}\n\ntable.highlighttable td.linenos {\n    padding-right: 0.5em;\n}\n\ntable.highlighttable td.code {\n    flex: 1;\n    overflow: hidden;\n}\n\n.highlight .hll {\n    display: block;\n}\n\ndiv.highlight pre,\ntable.highlighttable pre {\n    margin: 0;\n}\n\ndiv.code-block-caption + div {\n    margin-top: 0;\n}\n\ndiv.code-block-caption {\n    margin-top: 1em;\n    padding: 2px 5px;\n    font-size: small;\n}\n\ndiv.code-block-caption code {\n    background-color: transparent;\n}\n\ntable.highlighttable td.linenos,\nspan.linenos,\ndiv.highlight span.gp { /* gp: Generic.Prompt */\n    user-select: none;\n    -webkit-user-select: text; /* Safari fallback only */\n    -webkit-user-select: none; /* Chrome/Safari */\n    -moz-user-select: none; /* Firefox */\n    -ms-user-select: none; /* IE10+ */\n}\n\ndiv.code-block-caption span.caption-number {\n    padding: 0.1em 0.3em;\n    font-style: italic;\n}\n\ndiv.code-block-caption span.caption-text {\n}\n\ndiv.literal-block-wrapper {\n    margin: 1em 0;\n}\n\ncode.xref, a code {\n    background-color: transparent;\n    font-weight: bold;\n}\n\nh1 code, h2 code, h3 code, h4 code, h5 code, h6 code {\n    background-color: transparent;\n}\n\n.viewcode-link {\n    float: right;\n}\n\n.viewcode-back {\n    float: right;\n    font-family: sans-serif;\n}\n\ndiv.viewcode-block:target {\n    margin: -1px -10px;\n    padding: 0 10px;\n}\n\n/* -- math display ---------------------------------------------------------- */\n\nimg.math {\n    vertical-align: middle;\n}\n\ndiv.body div.math p {\n    text-align: center;\n}\n\nspan.eqno {\n    float: right;\n}\n\nspan.eqno a.headerlink {\n    position: absolute;\n    z-index: 1;\n}\n\ndiv.math:hover a.headerlink {\n    visibility: visible;\n}\n\n/* -- printout stylesheet --------------------------------------------------- */\n\n@media print {\n    div.document,\n    div.documentwrapper,\n    div.bodywrapper {\n        margin: 0 !important;\n        width: 100%;\n    }\n\n    div.sphinxsidebar,\n    div.related,\n    div.footer,\n    #top-link {\n        display: none;\n    }\n}"
  },
  {
    "path": "docs/WechatAPIClient/_static/debug.css",
    "content": "/*\n  This CSS file should be overridden by the theme authors. It's\n  meant for debugging and developing the skeleton that this theme provides.\n*/\nbody {\n    font-family: -apple-system, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif,\n    \"Apple Color Emoji\", \"Segoe UI Emoji\";\n    background: lavender;\n}\n.sb-announcement {\n    background: rgb(131, 131, 131);\n}\n.sb-announcement__inner {\n    background: black;\n    color: white;\n}\n.sb-header {\n    background: lightskyblue;\n}\n.sb-header__inner {\n    background: royalblue;\n    color: white;\n}\n.sb-header-secondary {\n    background: lightcyan;\n}\n.sb-header-secondary__inner {\n    background: cornflowerblue;\n    color: white;\n}\n.sb-sidebar-primary {\n    background: lightgreen;\n}\n.sb-main {\n    background: blanchedalmond;\n}\n.sb-main__inner {\n    background: antiquewhite;\n}\n.sb-header-article {\n    background: lightsteelblue;\n}\n.sb-article-container {\n    background: snow;\n}\n.sb-article-main {\n    background: white;\n}\n.sb-footer-article {\n    background: lightpink;\n}\n.sb-sidebar-secondary {\n    background: lightgoldenrodyellow;\n}\n.sb-footer-content {\n    background: plum;\n}\n.sb-footer-content__inner {\n    background: palevioletred;\n}\n.sb-footer {\n    background: pink;\n}\n.sb-footer__inner {\n    background: salmon;\n}\n.sb-article {\n    background: white;\n}\n"
  },
  {
    "path": "docs/WechatAPIClient/_static/doctools.js",
    "content": "/*\n * Base JavaScript utilities for all Sphinx HTML documentation.\n */\n\"use strict\";\n\nconst BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([\n    \"TEXTAREA\",\n    \"INPUT\",\n    \"SELECT\",\n    \"BUTTON\",\n]);\n\nconst _ready = (callback) => {\n    if (document.readyState !== \"loading\") {\n        callback();\n    } else {\n        document.addEventListener(\"DOMContentLoaded\", callback);\n    }\n};\n\n/**\n * Small JavaScript module for the documentation.\n */\nconst Documentation = {\n    init: () => {\n        Documentation.initDomainIndexTable();\n        Documentation.initOnKeyListeners();\n    },\n\n    /**\n     * i18n support\n     */\n    TRANSLATIONS: {},\n    PLURAL_EXPR: (n) => (n === 1 ? 0 : 1),\n    LOCALE: \"unknown\",\n\n    // gettext and ngettext don't access this so that the functions\n    // can safely bound to a different name (_ = Documentation.gettext)\n    gettext: (string) => {\n        const translated = Documentation.TRANSLATIONS[string];\n        switch (typeof translated) {\n            case \"undefined\":\n                return string; // no translation\n            case \"string\":\n                return translated; // translation exists\n            default:\n                return translated[0]; // (singular, plural) translation tuple exists\n        }\n    },\n\n    ngettext: (singular, plural, n) => {\n        const translated = Documentation.TRANSLATIONS[singular];\n        if (typeof translated !== \"undefined\")\n            return translated[Documentation.PLURAL_EXPR(n)];\n        return n === 1 ? singular : plural;\n    },\n\n    addTranslations: (catalog) => {\n        Object.assign(Documentation.TRANSLATIONS, catalog.messages);\n        Documentation.PLURAL_EXPR = new Function(\n            \"n\",\n            `return (${catalog.plural_expr})`\n        );\n        Documentation.LOCALE = catalog.locale;\n    },\n\n    /**\n     * helper function to focus on search bar\n     */\n    focusSearchBar: () => {\n        document.querySelectorAll(\"input[name=q]\")[0]?.focus();\n    },\n\n    /**\n     * Initialise the domain index toggle buttons\n     */\n    initDomainIndexTable: () => {\n        const toggler = (el) => {\n            const idNumber = el.id.substr(7);\n            const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`);\n            if (el.src.substr(-9) === \"minus.png\") {\n                el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`;\n                toggledRows.forEach((el) => (el.style.display = \"none\"));\n            } else {\n                el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`;\n                toggledRows.forEach((el) => (el.style.display = \"\"));\n            }\n        };\n\n        const togglerElements = document.querySelectorAll(\"img.toggler\");\n        togglerElements.forEach((el) =>\n            el.addEventListener(\"click\", (event) => toggler(event.currentTarget))\n        );\n        togglerElements.forEach((el) => (el.style.display = \"\"));\n        if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler);\n    },\n\n    initOnKeyListeners: () => {\n        // only install a listener if it is really needed\n        if (\n            !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS &&\n            !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS\n        )\n            return;\n\n        document.addEventListener(\"keydown\", (event) => {\n            // bail for input elements\n            if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;\n            // bail with special keys\n            if (event.altKey || event.ctrlKey || event.metaKey) return;\n\n            if (!event.shiftKey) {\n                switch (event.key) {\n                    case \"ArrowLeft\":\n                        if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;\n\n                        const prevLink = document.querySelector('link[rel=\"prev\"]');\n                        if (prevLink && prevLink.href) {\n                            window.location.href = prevLink.href;\n                            event.preventDefault();\n                        }\n                        break;\n                    case \"ArrowRight\":\n                        if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;\n\n                        const nextLink = document.querySelector('link[rel=\"next\"]');\n                        if (nextLink && nextLink.href) {\n                            window.location.href = nextLink.href;\n                            event.preventDefault();\n                        }\n                        break;\n                }\n            }\n\n            // some keyboard layouts may need Shift to get /\n            switch (event.key) {\n                case \"/\":\n                    if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break;\n                    Documentation.focusSearchBar();\n                    event.preventDefault();\n            }\n        });\n    },\n};\n\n// quick alias for translations\nconst _ = Documentation.gettext;\n\n_ready(Documentation.init);\n"
  },
  {
    "path": "docs/WechatAPIClient/_static/documentation_options.js",
    "content": "const DOCUMENTATION_OPTIONS = {\n    VERSION: '',\n    LANGUAGE: 'zh-CN',\n    COLLAPSE_INDEX: false,\n    BUILDER: 'html',\n    FILE_SUFFIX: '.html',\n    LINK_SUFFIX: '.html',\n    HAS_SOURCE: true,\n    SOURCELINK_SUFFIX: '.txt',\n    NAVIGATION_WITH_KEYS: true,\n    SHOW_SEARCH_SUMMARY: true,\n    ENABLE_SEARCH_SHORTCUTS: true,\n};"
  },
  {
    "path": "docs/WechatAPIClient/_static/language_data.js",
    "content": "/*\n * This script contains the language-specific data used by searchtools.js,\n * namely the list of stopwords, stemmer, scorer and splitter.\n */\n\nvar stopwords = [\"a\", \"and\", \"are\", \"as\", \"at\", \"be\", \"but\", \"by\", \"for\", \"if\", \"in\", \"into\", \"is\", \"it\", \"near\", \"no\", \"not\", \"of\", \"on\", \"or\", \"such\", \"that\", \"the\", \"their\", \"then\", \"there\", \"these\", \"they\", \"this\", \"to\", \"was\", \"will\", \"with\"];\n\n\n/* Non-minified version is copied as a separate JS file, if available */\n\n/**\n * Porter Stemmer\n */\nvar Stemmer = function () {\n\n    var step2list = {\n        ational: 'ate',\n        tional: 'tion',\n        enci: 'ence',\n        anci: 'ance',\n        izer: 'ize',\n        bli: 'ble',\n        alli: 'al',\n        entli: 'ent',\n        eli: 'e',\n        ousli: 'ous',\n        ization: 'ize',\n        ation: 'ate',\n        ator: 'ate',\n        alism: 'al',\n        iveness: 'ive',\n        fulness: 'ful',\n        ousness: 'ous',\n        aliti: 'al',\n        iviti: 'ive',\n        biliti: 'ble',\n        logi: 'log'\n    };\n\n    var step3list = {\n        icate: 'ic',\n        ative: '',\n        alize: 'al',\n        iciti: 'ic',\n        ical: 'ic',\n        ful: '',\n        ness: ''\n    };\n\n    var c = \"[^aeiou]\";          // consonant\n    var v = \"[aeiouy]\";          // vowel\n    var C = c + \"[^aeiouy]*\";    // consonant sequence\n    var V = v + \"[aeiou]*\";      // vowel sequence\n\n    var mgr0 = \"^(\" + C + \")?\" + V + C;                      // [C]VC... is m>0\n    var meq1 = \"^(\" + C + \")?\" + V + C + \"(\" + V + \")?$\";    // [C]VC[V] is m=1\n    var mgr1 = \"^(\" + C + \")?\" + V + C + V + C;              // [C]VCVC... is m>1\n    var s_v = \"^(\" + C + \")?\" + v;                         // vowel in stem\n\n    this.stemWord = function (w) {\n        var stem;\n        var suffix;\n        var firstch;\n        var origword = w;\n\n        if (w.length < 3)\n            return w;\n\n        var re;\n        var re2;\n        var re3;\n        var re4;\n\n        firstch = w.substr(0, 1);\n        if (firstch == \"y\")\n            w = firstch.toUpperCase() + w.substr(1);\n\n        // Step 1a\n        re = /^(.+?)(ss|i)es$/;\n        re2 = /^(.+?)([^s])s$/;\n\n        if (re.test(w))\n            w = w.replace(re, \"$1$2\");\n        else if (re2.test(w))\n            w = w.replace(re2, \"$1$2\");\n\n        // Step 1b\n        re = /^(.+?)eed$/;\n        re2 = /^(.+?)(ed|ing)$/;\n        if (re.test(w)) {\n            var fp = re.exec(w);\n            re = new RegExp(mgr0);\n            if (re.test(fp[1])) {\n                re = /.$/;\n                w = w.replace(re, \"\");\n            }\n        } else if (re2.test(w)) {\n            var fp = re2.exec(w);\n            stem = fp[1];\n            re2 = new RegExp(s_v);\n            if (re2.test(stem)) {\n                w = stem;\n                re2 = /(at|bl|iz)$/;\n                re3 = new RegExp(\"([^aeiouylsz])\\\\1$\");\n                re4 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n                if (re2.test(w))\n                    w = w + \"e\";\n                else if (re3.test(w)) {\n                    re = /.$/;\n                    w = w.replace(re, \"\");\n                } else if (re4.test(w))\n                    w = w + \"e\";\n            }\n        }\n\n        // Step 1c\n        re = /^(.+?)y$/;\n        if (re.test(w)) {\n            var fp = re.exec(w);\n            stem = fp[1];\n            re = new RegExp(s_v);\n            if (re.test(stem))\n                w = stem + \"i\";\n        }\n\n        // Step 2\n        re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;\n        if (re.test(w)) {\n            var fp = re.exec(w);\n            stem = fp[1];\n            suffix = fp[2];\n            re = new RegExp(mgr0);\n            if (re.test(stem))\n                w = stem + step2list[suffix];\n        }\n\n        // Step 3\n        re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;\n        if (re.test(w)) {\n            var fp = re.exec(w);\n            stem = fp[1];\n            suffix = fp[2];\n            re = new RegExp(mgr0);\n            if (re.test(stem))\n                w = stem + step3list[suffix];\n        }\n\n        // Step 4\n        re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;\n        re2 = /^(.+?)(s|t)(ion)$/;\n        if (re.test(w)) {\n            var fp = re.exec(w);\n            stem = fp[1];\n            re = new RegExp(mgr1);\n            if (re.test(stem))\n                w = stem;\n        } else if (re2.test(w)) {\n            var fp = re2.exec(w);\n            stem = fp[1] + fp[2];\n            re2 = new RegExp(mgr1);\n            if (re2.test(stem))\n                w = stem;\n        }\n\n        // Step 5\n        re = /^(.+?)e$/;\n        if (re.test(w)) {\n            var fp = re.exec(w);\n            stem = fp[1];\n            re = new RegExp(mgr1);\n            re2 = new RegExp(meq1);\n            re3 = new RegExp(\"^\" + C + v + \"[^aeiouwxy]$\");\n            if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))\n                w = stem;\n        }\n        re = /ll$/;\n        re2 = new RegExp(mgr1);\n        if (re.test(w) && re2.test(w)) {\n            re = /.$/;\n            w = w.replace(re, \"\");\n        }\n\n        // and turn initial Y back to y\n        if (firstch == \"y\")\n            w = firstch.toLowerCase() + w.substr(1);\n        return w;\n    }\n}\n\n"
  },
  {
    "path": "docs/WechatAPIClient/_static/pygments.css",
    "content": ".highlight pre {\n    line-height: 125%;\n}\n\n.highlight td.linenos .normal {\n    color: inherit;\n    background-color: transparent;\n    padding-left: 5px;\n    padding-right: 5px;\n}\n\n.highlight span.linenos {\n    color: inherit;\n    background-color: transparent;\n    padding-left: 5px;\n    padding-right: 5px;\n}\n\n.highlight td.linenos .special {\n    color: #000000;\n    background-color: #ffffc0;\n    padding-left: 5px;\n    padding-right: 5px;\n}\n\n.highlight span.linenos.special {\n    color: #000000;\n    background-color: #ffffc0;\n    padding-left: 5px;\n    padding-right: 5px;\n}\n\n.highlight .hll {\n    background-color: #ffffcc\n}\n\n.highlight {\n    background: #f8f8f8;\n}\n\n.highlight .c {\n    color: #8F5902;\n    font-style: italic\n}\n\n/* Comment */\n.highlight .err {\n    color: #A40000;\n    border: 1px solid #EF2929\n}\n\n/* Error */\n.highlight .g {\n    color: #000\n}\n\n/* Generic */\n.highlight .k {\n    color: #204A87;\n    font-weight: bold\n}\n\n/* Keyword */\n.highlight .l {\n    color: #000\n}\n\n/* Literal */\n.highlight .n {\n    color: #000\n}\n\n/* Name */\n.highlight .o {\n    color: #CE5C00;\n    font-weight: bold\n}\n\n/* Operator */\n.highlight .x {\n    color: #000\n}\n\n/* Other */\n.highlight .p {\n    color: #000;\n    font-weight: bold\n}\n\n/* Punctuation */\n.highlight .ch {\n    color: #8F5902;\n    font-style: italic\n}\n\n/* Comment.Hashbang */\n.highlight .cm {\n    color: #8F5902;\n    font-style: italic\n}\n\n/* Comment.Multiline */\n.highlight .cp {\n    color: #8F5902;\n    font-style: italic\n}\n\n/* Comment.Preproc */\n.highlight .cpf {\n    color: #8F5902;\n    font-style: italic\n}\n\n/* Comment.PreprocFile */\n.highlight .c1 {\n    color: #8F5902;\n    font-style: italic\n}\n\n/* Comment.Single */\n.highlight .cs {\n    color: #8F5902;\n    font-style: italic\n}\n\n/* Comment.Special */\n.highlight .gd {\n    color: #A40000\n}\n\n/* Generic.Deleted */\n.highlight .ge {\n    color: #000;\n    font-style: italic\n}\n\n/* Generic.Emph */\n.highlight .ges {\n    color: #000;\n    font-weight: bold;\n    font-style: italic\n}\n\n/* Generic.EmphStrong */\n.highlight .gr {\n    color: #EF2929\n}\n\n/* Generic.Error */\n.highlight .gh {\n    color: #000080;\n    font-weight: bold\n}\n\n/* Generic.Heading */\n.highlight .gi {\n    color: #00A000\n}\n\n/* Generic.Inserted */\n.highlight .go {\n    color: #000;\n    font-style: italic\n}\n\n/* Generic.Output */\n.highlight .gp {\n    color: #8F5902\n}\n\n/* Generic.Prompt */\n.highlight .gs {\n    color: #000;\n    font-weight: bold\n}\n\n/* Generic.Strong */\n.highlight .gu {\n    color: #800080;\n    font-weight: bold\n}\n\n/* Generic.Subheading */\n.highlight .gt {\n    color: #A40000;\n    font-weight: bold\n}\n\n/* Generic.Traceback */\n.highlight .kc {\n    color: #204A87;\n    font-weight: bold\n}\n\n/* Keyword.Constant */\n.highlight .kd {\n    color: #204A87;\n    font-weight: bold\n}\n\n/* Keyword.Declaration */\n.highlight .kn {\n    color: #204A87;\n    font-weight: bold\n}\n\n/* Keyword.Namespace */\n.highlight .kp {\n    color: #204A87;\n    font-weight: bold\n}\n\n/* Keyword.Pseudo */\n.highlight .kr {\n    color: #204A87;\n    font-weight: bold\n}\n\n/* Keyword.Reserved */\n.highlight .kt {\n    color: #204A87;\n    font-weight: bold\n}\n\n/* Keyword.Type */\n.highlight .ld {\n    color: #000\n}\n\n/* Literal.Date */\n.highlight .m {\n    color: #0000CF;\n    font-weight: bold\n}\n\n/* Literal.Number */\n.highlight .s {\n    color: #4E9A06\n}\n\n/* Literal.String */\n.highlight .na {\n    color: #C4A000\n}\n\n/* Name.Attribute */\n.highlight .nb {\n    color: #204A87\n}\n\n/* Name.Builtin */\n.highlight .nc {\n    color: #000\n}\n\n/* Name.Class */\n.highlight .no {\n    color: #000\n}\n\n/* Name.Constant */\n.highlight .nd {\n    color: #5C35CC;\n    font-weight: bold\n}\n\n/* Name.Decorator */\n.highlight .ni {\n    color: #CE5C00\n}\n\n/* Name.Entity */\n.highlight .ne {\n    color: #C00;\n    font-weight: bold\n}\n\n/* Name.Exception */\n.highlight .nf {\n    color: #000\n}\n\n/* Name.Function */\n.highlight .nl {\n    color: #F57900\n}\n\n/* Name.Label */\n.highlight .nn {\n    color: #000\n}\n\n/* Name.Namespace */\n.highlight .nx {\n    color: #000\n}\n\n/* Name.Other */\n.highlight .py {\n    color: #000\n}\n\n/* Name.Property */\n.highlight .nt {\n    color: #204A87;\n    font-weight: bold\n}\n\n/* Name.Tag */\n.highlight .nv {\n    color: #000\n}\n\n/* Name.Variable */\n.highlight .ow {\n    color: #204A87;\n    font-weight: bold\n}\n\n/* Operator.Word */\n.highlight .pm {\n    color: #000;\n    font-weight: bold\n}\n\n/* Punctuation.Marker */\n.highlight .w {\n    color: #F8F8F8\n}\n\n/* Text.Whitespace */\n.highlight .mb {\n    color: #0000CF;\n    font-weight: bold\n}\n\n/* Literal.Number.Bin */\n.highlight .mf {\n    color: #0000CF;\n    font-weight: bold\n}\n\n/* Literal.Number.Float */\n.highlight .mh {\n    color: #0000CF;\n    font-weight: bold\n}\n\n/* Literal.Number.Hex */\n.highlight .mi {\n    color: #0000CF;\n    font-weight: bold\n}\n\n/* Literal.Number.Integer */\n.highlight .mo {\n    color: #0000CF;\n    font-weight: bold\n}\n\n/* Literal.Number.Oct */\n.highlight .sa {\n    color: #4E9A06\n}\n\n/* Literal.String.Affix */\n.highlight .sb {\n    color: #4E9A06\n}\n\n/* Literal.String.Backtick */\n.highlight .sc {\n    color: #4E9A06\n}\n\n/* Literal.String.Char */\n.highlight .dl {\n    color: #4E9A06\n}\n\n/* Literal.String.Delimiter */\n.highlight .sd {\n    color: #8F5902;\n    font-style: italic\n}\n\n/* Literal.String.Doc */\n.highlight .s2 {\n    color: #4E9A06\n}\n\n/* Literal.String.Double */\n.highlight .se {\n    color: #4E9A06\n}\n\n/* Literal.String.Escape */\n.highlight .sh {\n    color: #4E9A06\n}\n\n/* Literal.String.Heredoc */\n.highlight .si {\n    color: #4E9A06\n}\n\n/* Literal.String.Interpol */\n.highlight .sx {\n    color: #4E9A06\n}\n\n/* Literal.String.Other */\n.highlight .sr {\n    color: #4E9A06\n}\n\n/* Literal.String.Regex */\n.highlight .s1 {\n    color: #4E9A06\n}\n\n/* Literal.String.Single */\n.highlight .ss {\n    color: #4E9A06\n}\n\n/* Literal.String.Symbol */\n.highlight .bp {\n    color: #3465A4\n}\n\n/* Name.Builtin.Pseudo */\n.highlight .fm {\n    color: #000\n}\n\n/* Name.Function.Magic */\n.highlight .vc {\n    color: #000\n}\n\n/* Name.Variable.Class */\n.highlight .vg {\n    color: #000\n}\n\n/* Name.Variable.Global */\n.highlight .vi {\n    color: #000\n}\n\n/* Name.Variable.Instance */\n.highlight .vm {\n    color: #000\n}\n\n/* Name.Variable.Magic */\n.highlight .il {\n    color: #0000CF;\n    font-weight: bold\n}\n\n/* Literal.Number.Integer.Long */\n@media not print {\n    body[data-theme=\"dark\"] .highlight pre {\n        line-height: 125%;\n    }\n\n    body[data-theme=\"dark\"] .highlight td.linenos .normal {\n        color: #aaaaaa;\n        background-color: transparent;\n        padding-left: 5px;\n        padding-right: 5px;\n    }\n\n    body[data-theme=\"dark\"] .highlight span.linenos {\n        color: #aaaaaa;\n        background-color: transparent;\n        padding-left: 5px;\n        padding-right: 5px;\n    }\n\n    body[data-theme=\"dark\"] .highlight td.linenos .special {\n        color: #000000;\n        background-color: #ffffc0;\n        padding-left: 5px;\n        padding-right: 5px;\n    }\n\n    body[data-theme=\"dark\"] .highlight span.linenos.special {\n        color: #000000;\n        background-color: #ffffc0;\n        padding-left: 5px;\n        padding-right: 5px;\n    }\n\n    body[data-theme=\"dark\"] .highlight .hll {\n        background-color: #404040\n    }\n\n    body[data-theme=\"dark\"] .highlight {\n        background: #202020;\n        color: #D0D0D0\n    }\n\n    body[data-theme=\"dark\"] .highlight .c {\n        color: #ABABAB;\n        font-style: italic\n    }\n\n    /* Comment */\n    body[data-theme=\"dark\"] .highlight .err {\n        color: #A61717;\n        background-color: #E3D2D2\n    }\n\n    /* Error */\n    body[data-theme=\"dark\"] .highlight .esc {\n        color: #D0D0D0\n    }\n\n    /* Escape */\n    body[data-theme=\"dark\"] .highlight .g {\n        color: #D0D0D0\n    }\n\n    /* Generic */\n    body[data-theme=\"dark\"] .highlight .k {\n        color: #6EBF26;\n        font-weight: bold\n    }\n\n    /* Keyword */\n    body[data-theme=\"dark\"] .highlight .l {\n        color: #D0D0D0\n    }\n\n    /* Literal */\n    body[data-theme=\"dark\"] .highlight .n {\n        color: #D0D0D0\n    }\n\n    /* Name */\n    body[data-theme=\"dark\"] .highlight .o {\n        color: #D0D0D0\n    }\n\n    /* Operator */\n    body[data-theme=\"dark\"] .highlight .x {\n        color: #D0D0D0\n    }\n\n    /* Other */\n    body[data-theme=\"dark\"] .highlight .p {\n        color: #D0D0D0\n    }\n\n    /* Punctuation */\n    body[data-theme=\"dark\"] .highlight .ch {\n        color: #ABABAB;\n        font-style: italic\n    }\n\n    /* Comment.Hashbang */\n    body[data-theme=\"dark\"] .highlight .cm {\n        color: #ABABAB;\n        font-style: italic\n    }\n\n    /* Comment.Multiline */\n    body[data-theme=\"dark\"] .highlight .cp {\n        color: #FF3A3A;\n        font-weight: bold\n    }\n\n    /* Comment.Preproc */\n    body[data-theme=\"dark\"] .highlight .cpf {\n        color: #ABABAB;\n        font-style: italic\n    }\n\n    /* Comment.PreprocFile */\n    body[data-theme=\"dark\"] .highlight .c1 {\n        color: #ABABAB;\n        font-style: italic\n    }\n\n    /* Comment.Single */\n    body[data-theme=\"dark\"] .highlight .cs {\n        color: #E50808;\n        font-weight: bold;\n        background-color: #520000\n    }\n\n    /* Comment.Special */\n    body[data-theme=\"dark\"] .highlight .gd {\n        color: #FF3A3A\n    }\n\n    /* Generic.Deleted */\n    body[data-theme=\"dark\"] .highlight .ge {\n        color: #D0D0D0;\n        font-style: italic\n    }\n\n    /* Generic.Emph */\n    body[data-theme=\"dark\"] .highlight .ges {\n        color: #D0D0D0;\n        font-weight: bold;\n        font-style: italic\n    }\n\n    /* Generic.EmphStrong */\n    body[data-theme=\"dark\"] .highlight .gr {\n        color: #FF3A3A\n    }\n\n    /* Generic.Error */\n    body[data-theme=\"dark\"] .highlight .gh {\n        color: #FFF;\n        font-weight: bold\n    }\n\n    /* Generic.Heading */\n    body[data-theme=\"dark\"] .highlight .gi {\n        color: #589819\n    }\n\n    /* Generic.Inserted */\n    body[data-theme=\"dark\"] .highlight .go {\n        color: #CCC\n    }\n\n    /* Generic.Output */\n    body[data-theme=\"dark\"] .highlight .gp {\n        color: #AAA\n    }\n\n    /* Generic.Prompt */\n    body[data-theme=\"dark\"] .highlight .gs {\n        color: #D0D0D0;\n        font-weight: bold\n    }\n\n    /* Generic.Strong */\n    body[data-theme=\"dark\"] .highlight .gu {\n        color: #FFF;\n        text-decoration: underline\n    }\n\n    /* Generic.Subheading */\n    body[data-theme=\"dark\"] .highlight .gt {\n        color: #FF3A3A\n    }\n\n    /* Generic.Traceback */\n    body[data-theme=\"dark\"] .highlight .kc {\n        color: #6EBF26;\n        font-weight: bold\n    }\n\n    /* Keyword.Constant */\n    body[data-theme=\"dark\"] .highlight .kd {\n        color: #6EBF26;\n        font-weight: bold\n    }\n\n    /* Keyword.Declaration */\n    body[data-theme=\"dark\"] .highlight .kn {\n        color: #6EBF26;\n        font-weight: bold\n    }\n\n    /* Keyword.Namespace */\n    body[data-theme=\"dark\"] .highlight .kp {\n        color: #6EBF26\n    }\n\n    /* Keyword.Pseudo */\n    body[data-theme=\"dark\"] .highlight .kr {\n        color: #6EBF26;\n        font-weight: bold\n    }\n\n    /* Keyword.Reserved */\n    body[data-theme=\"dark\"] .highlight .kt {\n        color: #6EBF26;\n        font-weight: bold\n    }\n\n    /* Keyword.Type */\n    body[data-theme=\"dark\"] .highlight .ld {\n        color: #D0D0D0\n    }\n\n    /* Literal.Date */\n    body[data-theme=\"dark\"] .highlight .m {\n        color: #51B2FD\n    }\n\n    /* Literal.Number */\n    body[data-theme=\"dark\"] .highlight .s {\n        color: #ED9D13\n    }\n\n    /* Literal.String */\n    body[data-theme=\"dark\"] .highlight .na {\n        color: #BBB\n    }\n\n    /* Name.Attribute */\n    body[data-theme=\"dark\"] .highlight .nb {\n        color: #2FBCCD\n    }\n\n    /* Name.Builtin */\n    body[data-theme=\"dark\"] .highlight .nc {\n        color: #71ADFF;\n        text-decoration: underline\n    }\n\n    /* Name.Class */\n    body[data-theme=\"dark\"] .highlight .no {\n        color: #40FFFF\n    }\n\n    /* Name.Constant */\n    body[data-theme=\"dark\"] .highlight .nd {\n        color: #FFA500\n    }\n\n    /* Name.Decorator */\n    body[data-theme=\"dark\"] .highlight .ni {\n        color: #D0D0D0\n    }\n\n    /* Name.Entity */\n    body[data-theme=\"dark\"] .highlight .ne {\n        color: #BBB\n    }\n\n    /* Name.Exception */\n    body[data-theme=\"dark\"] .highlight .nf {\n        color: #71ADFF\n    }\n\n    /* Name.Function */\n    body[data-theme=\"dark\"] .highlight .nl {\n        color: #D0D0D0\n    }\n\n    /* Name.Label */\n    body[data-theme=\"dark\"] .highlight .nn {\n        color: #71ADFF;\n        text-decoration: underline\n    }\n\n    /* Name.Namespace */\n    body[data-theme=\"dark\"] .highlight .nx {\n        color: #D0D0D0\n    }\n\n    /* Name.Other */\n    body[data-theme=\"dark\"] .highlight .py {\n        color: #D0D0D0\n    }\n\n    /* Name.Property */\n    body[data-theme=\"dark\"] .highlight .nt {\n        color: #6EBF26;\n        font-weight: bold\n    }\n\n    /* Name.Tag */\n    body[data-theme=\"dark\"] .highlight .nv {\n        color: #40FFFF\n    }\n\n    /* Name.Variable */\n    body[data-theme=\"dark\"] .highlight .ow {\n        color: #6EBF26;\n        font-weight: bold\n    }\n\n    /* Operator.Word */\n    body[data-theme=\"dark\"] .highlight .pm {\n        color: #D0D0D0\n    }\n\n    /* Punctuation.Marker */\n    body[data-theme=\"dark\"] .highlight .w {\n        color: #666\n    }\n\n    /* Text.Whitespace */\n    body[data-theme=\"dark\"] .highlight .mb {\n        color: #51B2FD\n    }\n\n    /* Literal.Number.Bin */\n    body[data-theme=\"dark\"] .highlight .mf {\n        color: #51B2FD\n    }\n\n    /* Literal.Number.Float */\n    body[data-theme=\"dark\"] .highlight .mh {\n        color: #51B2FD\n    }\n\n    /* Literal.Number.Hex */\n    body[data-theme=\"dark\"] .highlight .mi {\n        color: #51B2FD\n    }\n\n    /* Literal.Number.Integer */\n    body[data-theme=\"dark\"] .highlight .mo {\n        color: #51B2FD\n    }\n\n    /* Literal.Number.Oct */\n    body[data-theme=\"dark\"] .highlight .sa {\n        color: #ED9D13\n    }\n\n    /* Literal.String.Affix */\n    body[data-theme=\"dark\"] .highlight .sb {\n        color: #ED9D13\n    }\n\n    /* Literal.String.Backtick */\n    body[data-theme=\"dark\"] .highlight .sc {\n        color: #ED9D13\n    }\n\n    /* Literal.String.Char */\n    body[data-theme=\"dark\"] .highlight .dl {\n        color: #ED9D13\n    }\n\n    /* Literal.String.Delimiter */\n    body[data-theme=\"dark\"] .highlight .sd {\n        color: #ED9D13\n    }\n\n    /* Literal.String.Doc */\n    body[data-theme=\"dark\"] .highlight .s2 {\n        color: #ED9D13\n    }\n\n    /* Literal.String.Double */\n    body[data-theme=\"dark\"] .highlight .se {\n        color: #ED9D13\n    }\n\n    /* Literal.String.Escape */\n    body[data-theme=\"dark\"] .highlight .sh {\n        color: #ED9D13\n    }\n\n    /* Literal.String.Heredoc */\n    body[data-theme=\"dark\"] .highlight .si {\n        color: #ED9D13\n    }\n\n    /* Literal.String.Interpol */\n    body[data-theme=\"dark\"] .highlight .sx {\n        color: #FFA500\n    }\n\n    /* Literal.String.Other */\n    body[data-theme=\"dark\"] .highlight .sr {\n        color: #ED9D13\n    }\n\n    /* Literal.String.Regex */\n    body[data-theme=\"dark\"] .highlight .s1 {\n        color: #ED9D13\n    }\n\n    /* Literal.String.Single */\n    body[data-theme=\"dark\"] .highlight .ss {\n        color: #ED9D13\n    }\n\n    /* Literal.String.Symbol */\n    body[data-theme=\"dark\"] .highlight .bp {\n        color: #2FBCCD\n    }\n\n    /* Name.Builtin.Pseudo */\n    body[data-theme=\"dark\"] .highlight .fm {\n        color: #71ADFF\n    }\n\n    /* Name.Function.Magic */\n    body[data-theme=\"dark\"] .highlight .vc {\n        color: #40FFFF\n    }\n\n    /* Name.Variable.Class */\n    body[data-theme=\"dark\"] .highlight .vg {\n        color: #40FFFF\n    }\n\n    /* Name.Variable.Global */\n    body[data-theme=\"dark\"] .highlight .vi {\n        color: #40FFFF\n    }\n\n    /* Name.Variable.Instance */\n    body[data-theme=\"dark\"] .highlight .vm {\n        color: #40FFFF\n    }\n\n    /* Name.Variable.Magic */\n    body[data-theme=\"dark\"] .highlight .il {\n        color: #51B2FD\n    }\n\n    /* Literal.Number.Integer.Long */\n    @media (prefers-color-scheme: dark) {\n        body:not([data-theme=\"light\"]) .highlight pre {\n            line-height: 125%;\n        }\n\n        body:not([data-theme=\"light\"]) .highlight td.linenos .normal {\n            color: #aaaaaa;\n            background-color: transparent;\n            padding-left: 5px;\n            padding-right: 5px;\n        }\n\n        body:not([data-theme=\"light\"]) .highlight span.linenos {\n            color: #aaaaaa;\n            background-color: transparent;\n            padding-left: 5px;\n            padding-right: 5px;\n        }\n\n        body:not([data-theme=\"light\"]) .highlight td.linenos .special {\n            color: #000000;\n            background-color: #ffffc0;\n            padding-left: 5px;\n            padding-right: 5px;\n        }\n\n        body:not([data-theme=\"light\"]) .highlight span.linenos.special {\n            color: #000000;\n            background-color: #ffffc0;\n            padding-left: 5px;\n            padding-right: 5px;\n        }\n\n        body:not([data-theme=\"light\"]) .highlight .hll {\n            background-color: #404040\n        }\n\n        body:not([data-theme=\"light\"]) .highlight {\n            background: #202020;\n            color: #D0D0D0\n        }\n\n        body:not([data-theme=\"light\"]) .highlight .c {\n            color: #ABABAB;\n            font-style: italic\n        }\n\n        /* Comment */\n        body:not([data-theme=\"light\"]) .highlight .err {\n            color: #A61717;\n            background-color: #E3D2D2\n        }\n\n        /* Error */\n        body:not([data-theme=\"light\"]) .highlight .esc {\n            color: #D0D0D0\n        }\n\n        /* Escape */\n        body:not([data-theme=\"light\"]) .highlight .g {\n            color: #D0D0D0\n        }\n\n        /* Generic */\n        body:not([data-theme=\"light\"]) .highlight .k {\n            color: #6EBF26;\n            font-weight: bold\n        }\n\n        /* Keyword */\n        body:not([data-theme=\"light\"]) .highlight .l {\n            color: #D0D0D0\n        }\n\n        /* Literal */\n        body:not([data-theme=\"light\"]) .highlight .n {\n            color: #D0D0D0\n        }\n\n        /* Name */\n        body:not([data-theme=\"light\"]) .highlight .o {\n            color: #D0D0D0\n        }\n\n        /* Operator */\n        body:not([data-theme=\"light\"]) .highlight .x {\n            color: #D0D0D0\n        }\n\n        /* Other */\n        body:not([data-theme=\"light\"]) .highlight .p {\n            color: #D0D0D0\n        }\n\n        /* Punctuation */\n        body:not([data-theme=\"light\"]) .highlight .ch {\n            color: #ABABAB;\n            font-style: italic\n        }\n\n        /* Comment.Hashbang */\n        body:not([data-theme=\"light\"]) .highlight .cm {\n            color: #ABABAB;\n            font-style: italic\n        }\n\n        /* Comment.Multiline */\n        body:not([data-theme=\"light\"]) .highlight .cp {\n            color: #FF3A3A;\n            font-weight: bold\n        }\n\n        /* Comment.Preproc */\n        body:not([data-theme=\"light\"]) .highlight .cpf {\n            color: #ABABAB;\n            font-style: italic\n        }\n\n        /* Comment.PreprocFile */\n        body:not([data-theme=\"light\"]) .highlight .c1 {\n            color: #ABABAB;\n            font-style: italic\n        }\n\n        /* Comment.Single */\n        body:not([data-theme=\"light\"]) .highlight .cs {\n            color: #E50808;\n            font-weight: bold;\n            background-color: #520000\n        }\n\n        /* Comment.Special */\n        body:not([data-theme=\"light\"]) .highlight .gd {\n            color: #FF3A3A\n        }\n\n        /* Generic.Deleted */\n        body:not([data-theme=\"light\"]) .highlight .ge {\n            color: #D0D0D0;\n            font-style: italic\n        }\n\n        /* Generic.Emph */\n        body:not([data-theme=\"light\"]) .highlight .ges {\n            color: #D0D0D0;\n            font-weight: bold;\n            font-style: italic\n        }\n\n        /* Generic.EmphStrong */\n        body:not([data-theme=\"light\"]) .highlight .gr {\n            color: #FF3A3A\n        }\n\n        /* Generic.Error */\n        body:not([data-theme=\"light\"]) .highlight .gh {\n            color: #FFF;\n            font-weight: bold\n        }\n\n        /* Generic.Heading */\n        body:not([data-theme=\"light\"]) .highlight .gi {\n            color: #589819\n        }\n\n        /* Generic.Inserted */\n        body:not([data-theme=\"light\"]) .highlight .go {\n            color: #CCC\n        }\n\n        /* Generic.Output */\n        body:not([data-theme=\"light\"]) .highlight .gp {\n            color: #AAA\n        }\n\n        /* Generic.Prompt */\n        body:not([data-theme=\"light\"]) .highlight .gs {\n            color: #D0D0D0;\n            font-weight: bold\n        }\n\n        /* Generic.Strong */\n        body:not([data-theme=\"light\"]) .highlight .gu {\n            color: #FFF;\n            text-decoration: underline\n        }\n\n        /* Generic.Subheading */\n        body:not([data-theme=\"light\"]) .highlight .gt {\n            color: #FF3A3A\n        }\n\n        /* Generic.Traceback */\n        body:not([data-theme=\"light\"]) .highlight .kc {\n            color: #6EBF26;\n            font-weight: bold\n        }\n\n        /* Keyword.Constant */\n        body:not([data-theme=\"light\"]) .highlight .kd {\n            color: #6EBF26;\n            font-weight: bold\n        }\n\n        /* Keyword.Declaration */\n        body:not([data-theme=\"light\"]) .highlight .kn {\n            color: #6EBF26;\n            font-weight: bold\n        }\n\n        /* Keyword.Namespace */\n        body:not([data-theme=\"light\"]) .highlight .kp {\n            color: #6EBF26\n        }\n\n        /* Keyword.Pseudo */\n        body:not([data-theme=\"light\"]) .highlight .kr {\n            color: #6EBF26;\n            font-weight: bold\n        }\n\n        /* Keyword.Reserved */\n        body:not([data-theme=\"light\"]) .highlight .kt {\n            color: #6EBF26;\n            font-weight: bold\n        }\n\n        /* Keyword.Type */\n        body:not([data-theme=\"light\"]) .highlight .ld {\n            color: #D0D0D0\n        }\n\n        /* Literal.Date */\n        body:not([data-theme=\"light\"]) .highlight .m {\n            color: #51B2FD\n        }\n\n        /* Literal.Number */\n        body:not([data-theme=\"light\"]) .highlight .s {\n            color: #ED9D13\n        }\n\n        /* Literal.String */\n        body:not([data-theme=\"light\"]) .highlight .na {\n            color: #BBB\n        }\n\n        /* Name.Attribute */\n        body:not([data-theme=\"light\"]) .highlight .nb {\n            color: #2FBCCD\n        }\n\n        /* Name.Builtin */\n        body:not([data-theme=\"light\"]) .highlight .nc {\n            color: #71ADFF;\n            text-decoration: underline\n        }\n\n        /* Name.Class */\n        body:not([data-theme=\"light\"]) .highlight .no {\n            color: #40FFFF\n        }\n\n        /* Name.Constant */\n        body:not([data-theme=\"light\"]) .highlight .nd {\n            color: #FFA500\n        }\n\n        /* Name.Decorator */\n        body:not([data-theme=\"light\"]) .highlight .ni {\n            color: #D0D0D0\n        }\n\n        /* Name.Entity */\n        body:not([data-theme=\"light\"]) .highlight .ne {\n            color: #BBB\n        }\n\n        /* Name.Exception */\n        body:not([data-theme=\"light\"]) .highlight .nf {\n            color: #71ADFF\n        }\n\n        /* Name.Function */\n        body:not([data-theme=\"light\"]) .highlight .nl {\n            color: #D0D0D0\n        }\n\n        /* Name.Label */\n        body:not([data-theme=\"light\"]) .highlight .nn {\n            color: #71ADFF;\n            text-decoration: underline\n        }\n\n        /* Name.Namespace */\n        body:not([data-theme=\"light\"]) .highlight .nx {\n            color: #D0D0D0\n        }\n\n        /* Name.Other */\n        body:not([data-theme=\"light\"]) .highlight .py {\n            color: #D0D0D0\n        }\n\n        /* Name.Property */\n        body:not([data-theme=\"light\"]) .highlight .nt {\n            color: #6EBF26;\n            font-weight: bold\n        }\n\n        /* Name.Tag */\n        body:not([data-theme=\"light\"]) .highlight .nv {\n            color: #40FFFF\n        }\n\n        /* Name.Variable */\n        body:not([data-theme=\"light\"]) .highlight .ow {\n            color: #6EBF26;\n            font-weight: bold\n        }\n\n        /* Operator.Word */\n        body:not([data-theme=\"light\"]) .highlight .pm {\n            color: #D0D0D0\n        }\n\n        /* Punctuation.Marker */\n        body:not([data-theme=\"light\"]) .highlight .w {\n            color: #666\n        }\n\n        /* Text.Whitespace */\n        body:not([data-theme=\"light\"]) .highlight .mb {\n            color: #51B2FD\n        }\n\n        /* Literal.Number.Bin */\n        body:not([data-theme=\"light\"]) .highlight .mf {\n            color: #51B2FD\n        }\n\n        /* Literal.Number.Float */\n        body:not([data-theme=\"light\"]) .highlight .mh {\n            color: #51B2FD\n        }\n\n        /* Literal.Number.Hex */\n        body:not([data-theme=\"light\"]) .highlight .mi {\n            color: #51B2FD\n        }\n\n        /* Literal.Number.Integer */\n        body:not([data-theme=\"light\"]) .highlight .mo {\n            color: #51B2FD\n        }\n\n        /* Literal.Number.Oct */\n        body:not([data-theme=\"light\"]) .highlight .sa {\n            color: #ED9D13\n        }\n\n        /* Literal.String.Affix */\n        body:not([data-theme=\"light\"]) .highlight .sb {\n            color: #ED9D13\n        }\n\n        /* Literal.String.Backtick */\n        body:not([data-theme=\"light\"]) .highlight .sc {\n            color: #ED9D13\n        }\n\n        /* Literal.String.Char */\n        body:not([data-theme=\"light\"]) .highlight .dl {\n            color: #ED9D13\n        }\n\n        /* Literal.String.Delimiter */\n        body:not([data-theme=\"light\"]) .highlight .sd {\n            color: #ED9D13\n        }\n\n        /* Literal.String.Doc */\n        body:not([data-theme=\"light\"]) .highlight .s2 {\n            color: #ED9D13\n        }\n\n        /* Literal.String.Double */\n        body:not([data-theme=\"light\"]) .highlight .se {\n            color: #ED9D13\n        }\n\n        /* Literal.String.Escape */\n        body:not([data-theme=\"light\"]) .highlight .sh {\n            color: #ED9D13\n        }\n\n        /* Literal.String.Heredoc */\n        body:not([data-theme=\"light\"]) .highlight .si {\n            color: #ED9D13\n        }\n\n        /* Literal.String.Interpol */\n        body:not([data-theme=\"light\"]) .highlight .sx {\n            color: #FFA500\n        }\n\n        /* Literal.String.Other */\n        body:not([data-theme=\"light\"]) .highlight .sr {\n            color: #ED9D13\n        }\n\n        /* Literal.String.Regex */\n        body:not([data-theme=\"light\"]) .highlight .s1 {\n            color: #ED9D13\n        }\n\n        /* Literal.String.Single */\n        body:not([data-theme=\"light\"]) .highlight .ss {\n            color: #ED9D13\n        }\n\n        /* Literal.String.Symbol */\n        body:not([data-theme=\"light\"]) .highlight .bp {\n            color: #2FBCCD\n        }\n\n        /* Name.Builtin.Pseudo */\n        body:not([data-theme=\"light\"]) .highlight .fm {\n            color: #71ADFF\n        }\n\n        /* Name.Function.Magic */\n        body:not([data-theme=\"light\"]) .highlight .vc {\n            color: #40FFFF\n        }\n\n        /* Name.Variable.Class */\n        body:not([data-theme=\"light\"]) .highlight .vg {\n            color: #40FFFF\n        }\n\n        /* Name.Variable.Global */\n        body:not([data-theme=\"light\"]) .highlight .vi {\n            color: #40FFFF\n        }\n\n        /* Name.Variable.Instance */\n        body:not([data-theme=\"light\"]) .highlight .vm {\n            color: #40FFFF\n        }\n\n        /* Name.Variable.Magic */\n        body:not([data-theme=\"light\"]) .highlight .il {\n            color: #51B2FD\n        }\n\n        /* Literal.Number.Integer.Long */\n    }\n}"
  },
  {
    "path": "docs/WechatAPIClient/_static/scripts/furo-extensions.js",
    "content": ""
  },
  {
    "path": "docs/WechatAPIClient/_static/scripts/furo.js",
    "content": "/*! For license information please see furo.js.LICENSE.txt */\n(()=>{var t={856:function(t,e,n){var o,r;r=void 0!==n.g?n.g:\"undefined\"!=typeof window?window:this,o=function(){return function(t){\"use strict\";var e={navClass:\"active\",contentClass:\"active\",nested:!1,nestedClass:\"active\",offset:0,reflow:!1,events:!0},n=function(t,e,n){if(n.settings.events){var o=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:n});e.dispatchEvent(o)}},o=function(t){var e=0;if(t.offsetParent)for(;t;)e+=t.offsetTop,t=t.offsetParent;return e>=0?e:0},r=function(t){t&&t.sort((function(t,e){return o(t.content)<o(e.content)?-1:1}))},c=function(e,n,o){var r=e.getBoundingClientRect(),c=function(t){return\"function\"==typeof t.offset?parseFloat(t.offset()):parseFloat(t.offset)}(n);return o?parseInt(r.bottom,10)<(t.innerHeight||document.documentElement.clientHeight):parseInt(r.top,10)<=c},s=function(){return Math.ceil(t.innerHeight+t.pageYOffset)>=Math.max(document.body.scrollHeight,document.documentElement.scrollHeight,document.body.offsetHeight,document.documentElement.offsetHeight,document.body.clientHeight,document.documentElement.clientHeight)},l=function(t,e){var n=t[t.length-1];if(function(t,e){return!(!s()||!c(t.content,e,!0))}(n,e))return n;for(var o=t.length-1;o>=0;o--)if(c(t[o].content,e))return t[o]},a=function(t,e){if(e.nested&&t.parentNode){var n=t.parentNode.closest(\"li\");n&&(n.classList.remove(e.nestedClass),a(n,e))}},i=function(t,e){if(t){var o=t.nav.closest(\"li\");o&&(o.classList.remove(e.navClass),t.content.classList.remove(e.contentClass),a(o,e),n(\"gumshoeDeactivate\",o,{link:t.nav,content:t.content,settings:e}))}},u=function(t,e){if(e.nested){var n=t.parentNode.closest(\"li\");n&&(n.classList.add(e.nestedClass),u(n,e))}};return function(o,c){var s,a,d,f,m,v={setup:function(){s=document.querySelectorAll(o),a=[],Array.prototype.forEach.call(s,(function(t){var e=document.getElementById(decodeURIComponent(t.hash.substr(1)));e&&a.push({nav:t,content:e})})),r(a)},detect:function(){var t=l(a,m);t?d&&t.content===d.content||(i(d,m),function(t,e){if(t){var o=t.nav.closest(\"li\");o&&(o.classList.add(e.navClass),t.content.classList.add(e.contentClass),u(o,e),n(\"gumshoeActivate\",o,{link:t.nav,content:t.content,settings:e}))}}(t,m),d=t):d&&(i(d,m),d=null)}},h=function(e){f&&t.cancelAnimationFrame(f),f=t.requestAnimationFrame(v.detect)},g=function(e){f&&t.cancelAnimationFrame(f),f=t.requestAnimationFrame((function(){r(a),v.detect()}))};return v.destroy=function(){d&&i(d,m),t.removeEventListener(\"scroll\",h,!1),m.reflow&&t.removeEventListener(\"resize\",g,!1),a=null,s=null,d=null,f=null,m=null},m=function(){var t={};return Array.prototype.forEach.call(arguments,(function(e){for(var n in e){if(!e.hasOwnProperty(n))return;t[n]=e[n]}})),t}(e,c||{}),v.setup(),v.detect(),t.addEventListener(\"scroll\",h,!1),m.reflow&&t.addEventListener(\"resize\",g,!1),v}}(r)}.apply(e,[]),void 0===o||(t.exports=o)}},e={};function n(o){var r=e[o];if(void 0!==r)return r.exports;var c=e[o]={exports:{}};return t[o].call(c.exports,c,c.exports,n),c.exports}n.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var o in e)n.o(e,o)&&!n.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:e[o]})},n.g=function(){if(\"object\"==typeof globalThis)return globalThis;try{return this||new Function(\"return this\")()}catch(t){if(\"object\"==typeof window)return window}}(),n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),(()=>{\"use strict\";var t=n(856),e=n.n(t),o=null,r=null,c=document.documentElement.scrollTop;const s=64;function l(){const t=localStorage.getItem(\"theme\")||\"auto\";var e;\"light\"!==(e=window.matchMedia(\"(prefers-color-scheme: dark)\").matches?\"auto\"===t?\"light\":\"light\"==t?\"dark\":\"auto\":\"auto\"===t?\"dark\":\"dark\"==t?\"light\":\"auto\")&&\"dark\"!==e&&\"auto\"!==e&&(console.error(`Got invalid theme mode: ${e}. Resetting to auto.`),e=\"auto\"),document.body.dataset.theme=e,localStorage.setItem(\"theme\",e),console.log(`Changed to ${e} mode.`)}function a(){!function(){const t=document.getElementsByClassName(\"theme-toggle\");Array.from(t).forEach((t=>{t.addEventListener(\"click\",l)}))}(),function(){let t=0,e=!1;window.addEventListener(\"scroll\",(function(n){t=window.scrollY,e||(window.requestAnimationFrame((function(){var n;(function(t){const e=Math.floor(r.getBoundingClientRect().top);console.log(`headerTop: ${e}`),0==e&&t!=e?r.classList.add(\"scrolled\"):r.classList.remove(\"scrolled\")})(n=t),function(t){t<s?document.documentElement.classList.remove(\"show-back-to-top\"):t<c?document.documentElement.classList.add(\"show-back-to-top\"):t>c&&document.documentElement.classList.remove(\"show-back-to-top\"),c=t}(n),function(t){null!==o&&(0==t?o.scrollTo(0,0):Math.ceil(t)>=Math.floor(document.documentElement.scrollHeight-window.innerHeight)?o.scrollTo(0,o.scrollHeight):document.querySelector(\".scroll-current\"))}(n),e=!1})),e=!0)})),window.scroll()}(),null!==o&&new(e())(\".toc-tree a\",{reflow:!0,recursive:!0,navClass:\"scroll-current\",offset:()=>{let t=parseFloat(getComputedStyle(document.documentElement).fontSize);return r.getBoundingClientRect().height+2.5*t+1}})}document.addEventListener(\"DOMContentLoaded\",(function(){document.body.parentNode.classList.remove(\"no-js\"),r=document.querySelector(\"header\"),o=document.querySelector(\".toc-scroll\"),a()}))})()})();\n//# sourceMappingURL=furo.js.map"
  },
  {
    "path": "docs/WechatAPIClient/_static/scripts/furo.js.LICENSE.txt",
    "content": "/*!\n * gumshoejs v5.1.2 (patched by @pradyunsg)\n * A simple, framework-agnostic scrollspy script.\n * (c) 2019 Chris Ferdinandi\n * MIT License\n * http://github.com/cferdinandi/gumshoe\n */\n"
  },
  {
    "path": "docs/WechatAPIClient/_static/searchtools.js",
    "content": "/*\n * Sphinx JavaScript utilities for the full-text search.\n */\n\"use strict\";\n\n/**\n * Simple result scoring code.\n */\nif (typeof Scorer === \"undefined\") {\n    var Scorer = {\n        // Implement the following function to further tweak the score for each result\n        // The function takes a result array [docname, title, anchor, descr, score, filename]\n        // and returns the new score.\n        /*\n        score: result => {\n          const [docname, title, anchor, descr, score, filename, kind] = result\n          return score\n        },\n        */\n\n        // query matches the full name of an object\n        objNameMatch: 11,\n        // or matches in the last dotted part of the object name\n        objPartialMatch: 6,\n        // Additive scores depending on the priority of the object\n        objPrio: {\n            0: 15, // used to be importantResults\n            1: 5, // used to be objectResults\n            2: -5, // used to be unimportantResults\n        },\n        //  Used when the priority is not in the mapping.\n        objPrioDefault: 0,\n\n        // query found in title\n        title: 15,\n        partialTitle: 7,\n        // query found in terms\n        term: 5,\n        partialTerm: 2,\n    };\n}\n\n// Global search result kind enum, used by themes to style search results.\nclass SearchResultKind {\n    static get index() {\n        return \"index\";\n    }\n\n    static get object() {\n        return \"object\";\n    }\n\n    static get text() {\n        return \"text\";\n    }\n\n    static get title() {\n        return \"title\";\n    }\n}\n\nconst _removeChildren = (element) => {\n    while (element && element.lastChild) element.removeChild(element.lastChild);\n};\n\n/**\n * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping\n */\nconst _escapeRegExp = (string) =>\n    string.replace(/[.*+\\-?^${}()|[\\]\\\\]/g, \"\\\\$&\"); // $& means the whole matched string\n\nconst _displayItem = (item, searchTerms, highlightTerms) => {\n    const docBuilder = DOCUMENTATION_OPTIONS.BUILDER;\n    const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX;\n    const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX;\n    const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY;\n    const contentRoot = document.documentElement.dataset.content_root;\n\n    const [docName, title, anchor, descr, score, _filename, kind] = item;\n\n    let listItem = document.createElement(\"li\");\n    // Add a class representing the item's type:\n    // can be used by a theme's CSS selector for styling\n    // See SearchResultKind for the class names.\n    listItem.classList.add(`kind-${kind}`);\n    let requestUrl;\n    let linkUrl;\n    if (docBuilder === \"dirhtml\") {\n        // dirhtml builder\n        let dirname = docName + \"/\";\n        if (dirname.match(/\\/index\\/$/))\n            dirname = dirname.substring(0, dirname.length - 6);\n        else if (dirname === \"index/\") dirname = \"\";\n        requestUrl = contentRoot + dirname;\n        linkUrl = requestUrl;\n    } else {\n        // normal html builders\n        requestUrl = contentRoot + docName + docFileSuffix;\n        linkUrl = docName + docLinkSuffix;\n    }\n    let linkEl = listItem.appendChild(document.createElement(\"a\"));\n    linkEl.href = linkUrl + anchor;\n    linkEl.dataset.score = score;\n    linkEl.innerHTML = title;\n    if (descr) {\n        listItem.appendChild(document.createElement(\"span\")).innerHTML =\n            \" (\" + descr + \")\";\n        // highlight search terms in the description\n        if (SPHINX_HIGHLIGHT_ENABLED)  // set in sphinx_highlight.js\n            highlightTerms.forEach((term) => _highlightText(listItem, term, \"highlighted\"));\n    } else if (showSearchSummary)\n        fetch(requestUrl)\n            .then((responseData) => responseData.text())\n            .then((data) => {\n                if (data)\n                    listItem.appendChild(\n                        Search.makeSearchSummary(data, searchTerms, anchor)\n                    );\n                // highlight search terms in the summary\n                if (SPHINX_HIGHLIGHT_ENABLED)  // set in sphinx_highlight.js\n                    highlightTerms.forEach((term) => _highlightText(listItem, term, \"highlighted\"));\n            });\n    Search.output.appendChild(listItem);\n};\nconst _finishSearch = (resultCount) => {\n    Search.stopPulse();\n    Search.title.innerText = _(\"Search Results\");\n    if (!resultCount)\n        Search.status.innerText = Documentation.gettext(\n            \"Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories.\"\n        );\n    else\n        Search.status.innerText = Documentation.ngettext(\n            \"Search finished, found one page matching the search query.\",\n            \"Search finished, found ${resultCount} pages matching the search query.\",\n            resultCount,\n        ).replace('${resultCount}', resultCount);\n};\nconst _displayNextItem = (\n    results,\n    resultCount,\n    searchTerms,\n    highlightTerms,\n) => {\n    // results left, load the summary and display it\n    // this is intended to be dynamic (don't sub resultsCount)\n    if (results.length) {\n        _displayItem(results.pop(), searchTerms, highlightTerms);\n        setTimeout(\n            () => _displayNextItem(results, resultCount, searchTerms, highlightTerms),\n            5\n        );\n    }\n    // search finished, update title and status message\n    else _finishSearch(resultCount);\n};\n// Helper function used by query() to order search results.\n// Each input is an array of [docname, title, anchor, descr, score, filename, kind].\n// Order the results by score (in opposite order of appearance, since the\n// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically.\nconst _orderResultsByScoreThenName = (a, b) => {\n    const leftScore = a[4];\n    const rightScore = b[4];\n    if (leftScore === rightScore) {\n        // same score: sort alphabetically\n        const leftTitle = a[1].toLowerCase();\n        const rightTitle = b[1].toLowerCase();\n        if (leftTitle === rightTitle) return 0;\n        return leftTitle > rightTitle ? -1 : 1; // inverted is intentional\n    }\n    return leftScore > rightScore ? 1 : -1;\n};\n\n/**\n * Default splitQuery function. Can be overridden in ``sphinx.search`` with a\n * custom function per language.\n *\n * The regular expression works by splitting the string on consecutive characters\n * that are not Unicode letters, numbers, underscores, or emoji characters.\n * This is the same as ``\\W+`` in Python, preserving the surrogate pair area.\n */\nif (typeof splitQuery === \"undefined\") {\n    var splitQuery = (query) => query\n        .split(/[^\\p{Letter}\\p{Number}_\\p{Emoji_Presentation}]+/gu)\n        .filter(term => term)  // remove remaining empty strings\n}\n\n/**\n * Search Module\n */\nconst Search = {\n    _index: null,\n    _queued_query: null,\n    _pulse_status: -1,\n\n    htmlToText: (htmlString, anchor) => {\n        const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html');\n        for (const removalQuery of [\".headerlink\", \"script\", \"style\"]) {\n            htmlElement.querySelectorAll(removalQuery).forEach((el) => {\n                el.remove()\n            });\n        }\n        if (anchor) {\n            const anchorContent = htmlElement.querySelector(`[role=\"main\"] ${anchor}`);\n            if (anchorContent) return anchorContent.textContent;\n\n            console.warn(\n                `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.`\n            );\n        }\n\n        // if anchor not specified or not found, fall back to main content\n        const docContent = htmlElement.querySelector('[role=\"main\"]');\n        if (docContent) return docContent.textContent;\n\n        console.warn(\n            \"Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template.\"\n        );\n        return \"\";\n    },\n\n    init: () => {\n        const query = new URLSearchParams(window.location.search).get(\"q\");\n        document\n            .querySelectorAll('input[name=\"q\"]')\n            .forEach((el) => (el.value = query));\n        if (query) Search.performSearch(query);\n    },\n\n    loadIndex: (url) =>\n        (document.body.appendChild(document.createElement(\"script\")).src = url),\n\n    setIndex: (index) => {\n        Search._index = index;\n        if (Search._queued_query !== null) {\n            const query = Search._queued_query;\n            Search._queued_query = null;\n            Search.query(query);\n        }\n    },\n\n    hasIndex: () => Search._index !== null,\n\n    deferQuery: (query) => (Search._queued_query = query),\n\n    stopPulse: () => (Search._pulse_status = -1),\n\n    startPulse: () => {\n        if (Search._pulse_status >= 0) return;\n\n        const pulse = () => {\n            Search._pulse_status = (Search._pulse_status + 1) % 4;\n            Search.dots.innerText = \".\".repeat(Search._pulse_status);\n            if (Search._pulse_status >= 0) window.setTimeout(pulse, 500);\n        };\n        pulse();\n    },\n\n    /**\n     * perform a search for something (or wait until index is loaded)\n     */\n    performSearch: (query) => {\n        // create the required interface elements\n        const searchText = document.createElement(\"h2\");\n        searchText.textContent = _(\"Searching\");\n        const searchSummary = document.createElement(\"p\");\n        searchSummary.classList.add(\"search-summary\");\n        searchSummary.innerText = \"\";\n        const searchList = document.createElement(\"ul\");\n        searchList.setAttribute(\"role\", \"list\");\n        searchList.classList.add(\"search\");\n\n        const out = document.getElementById(\"search-results\");\n        Search.title = out.appendChild(searchText);\n        Search.dots = Search.title.appendChild(document.createElement(\"span\"));\n        Search.status = out.appendChild(searchSummary);\n        Search.output = out.appendChild(searchList);\n\n        const searchProgress = document.getElementById(\"search-progress\");\n        // Some themes don't use the search progress node\n        if (searchProgress) {\n            searchProgress.innerText = _(\"Preparing search...\");\n        }\n        Search.startPulse();\n\n        // index already loaded, the browser was quick!\n        if (Search.hasIndex()) Search.query(query);\n        else Search.deferQuery(query);\n    },\n\n    _parseQuery: (query) => {\n        // stem the search terms and add them to the correct list\n        const stemmer = new Stemmer();\n        const searchTerms = new Set();\n        const excludedTerms = new Set();\n        const highlightTerms = new Set();\n        const objectTerms = new Set(splitQuery(query.toLowerCase().trim()));\n        splitQuery(query.trim()).forEach((queryTerm) => {\n            const queryTermLower = queryTerm.toLowerCase();\n\n            // maybe skip this \"word\"\n            // stopwords array is from language_data.js\n            if (\n                stopwords.indexOf(queryTermLower) !== -1 ||\n                queryTerm.match(/^\\d+$/)\n            )\n                return;\n\n            // stem the word\n            let word = stemmer.stemWord(queryTermLower);\n            // select the correct list\n            if (word[0] === \"-\") excludedTerms.add(word.substr(1));\n            else {\n                searchTerms.add(word);\n                highlightTerms.add(queryTermLower);\n            }\n        });\n\n        if (SPHINX_HIGHLIGHT_ENABLED) {  // set in sphinx_highlight.js\n            localStorage.setItem(\"sphinx_highlight_terms\", [...highlightTerms].join(\" \"))\n        }\n\n        // console.debug(\"SEARCH: searching for:\");\n        // console.info(\"required: \", [...searchTerms]);\n        // console.info(\"excluded: \", [...excludedTerms]);\n\n        return [query, searchTerms, excludedTerms, highlightTerms, objectTerms];\n    },\n\n    /**\n     * execute search (requires search index to be loaded)\n     */\n    _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => {\n        const filenames = Search._index.filenames;\n        const docNames = Search._index.docnames;\n        const titles = Search._index.titles;\n        const allTitles = Search._index.alltitles;\n        const indexEntries = Search._index.indexentries;\n\n        // Collect multiple result groups to be sorted separately and then ordered.\n        // Each is an array of [docname, title, anchor, descr, score, filename, kind].\n        const normalResults = [];\n        const nonMainIndexResults = [];\n\n        _removeChildren(document.getElementById(\"search-progress\"));\n\n        const queryLower = query.toLowerCase().trim();\n        for (const [title, foundTitles] of Object.entries(allTitles)) {\n            if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length / 2)) {\n                for (const [file, id] of foundTitles) {\n                    const score = Math.round(Scorer.title * queryLower.length / title.length);\n                    const boost = titles[file] === title ? 1 : 0;  // add a boost for document titles\n                    normalResults.push([\n                        docNames[file],\n                        titles[file] !== title ? `${titles[file]} > ${title}` : title,\n                        id !== null ? \"#\" + id : \"\",\n                        null,\n                        score + boost,\n                        filenames[file],\n                        SearchResultKind.title,\n                    ]);\n                }\n            }\n        }\n\n        // search for explicit entries in index directives\n        for (const [entry, foundEntries] of Object.entries(indexEntries)) {\n            if (entry.includes(queryLower) && (queryLower.length >= entry.length / 2)) {\n                for (const [file, id, isMain] of foundEntries) {\n                    const score = Math.round(100 * queryLower.length / entry.length);\n                    const result = [\n                        docNames[file],\n                        titles[file],\n                        id ? \"#\" + id : \"\",\n                        null,\n                        score,\n                        filenames[file],\n                        SearchResultKind.index,\n                    ];\n                    if (isMain) {\n                        normalResults.push(result);\n                    } else {\n                        nonMainIndexResults.push(result);\n                    }\n                }\n            }\n        }\n\n        // lookup as object\n        objectTerms.forEach((term) =>\n            normalResults.push(...Search.performObjectSearch(term, objectTerms))\n        );\n\n        // lookup as search terms in fulltext\n        normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms));\n\n        // let the scorer override scores with a custom scoring function\n        if (Scorer.score) {\n            normalResults.forEach((item) => (item[4] = Scorer.score(item)));\n            nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item)));\n        }\n\n        // Sort each group of results by score and then alphabetically by name.\n        normalResults.sort(_orderResultsByScoreThenName);\n        nonMainIndexResults.sort(_orderResultsByScoreThenName);\n\n        // Combine the result groups in (reverse) order.\n        // Non-main index entries are typically arbitrary cross-references,\n        // so display them after other results.\n        let results = [...nonMainIndexResults, ...normalResults];\n\n        // remove duplicate search results\n        // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept\n        let seen = new Set();\n        results = results.reverse().reduce((acc, result) => {\n            let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(',');\n            if (!seen.has(resultStr)) {\n                acc.push(result);\n                seen.add(resultStr);\n            }\n            return acc;\n        }, []);\n\n        return results.reverse();\n    },\n\n    query: (query) => {\n        const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query);\n        const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms);\n\n        // for debugging\n        //Search.lastresults = results.slice();  // a copy\n        // console.info(\"search results:\", Search.lastresults);\n\n        // print the results\n        _displayNextItem(results, results.length, searchTerms, highlightTerms);\n    },\n\n    /**\n     * search for object names\n     */\n    performObjectSearch: (object, objectTerms) => {\n        const filenames = Search._index.filenames;\n        const docNames = Search._index.docnames;\n        const objects = Search._index.objects;\n        const objNames = Search._index.objnames;\n        const titles = Search._index.titles;\n\n        const results = [];\n\n        const objectSearchCallback = (prefix, match) => {\n            const name = match[4]\n            const fullname = (prefix ? prefix + \".\" : \"\") + name;\n            const fullnameLower = fullname.toLowerCase();\n            if (fullnameLower.indexOf(object) < 0) return;\n\n            let score = 0;\n            const parts = fullnameLower.split(\".\");\n\n            // check for different match types: exact matches of full name or\n            // \"last name\" (i.e. last dotted part)\n            if (fullnameLower === object || parts.slice(-1)[0] === object)\n                score += Scorer.objNameMatch;\n            else if (parts.slice(-1)[0].indexOf(object) > -1)\n                score += Scorer.objPartialMatch; // matches in last name\n\n            const objName = objNames[match[1]][2];\n            const title = titles[match[0]];\n\n            // If more than one term searched for, we require other words to be\n            // found in the name/title/description\n            const otherTerms = new Set(objectTerms);\n            otherTerms.delete(object);\n            if (otherTerms.size > 0) {\n                const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase();\n                if (\n                    [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0)\n                )\n                    return;\n            }\n\n            let anchor = match[3];\n            if (anchor === \"\") anchor = fullname;\n            else if (anchor === \"-\") anchor = objNames[match[1]][1] + \"-\" + fullname;\n\n            const descr = objName + _(\", in \") + title;\n\n            // add custom score for some objects according to scorer\n            if (Scorer.objPrio.hasOwnProperty(match[2]))\n                score += Scorer.objPrio[match[2]];\n            else score += Scorer.objPrioDefault;\n\n            results.push([\n                docNames[match[0]],\n                fullname,\n                \"#\" + anchor,\n                descr,\n                score,\n                filenames[match[0]],\n                SearchResultKind.object,\n            ]);\n        };\n        Object.keys(objects).forEach((prefix) =>\n            objects[prefix].forEach((array) =>\n                objectSearchCallback(prefix, array)\n            )\n        );\n        return results;\n    },\n\n    /**\n     * search for full-text terms in the index\n     */\n    performTermsSearch: (searchTerms, excludedTerms) => {\n        // prepare search\n        const terms = Search._index.terms;\n        const titleTerms = Search._index.titleterms;\n        const filenames = Search._index.filenames;\n        const docNames = Search._index.docnames;\n        const titles = Search._index.titles;\n\n        const scoreMap = new Map();\n        const fileMap = new Map();\n\n        // perform the search on the required terms\n        searchTerms.forEach((word) => {\n            const files = [];\n            const arr = [\n                {files: terms[word], score: Scorer.term},\n                {files: titleTerms[word], score: Scorer.title},\n            ];\n            // add support for partial matches\n            if (word.length > 2) {\n                const escapedWord = _escapeRegExp(word);\n                if (!terms.hasOwnProperty(word)) {\n                    Object.keys(terms).forEach((term) => {\n                        if (term.match(escapedWord))\n                            arr.push({files: terms[term], score: Scorer.partialTerm});\n                    });\n                }\n                if (!titleTerms.hasOwnProperty(word)) {\n                    Object.keys(titleTerms).forEach((term) => {\n                        if (term.match(escapedWord))\n                            arr.push({files: titleTerms[term], score: Scorer.partialTitle});\n                    });\n                }\n            }\n\n            // no match but word was a required one\n            if (arr.every((record) => record.files === undefined)) return;\n\n            // found search word in contents\n            arr.forEach((record) => {\n                if (record.files === undefined) return;\n\n                let recordFiles = record.files;\n                if (recordFiles.length === undefined) recordFiles = [recordFiles];\n                files.push(...recordFiles);\n\n                // set score for the word in each file\n                recordFiles.forEach((file) => {\n                    if (!scoreMap.has(file)) scoreMap.set(file, {});\n                    scoreMap.get(file)[word] = record.score;\n                });\n            });\n\n            // create the mapping\n            files.forEach((file) => {\n                if (!fileMap.has(file)) fileMap.set(file, [word]);\n                else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word);\n            });\n        });\n\n        // now check if the files don't contain excluded terms\n        const results = [];\n        for (const [file, wordList] of fileMap) {\n            // check if all requirements are matched\n\n            // as search terms with length < 3 are discarded\n            const filteredTermCount = [...searchTerms].filter(\n                (term) => term.length > 2\n            ).length;\n            if (\n                wordList.length !== searchTerms.size &&\n                wordList.length !== filteredTermCount\n            )\n                continue;\n\n            // ensure that none of the excluded terms is in the search result\n            if (\n                [...excludedTerms].some(\n                    (term) =>\n                        terms[term] === file ||\n                        titleTerms[term] === file ||\n                        (terms[term] || []).includes(file) ||\n                        (titleTerms[term] || []).includes(file)\n                )\n            )\n                break;\n\n            // select one (max) score for the file.\n            const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w]));\n            // add result to the result list\n            results.push([\n                docNames[file],\n                titles[file],\n                \"\",\n                null,\n                score,\n                filenames[file],\n                SearchResultKind.text,\n            ]);\n        }\n        return results;\n    },\n\n    /**\n     * helper function to return a node containing the\n     * search summary for a given text. keywords is a list\n     * of stemmed words.\n     */\n    makeSearchSummary: (htmlText, keywords, anchor) => {\n        const text = Search.htmlToText(htmlText, anchor);\n        if (text === \"\") return null;\n\n        const textLower = text.toLowerCase();\n        const actualStartPosition = [...keywords]\n            .map((k) => textLower.indexOf(k.toLowerCase()))\n            .filter((i) => i > -1)\n            .slice(-1)[0];\n        const startWithContext = Math.max(actualStartPosition - 120, 0);\n\n        const top = startWithContext === 0 ? \"\" : \"...\";\n        const tail = startWithContext + 240 < text.length ? \"...\" : \"\";\n\n        let summary = document.createElement(\"p\");\n        summary.classList.add(\"context\");\n        summary.textContent = top + text.substr(startWithContext, 240).trim() + tail;\n\n        return summary;\n    },\n};\n\n_ready(Search.init);\n"
  },
  {
    "path": "docs/WechatAPIClient/_static/skeleton.css",
    "content": "/* Some sane resets. */\nhtml {\n    height: 100%;\n}\n\nbody {\n    margin: 0;\n    min-height: 100%;\n}\n\n/* All the flexbox magic! */\nbody,\n.sb-announcement,\n.sb-content,\n.sb-main,\n.sb-container,\n.sb-container__inner,\n.sb-article-container,\n.sb-footer-content,\n.sb-header,\n.sb-header-secondary,\n.sb-footer {\n    display: flex;\n}\n\n/* These order things vertically */\nbody,\n.sb-main,\n.sb-article-container {\n    flex-direction: column;\n}\n\n/* Put elements in the center */\n.sb-header,\n.sb-header-secondary,\n.sb-container,\n.sb-content,\n.sb-footer,\n.sb-footer-content {\n    justify-content: center;\n}\n/* Put elements at the ends */\n.sb-article-container {\n    justify-content: space-between;\n}\n\n/* These elements grow. */\n.sb-main,\n.sb-content,\n.sb-container,\narticle {\n    flex-grow: 1;\n}\n\n/* Because padding making this wider is not fun */\narticle {\n    box-sizing: border-box;\n}\n\n/* The announcements element should never be wider than the page. */\n.sb-announcement {\n    max-width: 100%;\n}\n\n.sb-sidebar-primary,\n.sb-sidebar-secondary {\n    flex-shrink: 0;\n    width: 17rem;\n}\n\n.sb-announcement__inner {\n    justify-content: center;\n\n    box-sizing: border-box;\n    height: 3rem;\n\n    overflow-x: auto;\n    white-space: nowrap;\n}\n\n/* Sidebars, with checkbox-based toggle */\n.sb-sidebar-primary,\n.sb-sidebar-secondary {\n    position: fixed;\n    height: 100%;\n    top: 0;\n}\n\n.sb-sidebar-primary {\n    left: -17rem;\n    transition: left 250ms ease-in-out;\n}\n.sb-sidebar-secondary {\n    right: -17rem;\n    transition: right 250ms ease-in-out;\n}\n\n.sb-sidebar-toggle {\n    display: none;\n}\n.sb-sidebar-overlay {\n    position: fixed;\n    top: 0;\n    width: 0;\n    height: 0;\n\n    transition: width 0ms ease 250ms, height 0ms ease 250ms, opacity 250ms ease;\n\n    opacity: 0;\n    background-color: rgba(0, 0, 0, 0.54);\n}\n\n#sb-sidebar-toggle--primary:checked\n~ .sb-sidebar-overlay[for=\"sb-sidebar-toggle--primary\"],\n#sb-sidebar-toggle--secondary:checked\n~ .sb-sidebar-overlay[for=\"sb-sidebar-toggle--secondary\"] {\n    width: 100%;\n    height: 100%;\n    opacity: 1;\n    transition: width 0ms ease, height 0ms ease, opacity 250ms ease;\n}\n\n#sb-sidebar-toggle--primary:checked ~ .sb-container .sb-sidebar-primary {\n    left: 0;\n}\n#sb-sidebar-toggle--secondary:checked ~ .sb-container .sb-sidebar-secondary {\n    right: 0;\n}\n\n/* Full-width mode */\n.drop-secondary-sidebar-for-full-width-content\n.hide-when-secondary-sidebar-shown {\n    display: none !important;\n}\n.drop-secondary-sidebar-for-full-width-content .sb-sidebar-secondary {\n    display: none !important;\n}\n\n/* Mobile views */\n.sb-page-width {\n    width: 100%;\n}\n\n.sb-article-container,\n.sb-footer-content__inner,\n.drop-secondary-sidebar-for-full-width-content .sb-article,\n.drop-secondary-sidebar-for-full-width-content .match-content-width {\n    width: 100vw;\n}\n\n.sb-article,\n.match-content-width {\n    padding: 0 1rem;\n    box-sizing: border-box;\n}\n\n@media (min-width: 32rem) {\n    .sb-article,\n    .match-content-width {\n        padding: 0 2rem;\n    }\n}\n\n/* Tablet views */\n@media (min-width: 42rem) {\n    .sb-article-container {\n        width: auto;\n    }\n\n    .sb-footer-content__inner,\n    .drop-secondary-sidebar-for-full-width-content .sb-article,\n    .drop-secondary-sidebar-for-full-width-content .match-content-width {\n        width: 42rem;\n    }\n\n    .sb-article,\n    .match-content-width {\n        width: 42rem;\n    }\n}\n@media (min-width: 46rem) {\n    .sb-footer-content__inner,\n    .drop-secondary-sidebar-for-full-width-content .sb-article,\n    .drop-secondary-sidebar-for-full-width-content .match-content-width {\n        width: 46rem;\n    }\n\n    .sb-article,\n    .match-content-width {\n        width: 46rem;\n    }\n}\n@media (min-width: 50rem) {\n    .sb-footer-content__inner,\n    .drop-secondary-sidebar-for-full-width-content .sb-article,\n    .drop-secondary-sidebar-for-full-width-content .match-content-width {\n        width: 50rem;\n    }\n\n    .sb-article,\n    .match-content-width {\n        width: 50rem;\n    }\n}\n\n/* Tablet views */\n@media (min-width: 59rem) {\n    .sb-sidebar-secondary {\n        position: static;\n    }\n\n    .hide-when-secondary-sidebar-shown {\n        display: none !important;\n    }\n\n    .sb-footer-content__inner,\n    .drop-secondary-sidebar-for-full-width-content .sb-article,\n    .drop-secondary-sidebar-for-full-width-content .match-content-width {\n        width: 59rem;\n    }\n\n    .sb-article,\n    .match-content-width {\n        width: 42rem;\n    }\n}\n@media (min-width: 63rem) {\n    .sb-footer-content__inner,\n    .drop-secondary-sidebar-for-full-width-content .sb-article,\n    .drop-secondary-sidebar-for-full-width-content .match-content-width {\n        width: 63rem;\n    }\n\n    .sb-article,\n    .match-content-width {\n        width: 46rem;\n    }\n}\n@media (min-width: 67rem) {\n    .sb-footer-content__inner,\n    .drop-secondary-sidebar-for-full-width-content .sb-article,\n    .drop-secondary-sidebar-for-full-width-content .match-content-width {\n        width: 67rem;\n    }\n\n    .sb-article,\n    .match-content-width {\n        width: 50rem;\n    }\n}\n\n/* Desktop views */\n@media (min-width: 76rem) {\n    .sb-sidebar-primary {\n        position: static;\n    }\n\n    .hide-when-primary-sidebar-shown {\n        display: none !important;\n    }\n\n    .sb-footer-content__inner,\n    .drop-secondary-sidebar-for-full-width-content .sb-article,\n    .drop-secondary-sidebar-for-full-width-content .match-content-width {\n        width: 59rem;\n    }\n\n    .sb-article,\n    .match-content-width {\n        width: 42rem;\n    }\n}\n\n/* Full desktop views */\n@media (min-width: 80rem) {\n    .sb-article,\n    .match-content-width {\n        width: 46rem;\n    }\n\n    .sb-footer-content__inner,\n    .drop-secondary-sidebar-for-full-width-content .sb-article,\n    .drop-secondary-sidebar-for-full-width-content .match-content-width {\n        width: 63rem;\n    }\n}\n\n@media (min-width: 84rem) {\n    .sb-article,\n    .match-content-width {\n        width: 50rem;\n    }\n\n    .sb-footer-content__inner,\n    .drop-secondary-sidebar-for-full-width-content .sb-article,\n    .drop-secondary-sidebar-for-full-width-content .match-content-width {\n        width: 67rem;\n    }\n}\n\n@media (min-width: 88rem) {\n    .sb-footer-content__inner,\n    .drop-secondary-sidebar-for-full-width-content .sb-article,\n    .drop-secondary-sidebar-for-full-width-content .match-content-width {\n        width: 67rem;\n    }\n\n    .sb-page-width {\n        width: 88rem;\n    }\n}\n"
  },
  {
    "path": "docs/WechatAPIClient/_static/sphinx_highlight.js",
    "content": "/* Highlighting utilities for Sphinx HTML documentation. */\n\"use strict\";\n\nconst SPHINX_HIGHLIGHT_ENABLED = true\n\n/**\n * highlight a given string on a node by wrapping it in\n * span elements with the given class name.\n */\nconst _highlight = (node, addItems, text, className) => {\n    if (node.nodeType === Node.TEXT_NODE) {\n        const val = node.nodeValue;\n        const parent = node.parentNode;\n        const pos = val.toLowerCase().indexOf(text);\n        if (\n            pos >= 0 &&\n            !parent.classList.contains(className) &&\n            !parent.classList.contains(\"nohighlight\")\n        ) {\n            let span;\n\n            const closestNode = parent.closest(\"body, svg, foreignObject\");\n            const isInSVG = closestNode && closestNode.matches(\"svg\");\n            if (isInSVG) {\n                span = document.createElementNS(\"http://www.w3.org/2000/svg\", \"tspan\");\n            } else {\n                span = document.createElement(\"span\");\n                span.classList.add(className);\n            }\n\n            span.appendChild(document.createTextNode(val.substr(pos, text.length)));\n            const rest = document.createTextNode(val.substr(pos + text.length));\n            parent.insertBefore(\n                span,\n                parent.insertBefore(\n                    rest,\n                    node.nextSibling\n                )\n            );\n            node.nodeValue = val.substr(0, pos);\n            /* There may be more occurrences of search term in this node. So call this\n             * function recursively on the remaining fragment.\n             */\n            _highlight(rest, addItems, text, className);\n\n            if (isInSVG) {\n                const rect = document.createElementNS(\n                    \"http://www.w3.org/2000/svg\",\n                    \"rect\"\n                );\n                const bbox = parent.getBBox();\n                rect.x.baseVal.value = bbox.x;\n                rect.y.baseVal.value = bbox.y;\n                rect.width.baseVal.value = bbox.width;\n                rect.height.baseVal.value = bbox.height;\n                rect.setAttribute(\"class\", className);\n                addItems.push({parent: parent, target: rect});\n            }\n        }\n    } else if (node.matches && !node.matches(\"button, select, textarea\")) {\n        node.childNodes.forEach((el) => _highlight(el, addItems, text, className));\n    }\n};\nconst _highlightText = (thisNode, text, className) => {\n    let addItems = [];\n    _highlight(thisNode, addItems, text, className);\n    addItems.forEach((obj) =>\n        obj.parent.insertAdjacentElement(\"beforebegin\", obj.target)\n    );\n};\n\n/**\n * Small JavaScript module for the documentation.\n */\nconst SphinxHighlight = {\n\n    /**\n     * highlight the search words provided in localstorage in the text\n     */\n    highlightSearchWords: () => {\n        if (!SPHINX_HIGHLIGHT_ENABLED) return;  // bail if no highlight\n\n        // get and clear terms from localstorage\n        const url = new URL(window.location);\n        const highlight =\n            localStorage.getItem(\"sphinx_highlight_terms\")\n            || url.searchParams.get(\"highlight\")\n            || \"\";\n        localStorage.removeItem(\"sphinx_highlight_terms\")\n        url.searchParams.delete(\"highlight\");\n        window.history.replaceState({}, \"\", url);\n\n        // get individual terms from highlight string\n        const terms = highlight.toLowerCase().split(/\\s+/).filter(x => x);\n        if (terms.length === 0) return; // nothing to do\n\n        // There should never be more than one element matching \"div.body\"\n        const divBody = document.querySelectorAll(\"div.body\");\n        const body = divBody.length ? divBody[0] : document.querySelector(\"body\");\n        window.setTimeout(() => {\n            terms.forEach((term) => _highlightText(body, term, \"highlighted\"));\n        }, 10);\n\n        const searchBox = document.getElementById(\"searchbox\");\n        if (searchBox === null) return;\n        searchBox.appendChild(\n            document\n                .createRange()\n                .createContextualFragment(\n                    '<p class=\"highlight-link\">' +\n                    '<a href=\"javascript:SphinxHighlight.hideSearchWords()\">' +\n                    _(\"Hide Search Matches\") +\n                    \"</a></p>\"\n                )\n        );\n    },\n\n    /**\n     * helper function to hide the search marks again\n     */\n    hideSearchWords: () => {\n        document\n            .querySelectorAll(\"#searchbox .highlight-link\")\n            .forEach((el) => el.remove());\n        document\n            .querySelectorAll(\"span.highlighted\")\n            .forEach((el) => el.classList.remove(\"highlighted\"));\n        localStorage.removeItem(\"sphinx_highlight_terms\")\n    },\n\n    initEscapeListener: () => {\n        // only install a listener if it is really needed\n        if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return;\n\n        document.addEventListener(\"keydown\", (event) => {\n            // bail for input elements\n            if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;\n            // bail with special keys\n            if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;\n            if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === \"Escape\")) {\n                SphinxHighlight.hideSearchWords();\n                event.preventDefault();\n            }\n        });\n    },\n};\n\n_ready(() => {\n    /* Do not call highlightSearchWords() when we are on the search page.\n     * It will highlight words from the *previous* search query.\n     */\n    if (typeof Search === \"undefined\") SphinxHighlight.highlightSearchWords();\n    SphinxHighlight.initEscapeListener();\n});\n"
  },
  {
    "path": "docs/WechatAPIClient/_static/styles/furo-extensions.css",
    "content": "#furo-sidebar-ad-placement{padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)}#furo-sidebar-ad-placement .ethical-sidebar{background:var(--color-background-secondary);border:none;box-shadow:none}#furo-sidebar-ad-placement .ethical-sidebar:hover{background:var(--color-background-hover)}#furo-sidebar-ad-placement .ethical-sidebar a{color:var(--color-foreground-primary)}#furo-sidebar-ad-placement .ethical-callout a{color:var(--color-foreground-secondary)!important}#furo-readthedocs-versions{background:transparent;display:block;position:static;width:100%}#furo-readthedocs-versions .rst-versions{background:#1a1c1e}#furo-readthedocs-versions .rst-current-version{background:var(--color-sidebar-item-background);cursor:unset}#furo-readthedocs-versions .rst-current-version:hover{background:var(--color-sidebar-item-background)}#furo-readthedocs-versions .rst-current-version .fa-book{color:var(--color-foreground-primary)}#furo-readthedocs-versions>.rst-other-versions{padding:0}#furo-readthedocs-versions>.rst-other-versions small{opacity:1}#furo-readthedocs-versions .injected .rst-versions{position:unset}#furo-readthedocs-versions:focus-within,#furo-readthedocs-versions:hover{box-shadow:0 0 0 1px var(--color-sidebar-background-border)}#furo-readthedocs-versions:focus-within .rst-current-version,#furo-readthedocs-versions:hover .rst-current-version{background:#1a1c1e;font-size:inherit;height:auto;line-height:inherit;padding:12px;text-align:right}#furo-readthedocs-versions:focus-within .rst-current-version .fa-book,#furo-readthedocs-versions:hover .rst-current-version .fa-book{color:#fff;float:left}#furo-readthedocs-versions:focus-within .fa-caret-down,#furo-readthedocs-versions:hover .fa-caret-down{display:none}#furo-readthedocs-versions:focus-within .injected,#furo-readthedocs-versions:focus-within .rst-current-version,#furo-readthedocs-versions:focus-within .rst-other-versions,#furo-readthedocs-versions:hover .injected,#furo-readthedocs-versions:hover .rst-current-version,#furo-readthedocs-versions:hover .rst-other-versions{display:block}#furo-readthedocs-versions:focus-within>.rst-current-version,#furo-readthedocs-versions:hover>.rst-current-version{display:none}.highlight:hover button.copybtn{color:var(--color-code-foreground)}.highlight button.copybtn{align-items:center;background-color:var(--color-code-background);border:none;color:var(--color-background-item);cursor:pointer;height:1.25em;right:.5rem;top:.625rem;transition:color .3s,opacity .3s;width:1.25em}.highlight button.copybtn:hover{background-color:var(--color-code-background);color:var(--color-brand-content)}.highlight button.copybtn:after{background-color:transparent;color:var(--color-code-foreground);display:none}.highlight button.copybtn.success{color:#22863a;transition:color 0ms}.highlight button.copybtn.success:after{display:block}.highlight button.copybtn svg{padding:0}body{--sd-color-primary:var(--color-brand-primary);--sd-color-primary-highlight:var(--color-brand-content);--sd-color-primary-text:var(--color-background-primary);--sd-color-shadow:rgba(0,0,0,.05);--sd-color-card-border:var(--color-card-border);--sd-color-card-border-hover:var(--color-brand-content);--sd-color-card-background:var(--color-card-background);--sd-color-card-text:var(--color-foreground-primary);--sd-color-card-header:var(--color-card-marginals-background);--sd-color-card-footer:var(--color-card-marginals-background);--sd-color-tabs-label-active:var(--color-brand-content);--sd-color-tabs-label-hover:var(--color-foreground-muted);--sd-color-tabs-label-inactive:var(--color-foreground-muted);--sd-color-tabs-underline-active:var(--color-brand-content);--sd-color-tabs-underline-hover:var(--color-foreground-border);--sd-color-tabs-underline-inactive:var(--color-background-border);--sd-color-tabs-overline:var(--color-background-border);--sd-color-tabs-underline:var(--color-background-border)}.sd-tab-content{box-shadow:0 -2px var(--sd-color-tabs-overline),0 1px var(--sd-color-tabs-underline)}.sd-card{box-shadow:0 .1rem .25rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)}.sd-shadow-sm{box-shadow:0 .1rem .25rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-shadow-md{box-shadow:0 .3rem .75rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-shadow-lg{box-shadow:0 .6rem 1.5rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-card-hover:hover{transform:none}.sd-cards-carousel{gap:.25rem;padding:.25rem}body{--tabs--label-text:var(--color-foreground-muted);--tabs--label-text--hover:var(--color-foreground-muted);--tabs--label-text--active:var(--color-brand-content);--tabs--label-text--active--hover:var(--color-brand-content);--tabs--label-background:transparent;--tabs--label-background--hover:transparent;--tabs--label-background--active:transparent;--tabs--label-background--active--hover:transparent;--tabs--padding-x:0.25em;--tabs--margin-x:1em;--tabs--border:var(--color-background-border);--tabs--label-border:transparent;--tabs--label-border--hover:var(--color-foreground-muted);--tabs--label-border--active:var(--color-brand-content);--tabs--label-border--active--hover:var(--color-brand-content)}[role=main] .container{max-width:none;padding-left:0;padding-right:0}.shadow.docutils{border:none;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1)!important}.sphinx-bs .card{background-color:var(--color-background-secondary);color:var(--color-foreground)}\n/*# sourceMappingURL=furo-extensions.css.map*/"
  },
  {
    "path": "docs/WechatAPIClient/_static/styles/furo.css",
    "content": "/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}@media print{.content-icon-container,.headerlink,.mobile-header,.related-pages{display:none!important}.highlight{border:.1pt solid var(--color-foreground-border)}a,blockquote,dl,ol,p,pre,table,ul{page-break-inside:avoid}caption,figure,h1,h2,h3,h4,h5,h6,img{page-break-after:avoid;page-break-inside:avoid}dl,ol,ul{page-break-before:avoid}}.visually-hidden{height:1px!important;margin:-1px!important;overflow:hidden!important;padding:0!important;position:absolute!important;width:1px!important;clip:rect(0,0,0,0)!important;background:var(--color-background-primary);border:0!important;color:var(--color-foreground-primary);white-space:nowrap!important}:-moz-focusring{outline:auto}body{--font-stack:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;--font-stack--monospace:\"SFMono-Regular\",Menlo,Consolas,Monaco,Liberation Mono,Lucida Console,monospace;--font-stack--headings:var(--font-stack);--font-size--normal:100%;--font-size--small:87.5%;--font-size--small--2:81.25%;--font-size--small--3:75%;--font-size--small--4:62.5%;--sidebar-caption-font-size:var(--font-size--small--2);--sidebar-item-font-size:var(--font-size--small);--sidebar-search-input-font-size:var(--font-size--small);--toc-font-size:var(--font-size--small--3);--toc-font-size--mobile:var(--font-size--normal);--toc-title-font-size:var(--font-size--small--4);--admonition-font-size:0.8125rem;--admonition-title-font-size:0.8125rem;--code-font-size:var(--font-size--small--2);--api-font-size:var(--font-size--small);--header-height:calc(var(--sidebar-item-line-height) + var(--sidebar-item-spacing-vertical)*4);--header-padding:0.5rem;--sidebar-tree-space-above:1.5rem;--sidebar-caption-space-above:1rem;--sidebar-item-line-height:1rem;--sidebar-item-spacing-vertical:0.5rem;--sidebar-item-spacing-horizontal:1rem;--sidebar-item-height:calc(var(--sidebar-item-line-height) + var(--sidebar-item-spacing-vertical)*2);--sidebar-expander-width:var(--sidebar-item-height);--sidebar-search-space-above:0.5rem;--sidebar-search-input-spacing-vertical:0.5rem;--sidebar-search-input-spacing-horizontal:0.5rem;--sidebar-search-input-height:1rem;--sidebar-search-icon-size:var(--sidebar-search-input-height);--toc-title-padding:0.25rem 0;--toc-spacing-vertical:1.5rem;--toc-spacing-horizontal:1.5rem;--toc-item-spacing-vertical:0.4rem;--toc-item-spacing-horizontal:1rem;--icon-search:url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" viewBox=\"0 0 24 24\"><path stroke=\"none\" d=\"M0 0h24v24H0z\"/><circle cx=\"10\" cy=\"10\" r=\"7\"/><path d=\"m21 21-6-6\"/></svg>');--icon-pencil:url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75z\"/></svg>');--icon-abstract:url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M4 5h16v2H4zm0 4h16v2H4zm0 4h16v2H4zm0 4h10v2H4z\"/></svg>');--icon-info:url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2\"/></svg>');--icon-flame:url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M17.55 11.2c-.23-.3-.5-.56-.76-.82-.65-.6-1.4-1.03-2.03-1.66C13.3 7.26 13 4.85 13.91 3c-.91.23-1.75.75-2.45 1.32-2.54 2.08-3.54 5.75-2.34 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.22.1-.46.04-.64-.12a.8.8 0 0 1-.15-.17c-1.1-1.43-1.28-3.48-.53-5.12C5.89 10 5 12.3 5.14 14.47c.04.5.1 1 .27 1.5.14.6.4 1.2.72 1.73 1.04 1.73 2.87 2.97 4.84 3.22 2.1.27 4.35-.12 5.96-1.6 1.8-1.66 2.45-4.32 1.5-6.6l-.13-.26c-.2-.46-.47-.87-.8-1.25zm-3.1 6.3c-.28.24-.73.5-1.08.6-1.1.4-2.2-.16-2.87-.82 1.19-.28 1.89-1.16 2.09-2.05.17-.8-.14-1.46-.27-2.23-.12-.74-.1-1.37.18-2.06.17.38.37.76.6 1.06.76 1 1.95 1.44 2.2 2.8.04.14.06.28.06.43.03.82-.32 1.72-.92 2.27z\"/></svg>');--icon-question:url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"m15.07 11.25-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 0 0-2-2 2 2 0 0 0-2 2H8a4 4 0 0 1 4-4 4 4 0 0 1 4 4 3.2 3.2 0 0 1-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10c0-5.53-4.5-10-10-10\"/></svg>');--icon-warning:url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M13 14h-2v-4h2m0 8h-2v-2h2M1 21h22L12 2z\"/></svg>');--icon-failure:url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"M12 2c5.53 0 10 4.47 10 10s-4.47 10-10 10S2 17.53 2 12 6.47 2 12 2m3.59 5L12 10.59 8.41 7 7 8.41 10.59 12 7 15.59 8.41 17 12 13.41 15.59 17 17 15.59 13.41 12 17 8.41z\"/></svg>');--icon-spark:url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\"><path d=\"m11.5 20 4.86-9.73H13V4l-5 9.73h3.5zM12 2c2.75 0 5.1 1 7.05 2.95S22 9.25 22 12s-1 5.1-2.95 7.05S14.75 22 12 22s-5.1-1-7.05-2.95S2 14.75 2 12s1-5.1 2.95-7.05S9.25 2 12 2\"/></svg>');--color-admonition-title--caution:#ff9100;--color-admonition-title-background--caution:rgba(255,145,0,.2);--color-admonition-title--warning:#ff9100;--color-admonition-title-background--warning:rgba(255,145,0,.2);--color-admonition-title--danger:#ff5252;--color-admonition-title-background--danger:rgba(255,82,82,.2);--color-admonition-title--attention:#ff5252;--color-admonition-title-background--attention:rgba(255,82,82,.2);--color-admonition-title--error:#ff5252;--color-admonition-title-background--error:rgba(255,82,82,.2);--color-admonition-title--hint:#00c852;--color-admonition-title-background--hint:rgba(0,200,82,.2);--color-admonition-title--tip:#00c852;--color-admonition-title-background--tip:rgba(0,200,82,.2);--color-admonition-title--important:#00bfa5;--color-admonition-title-background--important:rgba(0,191,165,.2);--color-admonition-title--note:#00b0ff;--color-admonition-title-background--note:rgba(0,176,255,.2);--color-admonition-title--seealso:#448aff;--color-admonition-title-background--seealso:rgba(68,138,255,.2);--color-admonition-title--admonition-todo:grey;--color-admonition-title-background--admonition-todo:hsla(0,0%,50%,.2);--color-admonition-title:#651fff;--color-admonition-title-background:rgba(101,31,255,.2);--icon-admonition-default:var(--icon-abstract);--color-topic-title:#14b8a6;--color-topic-title-background:rgba(20,184,166,.2);--icon-topic-default:var(--icon-pencil);--color-problematic:#b30000;--color-foreground-primary:#000;--color-foreground-secondary:#5a5c63;--color-foreground-muted:#6b6f76;--color-foreground-border:#878787;--color-background-primary:#fff;--color-background-secondary:#f8f9fb;--color-background-hover:#efeff4;--color-background-hover--transparent:#efeff400;--color-background-border:#eeebee;--color-background-item:#ccc;--color-announcement-background:#000000dd;--color-announcement-text:#eeebee;--color-brand-primary:#0a4bff;--color-brand-content:#2757dd;--color-brand-visited:#872ee0;--color-api-background:var(--color-background-hover--transparent);--color-api-background-hover:var(--color-background-hover);--color-api-overall:var(--color-foreground-secondary);--color-api-name:var(--color-problematic);--color-api-pre-name:var(--color-problematic);--color-api-paren:var(--color-foreground-secondary);--color-api-keyword:var(--color-foreground-primary);--color-api-added:#21632c;--color-api-added-border:#38a84d;--color-api-changed:#046172;--color-api-changed-border:#06a1bc;--color-api-deprecated:#605706;--color-api-deprecated-border:#f0d90f;--color-api-removed:#b30000;--color-api-removed-border:#ff5c5c;--color-highlight-on-target:#ffc;--color-inline-code-background:var(--color-background-secondary);--color-highlighted-background:#def;--color-highlighted-text:var(--color-foreground-primary);--color-guilabel-background:#ddeeff80;--color-guilabel-border:#bedaf580;--color-guilabel-text:var(--color-foreground-primary);--color-admonition-background:transparent;--color-table-header-background:var(--color-background-secondary);--color-table-border:var(--color-background-border);--color-card-border:var(--color-background-secondary);--color-card-background:transparent;--color-card-marginals-background:var(--color-background-secondary);--color-header-background:var(--color-background-primary);--color-header-border:var(--color-background-border);--color-header-text:var(--color-foreground-primary);--color-sidebar-background:var(--color-background-secondary);--color-sidebar-background-border:var(--color-background-border);--color-sidebar-brand-text:var(--color-foreground-primary);--color-sidebar-caption-text:var(--color-foreground-muted);--color-sidebar-link-text:var(--color-foreground-secondary);--color-sidebar-link-text--top-level:var(--color-brand-primary);--color-sidebar-item-background:var(--color-sidebar-background);--color-sidebar-item-background--current:var( --color-sidebar-item-background );--color-sidebar-item-background--hover:linear-gradient(90deg,var(--color-background-hover--transparent) 0%,var(--color-background-hover) var(--sidebar-item-spacing-horizontal),var(--color-background-hover) 100%);--color-sidebar-item-expander-background:transparent;--color-sidebar-item-expander-background--hover:var( --color-background-hover );--color-sidebar-search-text:var(--color-foreground-primary);--color-sidebar-search-background:var(--color-background-secondary);--color-sidebar-search-background--focus:var(--color-background-primary);--color-sidebar-search-border:var(--color-background-border);--color-sidebar-search-icon:var(--color-foreground-muted);--color-toc-background:var(--color-background-primary);--color-toc-title-text:var(--color-foreground-muted);--color-toc-item-text:var(--color-foreground-secondary);--color-toc-item-text--hover:var(--color-foreground-primary);--color-toc-item-text--active:var(--color-brand-primary);--color-content-foreground:var(--color-foreground-primary);--color-content-background:transparent;--color-link:var(--color-brand-content);--color-link-underline:var(--color-background-border);--color-link--hover:var(--color-brand-content);--color-link-underline--hover:var(--color-foreground-border);--color-link--visited:var(--color-brand-visited);--color-link-underline--visited:var(--color-background-border);--color-link--visited--hover:var(--color-brand-visited);--color-link-underline--visited--hover:var(--color-foreground-border)}.only-light{display:block!important}html body .only-dark{display:none!important}@media not print{body[data-theme=dark]{--color-problematic:#ee5151;--color-foreground-primary:#cfd0d0;--color-foreground-secondary:#9ca0a5;--color-foreground-muted:#81868d;--color-foreground-border:#666;--color-background-primary:#131416;--color-background-secondary:#1a1c1e;--color-background-hover:#1e2124;--color-background-hover--transparent:#1e212400;--color-background-border:#303335;--color-background-item:#444;--color-announcement-background:#000000dd;--color-announcement-text:#eeebee;--color-brand-primary:#3d94ff;--color-brand-content:#5ca5ff;--color-brand-visited:#b27aeb;--color-highlighted-background:#083563;--color-guilabel-background:#08356380;--color-guilabel-border:#13395f80;--color-api-keyword:var(--color-foreground-secondary);--color-highlight-on-target:#330;--color-api-added:#3db854;--color-api-added-border:#267334;--color-api-changed:#09b0ce;--color-api-changed-border:#056d80;--color-api-deprecated:#b1a10b;--color-api-deprecated-border:#6e6407;--color-api-removed:#ff7575;--color-api-removed-border:#b03b3b;--color-admonition-background:#18181a;--color-card-border:var(--color-background-secondary);--color-card-background:#18181a;--color-card-marginals-background:var(--color-background-hover)}html body[data-theme=dark] .only-light{display:none!important}body[data-theme=dark] .only-dark{display:block!important}@media(prefers-color-scheme:dark){body:not([data-theme=light]){--color-problematic:#ee5151;--color-foreground-primary:#cfd0d0;--color-foreground-secondary:#9ca0a5;--color-foreground-muted:#81868d;--color-foreground-border:#666;--color-background-primary:#131416;--color-background-secondary:#1a1c1e;--color-background-hover:#1e2124;--color-background-hover--transparent:#1e212400;--color-background-border:#303335;--color-background-item:#444;--color-announcement-background:#000000dd;--color-announcement-text:#eeebee;--color-brand-primary:#3d94ff;--color-brand-content:#5ca5ff;--color-brand-visited:#b27aeb;--color-highlighted-background:#083563;--color-guilabel-background:#08356380;--color-guilabel-border:#13395f80;--color-api-keyword:var(--color-foreground-secondary);--color-highlight-on-target:#330;--color-api-added:#3db854;--color-api-added-border:#267334;--color-api-changed:#09b0ce;--color-api-changed-border:#056d80;--color-api-deprecated:#b1a10b;--color-api-deprecated-border:#6e6407;--color-api-removed:#ff7575;--color-api-removed-border:#b03b3b;--color-admonition-background:#18181a;--color-card-border:var(--color-background-secondary);--color-card-background:#18181a;--color-card-marginals-background:var(--color-background-hover)}html body:not([data-theme=light]) .only-light{display:none!important}body:not([data-theme=light]) .only-dark{display:block!important}}}body[data-theme=auto] .theme-toggle svg.theme-icon-when-auto-light{display:block}@media(prefers-color-scheme:dark){body[data-theme=auto] .theme-toggle svg.theme-icon-when-auto-dark{display:block}body[data-theme=auto] .theme-toggle svg.theme-icon-when-auto-light{display:none}}body[data-theme=dark] .theme-toggle svg.theme-icon-when-dark,body[data-theme=light] .theme-toggle svg.theme-icon-when-light{display:block}body{font-family:var(--font-stack)}code,kbd,pre,samp{font-family:var(--font-stack--monospace)}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}article{line-height:1.5}h1,h2,h3,h4,h5,h6{border-radius:.5rem;font-family:var(--font-stack--headings);font-weight:700;line-height:1.25;margin:.5rem -.5rem;padding-left:.5rem;padding-right:.5rem}h1+p,h2+p,h3+p,h4+p,h5+p,h6+p{margin-top:0}h1{font-size:2.5em;margin-bottom:1rem}h1,h2{margin-top:1.75rem}h2{font-size:2em}h3{font-size:1.5em}h4{font-size:1.25em}h5{font-size:1.125em}h6{font-size:1em}small{font-size:80%;opacity:75%}p{margin-bottom:.75rem;margin-top:.5rem}hr.docutils{background-color:var(--color-background-border);border:0;height:1px;margin:2rem 0;padding:0}.centered{text-align:center}a{color:var(--color-link);text-decoration:underline;text-decoration-color:var(--color-link-underline)}a:visited{color:var(--color-link--visited);text-decoration-color:var(--color-link-underline--visited)}a:visited:hover{color:var(--color-link--visited--hover);text-decoration-color:var(--color-link-underline--visited--hover)}a:hover{color:var(--color-link--hover);text-decoration-color:var(--color-link-underline--hover)}a.muted-link{color:inherit}a.muted-link:hover{color:var(--color-link--hover);text-decoration-color:var(--color-link-underline--hover)}a.muted-link:hover:visited{color:var(--color-link--visited--hover);text-decoration-color:var(--color-link-underline--visited--hover)}html{overflow-x:hidden;overflow-y:scroll;scroll-behavior:smooth}.sidebar-scroll,.toc-scroll,article[role=main] *{scrollbar-color:var(--color-foreground-border) transparent;scrollbar-width:thin}.sidebar-scroll::-webkit-scrollbar,.toc-scroll::-webkit-scrollbar,article[role=main] ::-webkit-scrollbar{height:.25rem;width:.25rem}.sidebar-scroll::-webkit-scrollbar-thumb,.toc-scroll::-webkit-scrollbar-thumb,article[role=main] ::-webkit-scrollbar-thumb{background-color:var(--color-foreground-border);border-radius:.125rem}body,html{height:100%}.skip-to-content,body,html{background:var(--color-background-primary);color:var(--color-foreground-primary)}.skip-to-content{border-radius:1rem;left:.25rem;padding:1rem;position:fixed;top:.25rem;transform:translateY(-200%);transition:transform .3s ease-in-out;z-index:40}.skip-to-content:focus-within{transform:translateY(0)}article{background:var(--color-content-background);color:var(--color-content-foreground);overflow-wrap:break-word}.page{display:flex;min-height:100%}.mobile-header{background-color:var(--color-header-background);border-bottom:1px solid var(--color-header-border);color:var(--color-header-text);display:none;height:var(--header-height);width:100%;z-index:10}.mobile-header.scrolled{border-bottom:none;box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2)}.mobile-header .header-center a{color:var(--color-header-text);text-decoration:none}.main{display:flex;flex:1}.sidebar-drawer{background:var(--color-sidebar-background);border-right:1px solid var(--color-sidebar-background-border);box-sizing:border-box;display:flex;justify-content:flex-end;min-width:15em;width:calc(50% - 26em)}.sidebar-container,.toc-drawer{box-sizing:border-box;width:15em}.toc-drawer{background:var(--color-toc-background);padding-right:1rem}.sidebar-sticky,.toc-sticky{display:flex;flex-direction:column;height:min(100%,100vh);height:100vh;position:sticky;top:0}.sidebar-scroll,.toc-scroll{flex-grow:1;flex-shrink:1;overflow:auto;scroll-behavior:smooth}.content{display:flex;flex-direction:column;justify-content:space-between;padding:0 3em;width:46em}.icon{display:inline-block;height:1rem;width:1rem}.icon svg{height:100%;width:100%}.announcement{align-items:center;background-color:var(--color-announcement-background);color:var(--color-announcement-text);display:flex;height:var(--header-height);overflow-x:auto}.announcement+.page{min-height:calc(100% - var(--header-height))}.announcement-content{box-sizing:border-box;min-width:100%;padding:.5rem;text-align:center;white-space:nowrap}.announcement-content a{color:var(--color-announcement-text);text-decoration-color:var(--color-announcement-text)}.announcement-content a:hover{color:var(--color-announcement-text);text-decoration-color:var(--color-link--hover)}.no-js .theme-toggle-container{display:none}.theme-toggle-container{display:flex}.theme-toggle{background:transparent;border:none;cursor:pointer;display:flex;padding:0}.theme-toggle svg{color:var(--color-foreground-primary);display:none;height:1.25rem;width:1.25rem}.theme-toggle-header{align-items:center;display:flex;justify-content:center}.nav-overlay-icon,.toc-overlay-icon{cursor:pointer;display:none}.nav-overlay-icon .icon,.toc-overlay-icon .icon{color:var(--color-foreground-secondary);height:1.5rem;width:1.5rem}.nav-overlay-icon,.toc-header-icon{align-items:center;justify-content:center}.toc-content-icon{height:1.5rem;width:1.5rem}.content-icon-container{display:flex;float:right;gap:.5rem;margin-bottom:1rem;margin-left:1rem;margin-top:1.5rem}.content-icon-container .edit-this-page svg,.content-icon-container .view-this-page svg{color:inherit;height:1.25rem;width:1.25rem}.sidebar-toggle{display:none;position:absolute}.sidebar-toggle[name=__toc]{left:20px}.sidebar-toggle:checked{left:40px}.overlay{background-color:rgba(0,0,0,.54);height:0;opacity:0;position:fixed;top:0;transition:width 0ms,height 0ms,opacity .25s ease-out;width:0}.sidebar-overlay{z-index:20}.toc-overlay{z-index:40}.sidebar-drawer{transition:left .25s ease-in-out;z-index:30}.toc-drawer{transition:right .25s ease-in-out;z-index:50}#__navigation:checked~.sidebar-overlay{height:100%;opacity:1;width:100%}#__navigation:checked~.page .sidebar-drawer{left:0;top:0}#__toc:checked~.toc-overlay{height:100%;opacity:1;width:100%}#__toc:checked~.page .toc-drawer{right:0;top:0}.back-to-top{background:var(--color-background-primary);border-radius:1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 1px 0 hsla(220,9%,46%,.502);display:none;font-size:.8125rem;left:0;margin-left:50%;padding:.5rem .75rem .5rem .5rem;position:fixed;text-decoration:none;top:1rem;transform:translateX(-50%);z-index:10}.back-to-top svg{height:1rem;width:1rem;fill:currentColor;display:inline-block}.back-to-top span{margin-left:.25rem}.show-back-to-top .back-to-top{align-items:center;display:flex}@media(min-width:97em){html{font-size:110%}}@media(max-width:82em){.toc-content-icon{display:flex}.toc-drawer{border-left:1px solid var(--color-background-muted);height:100vh;position:fixed;right:-15em;top:0}.toc-tree{border-left:none;font-size:var(--toc-font-size--mobile)}.sidebar-drawer{width:calc(50% - 18.5em)}}@media(max-width:67em){.content{margin-left:auto;margin-right:auto;padding:0 1em}}@media(max-width:63em){.nav-overlay-icon{display:flex}.sidebar-drawer{height:100vh;left:-15em;position:fixed;top:0;width:15em}.theme-toggle-header,.toc-header-icon{display:flex}.theme-toggle-content,.toc-content-icon{display:none}.mobile-header{align-items:center;display:flex;justify-content:space-between;position:sticky;top:0}.mobile-header .header-left,.mobile-header .header-right{display:flex;height:var(--header-height);padding:0 var(--header-padding)}.mobile-header .header-left label,.mobile-header .header-right label{height:100%;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.nav-overlay-icon .icon,.theme-toggle svg{height:1.5rem;width:1.5rem}:target{scroll-margin-top:calc(var(--header-height) + 2.5rem)}.back-to-top{top:calc(var(--header-height) + .5rem)}.page{flex-direction:column;justify-content:center}}@media(max-width:48em){.content{overflow-x:auto;width:100%}}@media(max-width:46em){article[role=main] aside.sidebar{float:none;margin:1rem 0;width:100%}}.admonition,.topic{background:var(--color-admonition-background);border-radius:.2rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1);font-size:var(--admonition-font-size);margin:1rem auto;overflow:hidden;padding:0 .5rem .5rem;page-break-inside:avoid}.admonition>:nth-child(2),.topic>:nth-child(2){margin-top:0}.admonition>:last-child,.topic>:last-child{margin-bottom:0}.admonition p.admonition-title,p.topic-title{font-size:var(--admonition-title-font-size);font-weight:500;line-height:1.3;margin:0 -.5rem .5rem;padding:.4rem .5rem .4rem 2rem;position:relative}.admonition p.admonition-title:before,p.topic-title:before{content:\"\";height:1rem;left:.5rem;position:absolute;width:1rem}p.admonition-title{background-color:var(--color-admonition-title-background)}p.admonition-title:before{background-color:var(--color-admonition-title);-webkit-mask-image:var(--icon-admonition-default);mask-image:var(--icon-admonition-default);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}p.topic-title{background-color:var(--color-topic-title-background)}p.topic-title:before{background-color:var(--color-topic-title);-webkit-mask-image:var(--icon-topic-default);mask-image:var(--icon-topic-default);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.admonition{border-left:.2rem solid var(--color-admonition-title)}.admonition.caution{border-left-color:var(--color-admonition-title--caution)}.admonition.caution>.admonition-title{background-color:var(--color-admonition-title-background--caution)}.admonition.caution>.admonition-title:before{background-color:var(--color-admonition-title--caution);-webkit-mask-image:var(--icon-spark);mask-image:var(--icon-spark)}.admonition.warning{border-left-color:var(--color-admonition-title--warning)}.admonition.warning>.admonition-title{background-color:var(--color-admonition-title-background--warning)}.admonition.warning>.admonition-title:before{background-color:var(--color-admonition-title--warning);-webkit-mask-image:var(--icon-warning);mask-image:var(--icon-warning)}.admonition.danger{border-left-color:var(--color-admonition-title--danger)}.admonition.danger>.admonition-title{background-color:var(--color-admonition-title-background--danger)}.admonition.danger>.admonition-title:before{background-color:var(--color-admonition-title--danger);-webkit-mask-image:var(--icon-spark);mask-image:var(--icon-spark)}.admonition.attention{border-left-color:var(--color-admonition-title--attention)}.admonition.attention>.admonition-title{background-color:var(--color-admonition-title-background--attention)}.admonition.attention>.admonition-title:before{background-color:var(--color-admonition-title--attention);-webkit-mask-image:var(--icon-warning);mask-image:var(--icon-warning)}.admonition.error{border-left-color:var(--color-admonition-title--error)}.admonition.error>.admonition-title{background-color:var(--color-admonition-title-background--error)}.admonition.error>.admonition-title:before{background-color:var(--color-admonition-title--error);-webkit-mask-image:var(--icon-failure);mask-image:var(--icon-failure)}.admonition.hint{border-left-color:var(--color-admonition-title--hint)}.admonition.hint>.admonition-title{background-color:var(--color-admonition-title-background--hint)}.admonition.hint>.admonition-title:before{background-color:var(--color-admonition-title--hint);-webkit-mask-image:var(--icon-question);mask-image:var(--icon-question)}.admonition.tip{border-left-color:var(--color-admonition-title--tip)}.admonition.tip>.admonition-title{background-color:var(--color-admonition-title-background--tip)}.admonition.tip>.admonition-title:before{background-color:var(--color-admonition-title--tip);-webkit-mask-image:var(--icon-info);mask-image:var(--icon-info)}.admonition.important{border-left-color:var(--color-admonition-title--important)}.admonition.important>.admonition-title{background-color:var(--color-admonition-title-background--important)}.admonition.important>.admonition-title:before{background-color:var(--color-admonition-title--important);-webkit-mask-image:var(--icon-flame);mask-image:var(--icon-flame)}.admonition.note{border-left-color:var(--color-admonition-title--note)}.admonition.note>.admonition-title{background-color:var(--color-admonition-title-background--note)}.admonition.note>.admonition-title:before{background-color:var(--color-admonition-title--note);-webkit-mask-image:var(--icon-pencil);mask-image:var(--icon-pencil)}.admonition.seealso{border-left-color:var(--color-admonition-title--seealso)}.admonition.seealso>.admonition-title{background-color:var(--color-admonition-title-background--seealso)}.admonition.seealso>.admonition-title:before{background-color:var(--color-admonition-title--seealso);-webkit-mask-image:var(--icon-info);mask-image:var(--icon-info)}.admonition.admonition-todo{border-left-color:var(--color-admonition-title--admonition-todo)}.admonition.admonition-todo>.admonition-title{background-color:var(--color-admonition-title-background--admonition-todo)}.admonition.admonition-todo>.admonition-title:before{background-color:var(--color-admonition-title--admonition-todo);-webkit-mask-image:var(--icon-pencil);mask-image:var(--icon-pencil)}.admonition-todo>.admonition-title{text-transform:uppercase}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dd{margin-left:2rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dd>:first-child{margin-top:.125rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list,dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dd>:last-child{margin-bottom:.75rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list>dt{font-size:var(--font-size--small);text-transform:uppercase}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd:empty{margin-bottom:.5rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd>ul{margin-left:-1.2rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd>ul>li>p:nth-child(2){margin-top:0}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd>ul>li>p+p:last-child:empty{margin-bottom:0;margin-top:0}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt{color:var(--color-api-overall)}.sig:not(.sig-inline){background:var(--color-api-background);border-radius:.25rem;font-family:var(--font-stack--monospace);font-size:var(--api-font-size);font-weight:700;margin-left:-.25rem;margin-right:-.25rem;padding:.25rem .5rem .25rem 3em;text-indent:-2.5em;transition:background .1s ease-out}.sig:not(.sig-inline):hover{background:var(--color-api-background-hover)}.sig:not(.sig-inline) a.reference .viewcode-link{font-weight:400;width:4.25rem}em.property{font-style:normal}em.property:first-child{color:var(--color-api-keyword)}.sig-name{color:var(--color-api-name)}.sig-prename{color:var(--color-api-pre-name);font-weight:400}.sig-paren{color:var(--color-api-paren)}.sig-param{font-style:normal}div.deprecated,div.versionadded,div.versionchanged,div.versionremoved{border-left:.1875rem solid;border-radius:.125rem;padding-left:.75rem}div.deprecated p,div.versionadded p,div.versionchanged p,div.versionremoved p{margin-bottom:.125rem;margin-top:.125rem}div.versionadded{border-color:var(--color-api-added-border)}div.versionadded .versionmodified{color:var(--color-api-added)}div.versionchanged{border-color:var(--color-api-changed-border)}div.versionchanged .versionmodified{color:var(--color-api-changed)}div.deprecated{border-color:var(--color-api-deprecated-border)}div.deprecated .versionmodified{color:var(--color-api-deprecated)}div.versionremoved{border-color:var(--color-api-removed-border)}div.versionremoved .versionmodified{color:var(--color-api-removed)}.viewcode-back,.viewcode-link{float:right;text-align:right}.line-block{margin-bottom:.75rem;margin-top:.5rem}.line-block .line-block{margin-bottom:0;margin-top:0;padding-left:1rem}.code-block-caption,article p.caption,table>caption{font-size:var(--font-size--small);text-align:center}.toctree-wrapper.compound .caption,.toctree-wrapper.compound :not(.caption)>.caption-text{font-size:var(--font-size--small);margin-bottom:0;text-align:initial;text-transform:uppercase}.toctree-wrapper.compound>ul{margin-bottom:0;margin-top:0}.sig-inline,code.literal{background:var(--color-inline-code-background);border-radius:.2em;font-size:var(--font-size--small--2);padding:.1em .2em}pre.literal-block .sig-inline,pre.literal-block code.literal{font-size:inherit;padding:0}p .sig-inline,p code.literal{border:1px solid var(--color-background-border)}.sig-inline{font-family:var(--font-stack--monospace)}div[class*=\" highlight-\"],div[class^=highlight-]{display:flex;margin:1em 0}div[class*=\" highlight-\"] .table-wrapper,div[class^=highlight-] .table-wrapper,pre{margin:0;padding:0}pre{overflow:auto}article[role=main] .highlight pre{line-height:1.5}.highlight pre,pre.literal-block{font-size:var(--code-font-size);padding:.625rem .875rem}pre.literal-block{background-color:var(--color-code-background);border-radius:.2rem;color:var(--color-code-foreground);margin-bottom:1rem;margin-top:1rem}.highlight{border-radius:.2rem;width:100%}.highlight .gp,.highlight span.linenos{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.highlight .hll{display:block;margin-left:-.875rem;margin-right:-.875rem;padding-left:.875rem;padding-right:.875rem}.code-block-caption{background-color:var(--color-code-background);border-bottom:1px solid;border-radius:.25rem;border-bottom-left-radius:0;border-bottom-right-radius:0;border-color:var(--color-background-border);color:var(--color-code-foreground);display:flex;font-weight:300;padding:.625rem .875rem}.code-block-caption+div[class]{margin-top:0}.code-block-caption+div[class] pre{border-top-left-radius:0;border-top-right-radius:0}.highlighttable{display:block;width:100%}.highlighttable tbody{display:block}.highlighttable tr{display:flex}.highlighttable td.linenos{background-color:var(--color-code-background);border-bottom-left-radius:.2rem;border-top-left-radius:.2rem;color:var(--color-code-foreground);padding:.625rem 0 .625rem .875rem}.highlighttable .linenodiv{box-shadow:-.0625rem 0 var(--color-foreground-border) inset;font-size:var(--code-font-size);padding-right:.875rem}.highlighttable td.code{display:block;flex:1;overflow:hidden;padding:0}.highlighttable td.code .highlight{border-bottom-left-radius:0;border-top-left-radius:0}.highlight span.linenos{box-shadow:-.0625rem 0 var(--color-foreground-border) inset;display:inline-block;margin-right:.875rem;padding-left:0;padding-right:.875rem}.footnote-reference{font-size:var(--font-size--small--4);vertical-align:super}dl.footnote.brackets{color:var(--color-foreground-secondary);display:grid;font-size:var(--font-size--small);grid-template-columns:max-content auto}dl.footnote.brackets dt{margin:0}dl.footnote.brackets dt>.fn-backref{margin-left:.25rem}dl.footnote.brackets dt:after{content:\":\"}dl.footnote.brackets dt .brackets:before{content:\"[\"}dl.footnote.brackets dt .brackets:after{content:\"]\"}dl.footnote.brackets dd{margin:0;padding:0 1rem}aside.footnote{color:var(--color-foreground-secondary);font-size:var(--font-size--small)}aside.footnote>span,div.citation>span{float:left;font-weight:500;padding-right:.25rem}aside.footnote>:not(span),div.citation>p{margin-left:2rem}img{box-sizing:border-box;height:auto;max-width:100%}article .figure,article figure{border-radius:.2rem;margin:0}article .figure :last-child,article figure :last-child{margin-bottom:0}article .align-left{clear:left;float:left;margin:0 1rem 1rem}article .align-right{clear:right;float:right;margin:0 1rem 1rem}article .align-center,article .align-default{display:block;margin-left:auto;margin-right:auto;text-align:center}article table.align-default{display:table;text-align:initial}.domainindex-jumpbox,.genindex-jumpbox{border-bottom:1px solid var(--color-background-border);border-top:1px solid var(--color-background-border);padding:.25rem}.domainindex-section h2,.genindex-section h2{margin-bottom:.5rem;margin-top:.75rem}.domainindex-section ul,.genindex-section ul{margin-bottom:0;margin-top:0}ol,ul{margin-bottom:1rem;margin-top:1rem;padding-left:1.2rem}ol li>p:first-child,ul li>p:first-child{margin-bottom:.25rem;margin-top:.25rem}ol li>p:last-child,ul li>p:last-child{margin-top:.25rem}ol li>ol,ol li>ul,ul li>ol,ul li>ul{margin-bottom:.5rem;margin-top:.5rem}ol.arabic{list-style:decimal}ol.loweralpha{list-style:lower-alpha}ol.upperalpha{list-style:upper-alpha}ol.lowerroman{list-style:lower-roman}ol.upperroman{list-style:upper-roman}.simple li>ol,.simple li>ul,.toctree-wrapper li>ol,.toctree-wrapper li>ul{margin-bottom:0;margin-top:0}.field-list dt,.option-list dt,dl.footnote dt,dl.glossary dt,dl.simple dt,dl:not([class]) dt{font-weight:500;margin-top:.25rem}.field-list dt+dt,.option-list dt+dt,dl.footnote dt+dt,dl.glossary dt+dt,dl.simple dt+dt,dl:not([class]) dt+dt{margin-top:0}.field-list dt .classifier:before,.option-list dt .classifier:before,dl.footnote dt .classifier:before,dl.glossary dt .classifier:before,dl.simple dt .classifier:before,dl:not([class]) dt .classifier:before{content:\":\";margin-left:.2rem;margin-right:.2rem}.field-list dd ul,.field-list dd>p:first-child,.option-list dd ul,.option-list dd>p:first-child,dl.footnote dd ul,dl.footnote dd>p:first-child,dl.glossary dd ul,dl.glossary dd>p:first-child,dl.simple dd ul,dl.simple dd>p:first-child,dl:not([class]) dd ul,dl:not([class]) dd>p:first-child{margin-top:.125rem}.field-list dd ul,.option-list dd ul,dl.footnote dd ul,dl.glossary dd ul,dl.simple dd ul,dl:not([class]) dd ul{margin-bottom:.125rem}.math-wrapper{overflow-x:auto;width:100%}div.math{position:relative;text-align:center}div.math .headerlink,div.math:focus .headerlink{display:none}div.math:hover .headerlink{display:inline-block}div.math span.eqno{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);z-index:1}abbr[title]{cursor:help}.problematic{color:var(--color-problematic)}kbd:not(.compound){background-color:var(--color-background-secondary);border:1px solid var(--color-foreground-border);border-radius:.2rem;box-shadow:0 .0625rem 0 rgba(0,0,0,.2),inset 0 0 0 .125rem var(--color-background-primary);color:var(--color-foreground-primary);display:inline-block;font-size:var(--font-size--small--3);margin:0 .2rem;padding:0 .2rem;vertical-align:text-bottom}blockquote{background:var(--color-background-secondary);border-left:4px solid var(--color-background-border);margin-left:0;margin-right:0;padding:.5rem 1rem}blockquote .attribution{font-weight:600;text-align:right}blockquote.highlights,blockquote.pull-quote{font-size:1.25em}blockquote.epigraph,blockquote.pull-quote{border-left-width:0;border-radius:.5rem}blockquote.highlights{background:transparent;border-left-width:0}p .reference img{vertical-align:middle}p.rubric{font-size:1.125em;font-weight:700;line-height:1.25}dd p.rubric{font-size:var(--font-size--small);font-weight:inherit;line-height:inherit;text-transform:uppercase}article .sidebar{background-color:var(--color-background-secondary);border:1px solid var(--color-background-border);border-radius:.2rem;clear:right;float:right;margin-left:1rem;margin-right:0;width:30%}article .sidebar>*{padding-left:1rem;padding-right:1rem}article .sidebar>ol,article .sidebar>ul{padding-left:2.2rem}article .sidebar .sidebar-title{border-bottom:1px solid var(--color-background-border);font-weight:500;margin:0;padding:.5rem 1rem}[role=main] .table-wrapper.container{margin-bottom:.5rem;margin-top:1rem;overflow-x:auto;padding:.2rem .2rem .75rem;width:100%}table.docutils{border-collapse:collapse;border-radius:.2rem;border-spacing:0;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1)}table.docutils th{background:var(--color-table-header-background)}table.docutils td,table.docutils th{border-bottom:1px solid var(--color-table-border);border-left:1px solid var(--color-table-border);border-right:1px solid var(--color-table-border);padding:0 .25rem}table.docutils td p,table.docutils th p{margin:.25rem}table.docutils td:first-child,table.docutils th:first-child{border-left:none}table.docutils td:last-child,table.docutils th:last-child{border-right:none}table.docutils td.text-left,table.docutils th.text-left{text-align:left}table.docutils td.text-right,table.docutils th.text-right{text-align:right}table.docutils td.text-center,table.docutils th.text-center{text-align:center}:target{scroll-margin-top:2.5rem}@media(max-width:67em){:target{scroll-margin-top:calc(2.5rem + var(--header-height))}section>span:target{scroll-margin-top:calc(2.8rem + var(--header-height))}}.headerlink{font-weight:100;-webkit-user-select:none;-moz-user-select:none;user-select:none}.code-block-caption>.headerlink,dl dt>.headerlink,figcaption p>.headerlink,h1>.headerlink,h2>.headerlink,h3>.headerlink,h4>.headerlink,h5>.headerlink,h6>.headerlink,p.caption>.headerlink,table>caption>.headerlink{margin-left:.5rem;visibility:hidden}.code-block-caption:hover>.headerlink,dl dt:hover>.headerlink,figcaption p:hover>.headerlink,h1:hover>.headerlink,h2:hover>.headerlink,h3:hover>.headerlink,h4:hover>.headerlink,h5:hover>.headerlink,h6:hover>.headerlink,p.caption:hover>.headerlink,table>caption:hover>.headerlink{visibility:visible}.code-block-caption>.toc-backref,dl dt>.toc-backref,figcaption p>.toc-backref,h1>.toc-backref,h2>.toc-backref,h3>.toc-backref,h4>.toc-backref,h5>.toc-backref,h6>.toc-backref,p.caption>.toc-backref,table>caption>.toc-backref{color:inherit;text-decoration-line:none}figure:hover>figcaption>p>.headerlink,table:hover>caption>.headerlink{visibility:visible}:target>h1:first-of-type,:target>h2:first-of-type,:target>h3:first-of-type,:target>h4:first-of-type,:target>h5:first-of-type,:target>h6:first-of-type,span:target~h1:first-of-type,span:target~h2:first-of-type,span:target~h3:first-of-type,span:target~h4:first-of-type,span:target~h5:first-of-type,span:target~h6:first-of-type{background-color:var(--color-highlight-on-target)}:target>h1:first-of-type code.literal,:target>h2:first-of-type code.literal,:target>h3:first-of-type code.literal,:target>h4:first-of-type code.literal,:target>h5:first-of-type code.literal,:target>h6:first-of-type code.literal,span:target~h1:first-of-type code.literal,span:target~h2:first-of-type code.literal,span:target~h3:first-of-type code.literal,span:target~h4:first-of-type code.literal,span:target~h5:first-of-type code.literal,span:target~h6:first-of-type code.literal{background-color:transparent}.literal-block-wrapper:target .code-block-caption,.this-will-duplicate-information-and-it-is-still-useful-here li :target,figure:target,table:target>caption{background-color:var(--color-highlight-on-target)}dt:target{background-color:var(--color-highlight-on-target)!important}.footnote-reference:target,.footnote>dt:target+dd{background-color:var(--color-highlight-on-target)}.guilabel{background-color:var(--color-guilabel-background);border:1px solid var(--color-guilabel-border);border-radius:.5em;color:var(--color-guilabel-text);font-size:.9em;padding:0 .3em}footer{display:flex;flex-direction:column;font-size:var(--font-size--small);margin-top:2rem}.bottom-of-page{align-items:center;border-top:1px solid var(--color-background-border);color:var(--color-foreground-secondary);display:flex;justify-content:space-between;line-height:1.5;margin-top:1rem;padding-bottom:1rem;padding-top:1rem}@media(max-width:46em){.bottom-of-page{flex-direction:column-reverse;gap:.25rem;text-align:center}}.bottom-of-page .left-details{font-size:var(--font-size--small)}.bottom-of-page .right-details{display:flex;flex-direction:column;gap:.25rem;text-align:right}.bottom-of-page .icons{display:flex;font-size:1rem;gap:.25rem;justify-content:flex-end}.bottom-of-page .icons a{text-decoration:none}.bottom-of-page .icons img,.bottom-of-page .icons svg{font-size:1.125rem;height:1em;width:1em}.related-pages a{align-items:center;display:flex;text-decoration:none}.related-pages a:hover .page-info .title{color:var(--color-link);text-decoration:underline;text-decoration-color:var(--color-link-underline)}.related-pages a svg.furo-related-icon,.related-pages a svg.furo-related-icon>use{color:var(--color-foreground-border);flex-shrink:0;height:.75rem;margin:0 .5rem;width:.75rem}.related-pages a.next-page{clear:right;float:right;max-width:50%;text-align:right}.related-pages a.prev-page{clear:left;float:left;max-width:50%}.related-pages a.prev-page svg{transform:rotate(180deg)}.page-info{display:flex;flex-direction:column;overflow-wrap:anywhere}.next-page .page-info{align-items:flex-end}.page-info .context{align-items:center;color:var(--color-foreground-muted);display:flex;font-size:var(--font-size--small);padding-bottom:.1rem;text-decoration:none}ul.search{list-style:none;padding-left:0}ul.search li{border-bottom:1px solid var(--color-background-border);padding:1rem 0}[role=main] .highlighted{background-color:var(--color-highlighted-background);color:var(--color-highlighted-text)}.sidebar-brand{display:flex;flex-direction:column;flex-shrink:0;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal);text-decoration:none}.sidebar-brand-text{color:var(--color-sidebar-brand-text);font-size:1.5rem;overflow-wrap:break-word}.sidebar-brand-text,.sidebar-logo-container{margin:var(--sidebar-item-spacing-vertical) 0}.sidebar-logo{display:block;margin:0 auto;max-width:100%}.sidebar-search-container{align-items:center;background:var(--color-sidebar-search-background);display:flex;margin-top:var(--sidebar-search-space-above);position:relative}.sidebar-search-container:focus-within,.sidebar-search-container:hover{background:var(--color-sidebar-search-background--focus)}.sidebar-search-container:before{background-color:var(--color-sidebar-search-icon);content:\"\";height:var(--sidebar-search-icon-size);left:var(--sidebar-item-spacing-horizontal);-webkit-mask-image:var(--icon-search);mask-image:var(--icon-search);position:absolute;width:var(--sidebar-search-icon-size)}.sidebar-search{background:transparent;border:none;border-bottom:1px solid var(--color-sidebar-search-border);border-top:1px solid var(--color-sidebar-search-border);box-sizing:border-box;color:var(--color-sidebar-search-foreground);padding:var(--sidebar-search-input-spacing-vertical) var(--sidebar-search-input-spacing-horizontal) var(--sidebar-search-input-spacing-vertical) calc(var(--sidebar-item-spacing-horizontal) + var(--sidebar-search-input-spacing-horizontal) + var(--sidebar-search-icon-size));width:100%;z-index:10}.sidebar-search:focus{outline:none}.sidebar-search::-moz-placeholder{font-size:var(--sidebar-search-input-font-size)}.sidebar-search::placeholder{font-size:var(--sidebar-search-input-font-size)}#searchbox .highlight-link{margin:0;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal) 0;text-align:center}#searchbox .highlight-link a{color:var(--color-sidebar-search-icon);font-size:var(--font-size--small--2)}.sidebar-tree{font-size:var(--sidebar-item-font-size);margin-bottom:var(--sidebar-item-spacing-vertical);margin-top:var(--sidebar-tree-space-above)}.sidebar-tree ul{display:flex;flex-direction:column;list-style:none;margin-bottom:0;margin-top:0;padding:0}.sidebar-tree li{margin:0;position:relative}.sidebar-tree li>ul{margin-left:var(--sidebar-item-spacing-horizontal)}.sidebar-tree .icon,.sidebar-tree .reference{color:var(--color-sidebar-link-text)}.sidebar-tree .reference{box-sizing:border-box;display:inline-block;height:100%;line-height:var(--sidebar-item-line-height);overflow-wrap:anywhere;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal);text-decoration:none;width:100%}.sidebar-tree .reference:hover{background:var(--color-sidebar-item-background--hover);color:var(--color-sidebar-link-text)}.sidebar-tree .reference.external:after{color:var(--color-sidebar-link-text);content:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23607D8B' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' viewBox='0 0 24 24'%3E%3Cpath stroke='none' d='M0 0h24v24H0z'/%3E%3Cpath d='M11 7H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-5M10 14 20 4M15 4h5v5'/%3E%3C/svg%3E\");margin:0 .25rem;vertical-align:middle}.sidebar-tree .current-page>.reference{font-weight:700}.sidebar-tree label{align-items:center;cursor:pointer;display:flex;height:var(--sidebar-item-height);justify-content:center;position:absolute;right:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:var(--sidebar-expander-width)}.sidebar-tree .caption,.sidebar-tree :not(.caption)>.caption-text{color:var(--color-sidebar-caption-text);font-size:var(--sidebar-caption-font-size);font-weight:700;margin:var(--sidebar-caption-space-above) 0 0 0;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal);text-transform:uppercase}.sidebar-tree li.has-children>.reference{padding-right:var(--sidebar-expander-width)}.sidebar-tree .toctree-l1>.reference,.sidebar-tree .toctree-l1>label .icon{color:var(--color-sidebar-link-text--top-level)}.sidebar-tree label{background:var(--color-sidebar-item-expander-background)}.sidebar-tree label:hover{background:var(--color-sidebar-item-expander-background--hover)}.sidebar-tree .current>.reference{background:var(--color-sidebar-item-background--current)}.sidebar-tree .current>.reference:hover{background:var(--color-sidebar-item-background--hover)}.toctree-checkbox{display:none;position:absolute}.toctree-checkbox~ul{display:none}.toctree-checkbox~label .icon svg{transform:rotate(90deg)}.toctree-checkbox:checked~ul{display:block}.toctree-checkbox:checked~label .icon svg{transform:rotate(-90deg)}.toc-title-container{padding:var(--toc-title-padding);padding-top:var(--toc-spacing-vertical)}.toc-title{color:var(--color-toc-title-text);font-size:var(--toc-title-font-size);padding-left:var(--toc-spacing-horizontal);text-transform:uppercase}.no-toc{display:none}.toc-tree-container{padding-bottom:var(--toc-spacing-vertical)}.toc-tree{border-left:1px solid var(--color-background-border);font-size:var(--toc-font-size);line-height:1.3;padding-left:calc(var(--toc-spacing-horizontal) - var(--toc-item-spacing-horizontal))}.toc-tree>ul>li:first-child{padding-top:0}.toc-tree>ul>li:first-child>ul{padding-left:0}.toc-tree>ul>li:first-child>a{display:none}.toc-tree ul{list-style-type:none;margin-bottom:0;margin-top:0;padding-left:var(--toc-item-spacing-horizontal)}.toc-tree li{padding-top:var(--toc-item-spacing-vertical)}.toc-tree li.scroll-current>.reference{color:var(--color-toc-item-text--active);font-weight:700}.toc-tree a.reference{color:var(--color-toc-item-text);overflow-wrap:anywhere;text-decoration:none}.toc-scroll{max-height:100vh;overflow-y:scroll}.contents:not(.this-will-duplicate-information-and-it-is-still-useful-here){background:rgba(255,0,0,.25);color:var(--color-problematic)}.contents:not(.this-will-duplicate-information-and-it-is-still-useful-here):before{content:\"ERROR: Adding a table of contents in Furo-based documentation is unnecessary, and does not work well with existing styling. Add a 'this-will-duplicate-information-and-it-is-still-useful-here' class, if you want an escape hatch.\"}.text-align\\:left>p{text-align:left}.text-align\\:center>p{text-align:center}.text-align\\:right>p{text-align:right}\n/*# sourceMappingURL=furo.css.map*/"
  },
  {
    "path": "docs/WechatAPIClient/_static/translations.js",
    "content": "Documentation.addTranslations({\n    \"locale\": \"zh_Hans_CN\",\n    \"messages\": {\n        \"%(filename)s &#8212; %(docstitle)s\": \"%(filename)s &#8212; %(docstitle)s\",\n        \"&#169; %(copyright_prefix)s %(copyright)s.\": \"&#169; %(copyright_prefix)s %(copyright)s.\",\n        \", in \": \"\\uff0c\\u5728 \",\n        \"About these documents\": \"\\u5173\\u4e8e\\u6b64\\u6587\\u6863\",\n        \"Automatically generated list of changes in version %(version)s\": \"\\u81ea\\u52a8\\u751f\\u6210\\u7684 %(version)s \\u7248\\u672c\\u53d8\\u66f4\\u5217\\u8868\",\n        \"C API changes\": \"C API \\u7684\\u53d8\\u66f4\",\n        \"Changes in Version %(version)s &#8212; %(docstitle)s\": \"\\u4e8e\\u7248\\u672c %(version)s&#8212; %(docstitle)s \\u53d8\\u66f4\",\n        \"Collapse sidebar\": \"\\u6298\\u53e0\\u8fb9\\u680f\",\n        \"Complete Table of Contents\": \"\\u5b8c\\u6574\\u76ee\\u5f55\",\n        \"Contents\": \"\\u76ee\\u5f55\",\n        \"Copyright\": \"\\u7248\\u6743\\u6240\\u6709\",\n        \"Created using <a href=\\\"https://www.sphinx-doc.org/\\\">Sphinx</a> %(sphinx_version)s.\": \"\\u7531 <a href=\\\"https://www.sphinx-doc.org/\\\">Sphinx</a> %(sphinx_version)s\\u521b\\u5efa\\u3002\",\n        \"Expand sidebar\": \"\\u5c55\\u5f00\\u8fb9\\u680f\",\n        \"Full index on one page\": \"\\u5355\\u9875\\u5168\\u7d22\\u5f15\",\n        \"General Index\": \"\\u603b\\u7d22\\u5f15\",\n        \"Global Module Index\": \"\\u5168\\u5c40\\u6a21\\u5757\\u7d22\\u5f15\",\n        \"Go\": \"\\u63d0\\u4ea4\",\n        \"Hide Search Matches\": \"\\u9690\\u85cf\\u641c\\u7d22\\u7ed3\\u679c\",\n        \"Index\": \"\\u7d22\\u5f15\",\n        \"Index &ndash; %(key)s\": \"\\u7d22\\u5f15 &ndash; %(key)s\",\n        \"Index pages by letter\": \"\\u5b57\\u6bcd\\u7d22\\u5f15\",\n        \"Indices and tables:\": \"\\u7d22\\u5f15\\u548c\\u8868\\u683c\\uff1a\",\n        \"Last updated on %(last_updated)s.\": \"\\u6700\\u540e\\u66f4\\u65b0\\u4e8e %(last_updated)s.\",\n        \"Library changes\": \"\\u5e93\\u7684\\u53d8\\u66f4\",\n        \"Navigation\": \"\\u5bfc\\u822a\",\n        \"Next topic\": \"\\u4e0b\\u4e00\\u4e3b\\u9898\",\n        \"Other changes\": \"\\u5176\\u4ed6\\u53d8\\u66f4\",\n        \"Overview\": \"\\u6982\\u8ff0\",\n        \"Please activate JavaScript to enable the search\\n    functionality.\": \"\\u8bf7\\u6fc0\\u6d3b JavaScript \\u4ee5\\u5f00\\u542f\\u641c\\u7d22\\u529f\\u80fd\\u3002\",\n        \"Preparing search...\": \"\\u6b63\\u5728\\u51c6\\u5907\\u641c\\u7d22\\u2026\\u2026\",\n        \"Previous topic\": \"\\u4e0a\\u4e00\\u4e3b\\u9898\",\n        \"Quick search\": \"\\u5feb\\u901f\\u641c\\u7d22\",\n        \"Search\": \"\\u641c\\u7d22\",\n        \"Search Page\": \"\\u641c\\u7d22\\u9875\\u9762\",\n        \"Search Results\": \"\\u641c\\u7d22\\u7ed3\\u679c\",\n        \"Search finished, found ${resultCount} page(s) matching the search query.\": \"\\u641c\\u7d22\\u5b8c\\u6210\\uff0c\\u5339\\u914d\\u5230 ${resultCount} \\u9875\\u3002\",\n        \"Search within %(docstitle)s\": \"\\u5728 %(docstitle)s \\u4e2d\\u641c\\u7d22\",\n        \"Searching\": \"\\u6b63\\u5728\\u641c\\u7d22\\u4e2d\",\n        \"Searching for multiple words only shows matches that contain\\n    all words.\": \"\\u5f53\\u641c\\u7d22\\u591a\\u4e2a\\u5173\\u952e\\u8bcd\\u65f6\\uff0c\\u53ea\\u4f1a\\u663e\\u793a\\u540c\\u65f6\\u5305\\u542b\\u6240\\u6709\\u5173\\u952e\\u8bcd\\u7684\\u5185\\u5bb9\\u3002\",\n        \"Show Source\": \"\\u663e\\u793a\\u6e90\\u4ee3\\u7801\",\n        \"Table of Contents\": \"\\u76ee\\u5f55\",\n        \"This Page\": \"\\u672c\\u9875\",\n        \"Welcome! This is\": \"\\u6b22\\u8fce\\uff01\",\n        \"Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories.\": \"\\u60a8\\u7684\\u641c\\u7d22\\u6ca1\\u6709\\u5339\\u914d\\u5230\\u6587\\u6863\\u3002\\u8bf7\\u786e\\u4fdd\\u5173\\u952e\\u8bcd\\u62fc\\u5199\\u6b63\\u786e\\uff0c\\u5e76\\u4e14\\u9009\\u62e9\\u4e86\\u5408\\u9002\\u7684\\u5206\\u7c7b\\u3002\",\n        \"all functions, classes, terms\": \"\\u6240\\u6709\\u51fd\\u6570\\u3001\\u7c7b\\u3001\\u672f\\u8bed\\u8bcd\\u6c47\",\n        \"can be huge\": \"\\u53ef\\u80fd\\u4f1a\\u5927\",\n        \"last updated\": \"\\u6700\\u540e\\u66f4\\u65b0\\u4e8e\",\n        \"lists all sections and subsections\": \"\\u5217\\u51fa\\u6240\\u6709\\u7684\\u7ae0\\u8282\\u548c\\u90e8\\u5206\",\n        \"next chapter\": \"\\u4e0b\\u4e00\\u7ae0\",\n        \"previous chapter\": \"\\u4e0a\\u4e00\\u7ae0\",\n        \"quick access to all modules\": \"\\u5feb\\u901f\\u67e5\\u770b\\u6240\\u6709\\u7684\\u6a21\\u5757\",\n        \"search\": \"\\u641c\\u7d22\",\n        \"search this documentation\": \"\\u641c\\u7d22\\u6587\\u6863\",\n        \"the documentation for\": \"\\u672c\\u6587\\u6863\\u5c5e\\u4e8e\"\n    },\n    \"plural_expr\": \"0\"\n});"
  },
  {
    "path": "docs/WechatAPIClient/genindex.html",
    "content": "<!doctype html>\n<html class=\"no-js\" data-content_root=\"./\" lang=\"zh-CN\">\n<head>\n    <meta charset=\"utf-8\"/>\n    <meta content=\"width=device-width,initial-scale=1\" name=\"viewport\"/>\n    <meta content=\"light dark\" name=\"color-scheme\">\n    <link href=\"#\" rel=\"index\" title=\"索引\"/>\n    <link href=\"search.html\" rel=\"search\" title=\"搜索\"/>\n\n    <!-- Generated with Sphinx 8.1.3 and Furo 2024.08.06 --><title>索引 - XYBotV2</title>\n    <link href=\"_static/pygments.css?v=8f2a1f02\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"_static/styles/furo.css?v=354aac6f\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"_static/styles/furo-extensions.css?v=302659d7\" rel=\"stylesheet\" type=\"text/css\"/>\n\n\n    <style>\n        body {\n            --color-code-background: #f8f8f8;\n            --color-code-foreground: black;\n            --color-brand-primary: #2962ff;\n            --color-brand-content: #2962ff;\n\n        }\n\n        @media not print {\n            body[data-theme=\"dark\"] {\n                --color-code-background: #202020;\n                --color-code-foreground: #d0d0d0;\n\n            }\n\n            @media (prefers-color-scheme: dark) {\n                body:not([data-theme=\"light\"]) {\n                    --color-code-background: #202020;\n                    --color-code-foreground: #d0d0d0;\n\n                }\n            }\n        }\n    </style>\n</head>\n<body>\n\n<script>\n    document.body.dataset.theme = localStorage.getItem(\"theme\") || \"auto\";\n</script>\n\n\n<svg style=\"display: none;\" xmlns=\"http://www.w3.org/2000/svg\">\n    <symbol id=\"svg-toc\" viewBox=\"0 0 24 24\">\n        <title>Contents</title>\n        <svg fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 1024 1024\">\n            <path d=\"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-menu\" viewBox=\"0 0 24 24\">\n        <title>Menu</title>\n        <svg class=\"feather-menu\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-arrow-right\" viewBox=\"0 0 24 24\">\n        <title>Expand</title>\n        <svg class=\"feather-chevron-right\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <polyline points=\"9 18 15 12 9 6\"></polyline>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun\" viewBox=\"0 0 24 24\">\n        <title>Light mode</title>\n        <svg class=\"feather-sun\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\n            <line x1=\"12\" x2=\"12\" y1=\"1\" y2=\"3\"></line>\n            <line x1=\"12\" x2=\"12\" y1=\"21\" y2=\"23\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"4.22\" y2=\"5.64\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"18.36\" y2=\"19.78\"></line>\n            <line x1=\"1\" x2=\"3\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"21\" x2=\"23\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"19.78\" y2=\"18.36\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"5.64\" y2=\"4.22\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon\" viewBox=\"0 0 24 24\">\n        <title>Dark mode</title>\n        <svg class=\"icon-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun-with-moon\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in light mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 5.411 14.504 C 5.471 14.504 5.532 14.504 5.591 14.504 C 3.639 16.319 4.383 19.569 6.931 20.352 C 7.693 20.586 8.512 20.551 9.25 20.252 C 8.023 23.207 4.056 23.725 2.11 21.184 C 0.166 18.642 1.702 14.949 4.874 14.536 C 5.051 14.512 5.231 14.5 5.411 14.5 L 5.411 14.504 Z\"\n                  style=\"opacity: 50%\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"3.25\" y2=\"1.25\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"15.85\" y2=\"17.85\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"5.094\" y2=\"3.68\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"14.05\" y2=\"15.464\"/>\n            <line x1=\"8.2\" x2=\"6.2\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"20.8\" x2=\"22.8\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"14.006\" y2=\"15.42\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"5.05\" y2=\"3.636\"/>\n            <circle cx=\"14.5\" cy=\"9.55\" r=\"3.6\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon-with-sun\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in dark mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 8.282 7.007 C 8.385 7.007 8.494 7.007 8.595 7.007 C 5.18 10.184 6.481 15.869 10.942 17.24 C 12.275 17.648 13.706 17.589 15 17.066 C 12.851 22.236 5.91 23.143 2.505 18.696 C -0.897 14.249 1.791 7.786 7.342 7.063 C 7.652 7.021 7.965 7 8.282 7 L 8.282 7.007 Z\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"3.705\" y2=\"2.5\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"11.295\" y2=\"12.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"4.816\" y2=\"3.964\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"10.212\" y2=\"11.063\"/>\n            <line style=\"opacity: 50%\" x1=\"14.205\" x2=\"13.001\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"21.795\" x2=\"23\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"10.184\" y2=\"11.036\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"4.789\" y2=\"3.937\"/>\n            <circle cx=\"18\" cy=\"7.5\" r=\"2.169\" style=\"opacity: 50%\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-pencil\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-pencil-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4\"/>\n            <path d=\"M13.5 6.5l4 4\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-eye\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-eye-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\"/>\n            <path\n                    d=\"M11.11 17.958c-3.209 -.307 -5.91 -2.293 -8.11 -5.958c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6c-.21 .352 -.427 .688 -.647 1.008\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n</svg>\n\n<input class=\"sidebar-toggle\" id=\"__navigation\" name=\"__navigation\" type=\"checkbox\">\n<input class=\"sidebar-toggle\" id=\"__toc\" name=\"__toc\" type=\"checkbox\">\n<label class=\"overlay sidebar-overlay\" for=\"__navigation\">\n    <div class=\"visually-hidden\">Hide navigation sidebar</div>\n</label>\n<label class=\"overlay toc-overlay\" for=\"__toc\">\n    <div class=\"visually-hidden\">Hide table of contents sidebar</div>\n</label>\n\n<a class=\"skip-to-content muted-link\" href=\"#furo-main-content\">Skip to content</a>\n\n\n\n<div class=\"page\">\n    <header class=\"mobile-header\">\n        <div class=\"header-left\">\n            <label class=\"nav-overlay-icon\" for=\"__navigation\">\n                <div class=\"visually-hidden\">Toggle site navigation sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-menu\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n        <div class=\"header-center\">\n            <a href=\"index.html\">\n                <div class=\"brand\">XYBotV2</div>\n            </a>\n        </div>\n        <div class=\"header-right\">\n            <div class=\"theme-toggle-container theme-toggle-header\">\n                <button class=\"theme-toggle\">\n                    <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                    <svg class=\"theme-icon-when-auto-light\">\n                        <use href=\"#svg-sun-with-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-auto-dark\">\n                        <use href=\"#svg-moon-with-sun\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-dark\">\n                        <use href=\"#svg-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-light\">\n                        <use href=\"#svg-sun\"></use>\n                    </svg>\n                </button>\n            </div>\n            <label class=\"toc-overlay-icon toc-header-icon no-toc\" for=\"__toc\">\n                <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-toc\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n    </header>\n    <aside class=\"sidebar-drawer\">\n        <div class=\"sidebar-container\">\n\n            <div class=\"sidebar-sticky\">\n                <div class=\"sidebar-scroll\"><a class=\"sidebar-brand\" href=\"index.html\">\n\n\n                    <span class=\"sidebar-brand-text\">XYBotV2</span>\n\n                </a>\n                    <form action=\"search.html\" class=\"sidebar-search-container\" method=\"get\" role=\"search\">\n                        <input aria-label=\"搜索\" class=\"sidebar-search\" name=\"q\" placeholder=\"搜索\">\n                        <input name=\"check_keywords\" type=\"hidden\" value=\"yes\">\n                        <input name=\"area\" type=\"hidden\" value=\"default\">\n                    </form>\n                    <div id=\"searchbox\"></div>\n                    <div class=\"sidebar-tree\">\n\n                    </div>\n                    <div class=\"sidebar-tree\">\n                        <p class=\"caption\"><span class=\"caption-text\">重要函数导航</span></p>\n                        <ul>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">登录</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.is_running\">检查WechatAPI是否在运行</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_qr_code\">获取登录二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.awaken_login\">二次登录(唤醒登录)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.check_login_uuid\">检查登录的UUID状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_cached_info\">获取登录缓存信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.log_out\">登出当前账号</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.heartbeat\">发送心跳包</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\">开始自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\">停止自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\">获取自动心跳状态</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">消息</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.sync_message\">同步消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_text_message\">发送文本消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_image_message\">发送图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_voice_message\">发送语音消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_video_message\">发送视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_link_message\">发送链接消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_card_message\">发送名片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_app_message\">发送应用(xml)消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_emoji_message\">发送表情消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\">转发图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\">转发视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\">转发文件消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.revoke_message\">撤回消息</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">用户</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_my_qrcode\">获取个人二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_profile\">获取用户信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.is_logged_in\">检查是否登录</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">群聊</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\">获取群聊信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\">获取群聊公告</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\">获取群聊成员列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\">获取群聊二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\">添加群成员(群聊最多40人)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\">邀请群聊成员(群聊大于40人)</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">好友</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contact\">获取联系人信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_detail\">获取联系人详情</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_list\">获取联系人列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_nickname\">获取用户昵称</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.accept_friend\">接受好友请求</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">红包</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\">获取红包详情</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">工具</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.check_database\">检查数据库状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.set_step\">设置步数</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_image\">下载高清图片</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_video\">下载视频</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_voice\">下载语音文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_attach\">下载附件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_byte\">base64转字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_file\">base64转文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.byte_to_base64\">字节转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.file_to_base64\">文件转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\">silk的base64转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\">silk字节转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\">WAV字节转AMR的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\">WAV字节转AMR字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\">WAV字节转silk的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\">WAV字节转silk字节</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                        </ul>\n                    </div>\n                </div>\n            </div>\n\n        </div>\n    </aside>\n    <div class=\"main\">\n        <div class=\"content\">\n            <div class=\"article-container\">\n                <a class=\"back-to-top muted-link\" href=\"#\">\n                    <svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n                        <path d=\"M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z\"></path>\n                    </svg>\n                    <span>Back to top</span>\n                </a>\n                <div class=\"content-icon-container\">\n                    <div class=\"theme-toggle-container theme-toggle-content\">\n                        <button class=\"theme-toggle\">\n                            <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                            <svg class=\"theme-icon-when-auto-light\">\n                                <use href=\"#svg-sun-with-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-auto-dark\">\n                                <use href=\"#svg-moon-with-sun\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-dark\">\n                                <use href=\"#svg-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-light\">\n                                <use href=\"#svg-sun\"></use>\n                            </svg>\n                        </button>\n                    </div>\n                    <label class=\"toc-overlay-icon toc-content-icon no-toc\" for=\"__toc\">\n                        <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                        <i class=\"icon\">\n                            <svg>\n                                <use href=\"#svg-toc\"></use>\n                            </svg>\n                        </i>\n                    </label>\n                </div>\n                <article id=\"furo-main-content\" role=\"main\">\n\n                    <section class=\"genindex-section\">\n                        <h1 id=\"index\">索引</h1>\n                        <div class=\"genindex-jumpbox\"><a href=\"#_\"><strong>_</strong></a> | <a\n                                href=\"#A\"><strong>A</strong></a> | <a href=\"#B\"><strong>B</strong></a> | <a\n                                href=\"#C\"><strong>C</strong></a> | <a href=\"#D\"><strong>D</strong></a> | <a\n                                href=\"#E\"><strong>E</strong></a> | <a href=\"#F\"><strong>F</strong></a> | <a\n                                href=\"#G\"><strong>G</strong></a> | <a href=\"#H\"><strong>H</strong></a> | <a\n                                href=\"#I\"><strong>I</strong></a> | <a href=\"#L\"><strong>L</strong></a> | <a\n                                href=\"#M\"><strong>M</strong></a> | <a href=\"#P\"><strong>P</strong></a> | <a\n                                href=\"#R\"><strong>R</strong></a> | <a href=\"#S\"><strong>S</strong></a> | <a\n                                href=\"#T\"><strong>T</strong></a> | <a href=\"#U\"><strong>U</strong></a> | <a\n                                href=\"#W\"><strong>W</strong></a></div>\n                    </section>\n                    <section class=\"genindex-section\" id=\"_\">\n                        <h2>_</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.protect.Singleton.__call__\">__call__()\n                                            （WechatAPI.Client.protect.Singleton 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.protect.Protect.__init__\">__init__()\n                                            （WechatAPI.Client.protect.Protect 方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.message.MessageMixin._process_message_queue\">_process_message_queue()\n                                                （WechatAPI.Client.message.MessageMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.message.MessageMixin._queue_message\">_queue_message()\n                                            （WechatAPI.Client.message.MessageMixin 方法）</a>\n                                        </li>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.message.MessageMixin._send_text_message\">_send_text_message()\n                                                （WechatAPI.Client.message.MessageMixin 方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n                    <section class=\"genindex-section\" id=\"A\">\n                        <h2>A</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.friend.FriendMixin.accept_friend\">accept_friend()\n                                            （WechatAPI.Client.friend.FriendMixin 方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\">add_chatroom_member()\n                                                （WechatAPI.Client.chatroom.ChatroomMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.login.LoginMixin.awaken_login\">awaken_login()\n                                            （WechatAPI.Client.login.LoginMixin 方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n                    <section class=\"genindex-section\" id=\"B\">\n                        <h2>B</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_byte\">base64_to_byte()（WechatAPI.Client.tool.ToolMixin\n                                            静态方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_file\">base64_to_file()（WechatAPI.Client.tool.ToolMixin\n                                            静态方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.tool.ToolMixin.byte_to_base64\">byte_to_base64()（WechatAPI.Client.tool.ToolMixin\n                                            静态方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n                    <section class=\"genindex-section\" id=\"C\">\n                        <h2>C</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin\">ChatroomMixin（WechatAPI.Client.chatroom\n                                            中的类）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.protect.Protect.check\">check()\n                                            （WechatAPI.Client.protect.Protect 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.tool.ToolMixin.check_database\">check_database()\n                                            （WechatAPI.Client.tool.ToolMixin 方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.login.LoginMixin.check_login_uuid\">check_login_uuid()\n                                            （WechatAPI.Client.login.LoginMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.login.LoginMixin.create_device_id\">create_device_id()（WechatAPI.Client.login.LoginMixin\n                                            静态方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.login.LoginMixin.create_device_name\">create_device_name()（WechatAPI.Client.login.LoginMixin\n                                            静态方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n                    <section class=\"genindex-section\" id=\"D\">\n                        <h2>D</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.base.Section.data_len\">data_len（WechatAPI.Client.base.Section\n                                            属性）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_attach\">download_attach()\n                                            （WechatAPI.Client.tool.ToolMixin 方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_image\">download_image()\n                                            （WechatAPI.Client.tool.ToolMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_video\">download_video()\n                                            （WechatAPI.Client.tool.ToolMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_voice\">download_voice()\n                                            （WechatAPI.Client.tool.ToolMixin 方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n                    <section class=\"genindex-section\" id=\"E\">\n                        <h2>E</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.base.WechatAPIClientBase.error_handler\">error_handler()（WechatAPI.Client.base.WechatAPIClientBase\n                                                静态方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n                    <section class=\"genindex-section\" id=\"F\">\n                        <h2>F</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.tool.ToolMixin.file_to_base64\">file_to_base64()（WechatAPI.Client.tool.ToolMixin\n                                            静态方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.friend.FriendMixin\">FriendMixin（WechatAPI.Client.friend\n                                            中的类）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n                    <section class=\"genindex-section\" id=\"G\">\n                        <h2>G</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\">get_auto_heartbeat_status()\n                                                （WechatAPI.Client.login.LoginMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.login.LoginMixin.get_cached_info\">get_cached_info()\n                                            （WechatAPI.Client.login.LoginMixin 方法）</a>\n                                        </li>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\">get_chatroom_announce()\n                                                （WechatAPI.Client.chatroom.ChatroomMixin 方法）</a>\n                                        </li>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\">get_chatroom_info()\n                                                （WechatAPI.Client.chatroom.ChatroomMixin 方法）</a>\n                                        </li>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\">get_chatroom_member_list()\n                                                （WechatAPI.Client.chatroom.ChatroomMixin 方法）</a>\n                                        </li>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\">get_chatroom_qrcode()\n                                                （WechatAPI.Client.chatroom.ChatroomMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contact\">get_contact()\n                                            （WechatAPI.Client.friend.FriendMixin 方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_detail\">get_contract_detail()\n                                                （WechatAPI.Client.friend.FriendMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_list\">get_contract_list()\n                                            （WechatAPI.Client.friend.FriendMixin 方法）</a>\n                                        </li>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\">get_hongbao_detail()\n                                                （WechatAPI.Client.hongbao.HongBaoMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.user.UserMixin.get_my_qrcode\">get_my_qrcode()\n                                            （WechatAPI.Client.user.UserMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_nickname\">get_nickname()\n                                            （WechatAPI.Client.friend.FriendMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.user.UserMixin.get_profile\">get_profile()\n                                            （WechatAPI.Client.user.UserMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.login.LoginMixin.get_qr_code\">get_qr_code()\n                                            （WechatAPI.Client.login.LoginMixin 方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n                    <section class=\"genindex-section\" id=\"H\">\n                        <h2>H</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.login.LoginMixin.heartbeat\">heartbeat()\n                                            （WechatAPI.Client.login.LoginMixin 方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.hongbao.HongBaoMixin\">HongBaoMixin（WechatAPI.Client.hongbao\n                                            中的类）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n                    <section class=\"genindex-section\" id=\"I\">\n                        <h2>I</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\">invite_chatroom_member()\n                                                （WechatAPI.Client.chatroom.ChatroomMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.base.Proxy.ip\">ip（WechatAPI.Client.base.Proxy\n                                            属性）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.user.UserMixin.is_logged_in\">is_logged_in()\n                                            （WechatAPI.Client.user.UserMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.login.LoginMixin.is_running\">is_running()\n                                            （WechatAPI.Client.login.LoginMixin 方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n                    <section class=\"genindex-section\" id=\"L\">\n                        <h2>L</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.login.LoginMixin.log_out\">log_out()\n                                            （WechatAPI.Client.login.LoginMixin 方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.login.LoginMixin\">LoginMixin（WechatAPI.Client.login\n                                            中的类）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n                    <section class=\"genindex-section\" id=\"M\">\n                        <h2>M</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.message.MessageMixin\">MessageMixin（WechatAPI.Client.message\n                                            中的类）</a>\n                                        </li>\n                                        <li>\n                                            module\n\n                                            <ul>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.base\">WechatAPI.Client.base</a>\n                                                </li>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.chatroom\">WechatAPI.Client.chatroom</a>\n                                                </li>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.friend\">WechatAPI.Client.friend</a>\n                                                </li>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.hongbao\">WechatAPI.Client.hongbao</a>\n                                                </li>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.login\">WechatAPI.Client.login</a>\n                                                </li>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.message\">WechatAPI.Client.message</a>\n                                                </li>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.protect\">WechatAPI.Client.protect</a>\n                                                </li>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.tool\">WechatAPI.Client.tool</a>\n                                                </li>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.user\">WechatAPI.Client.user</a>\n                                                </li>\n                                            </ul>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n                    <section class=\"genindex-section\" id=\"P\">\n                        <h2>P</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.base.Proxy.password\">password（WechatAPI.Client.base.Proxy\n                                            属性）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.base.Proxy.port\">port（WechatAPI.Client.base.Proxy\n                                            属性）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.protect.Protect\">Protect（WechatAPI.Client.protect\n                                            中的类）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.base.Proxy\">Proxy（WechatAPI.Client.base\n                                            中的类）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n                    <section class=\"genindex-section\" id=\"R\">\n                        <h2>R</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.message.MessageMixin.revoke_message\">revoke_message()\n                                            （WechatAPI.Client.message.MessageMixin 方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n                    <section class=\"genindex-section\" id=\"S\">\n                        <h2>S</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.base.Section\">Section（WechatAPI.Client.base\n                                            中的类）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.message.MessageMixin.send_app_message\">send_app_message()\n                                            （WechatAPI.Client.message.MessageMixin 方法）</a>\n                                        </li>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.message.MessageMixin.send_card_message\">send_card_message()\n                                                （WechatAPI.Client.message.MessageMixin 方法）</a>\n                                        </li>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\">send_cdn_file_msg()\n                                                （WechatAPI.Client.message.MessageMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\">send_cdn_img_msg()\n                                            （WechatAPI.Client.message.MessageMixin 方法）</a>\n                                        </li>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\">send_cdn_video_msg()\n                                                （WechatAPI.Client.message.MessageMixin 方法）</a>\n                                        </li>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.message.MessageMixin.send_emoji_message\">send_emoji_message()\n                                                （WechatAPI.Client.message.MessageMixin 方法）</a>\n                                        </li>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.message.MessageMixin.send_image_message\">send_image_message()\n                                                （WechatAPI.Client.message.MessageMixin 方法）</a>\n                                        </li>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.message.MessageMixin.send_link_message\">send_link_message()\n                                                （WechatAPI.Client.message.MessageMixin 方法）</a>\n                                        </li>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.message.MessageMixin.send_text_message\">send_text_message()\n                                                （WechatAPI.Client.message.MessageMixin 方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.message.MessageMixin.send_video_message\">send_video_message()\n                                                （WechatAPI.Client.message.MessageMixin 方法）</a>\n                                        </li>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.message.MessageMixin.send_voice_message\">send_voice_message()\n                                                （WechatAPI.Client.message.MessageMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.tool.ToolMixin.set_proxy\">set_proxy()\n                                            （WechatAPI.Client.tool.ToolMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.tool.ToolMixin.set_step\">set_step()\n                                            （WechatAPI.Client.tool.ToolMixin 方法）</a>\n                                        </li>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\">silk_base64_to_wav_byte()（WechatAPI.Client.tool.ToolMixin\n                                                静态方法）</a>\n                                        </li>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\">silk_byte_to_byte_wav_byte()（WechatAPI.Client.tool.ToolMixin\n                                                静态方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.protect.Singleton\">Singleton（WechatAPI.Client.protect\n                                            中的类）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\">start_auto_heartbeat()\n                                            （WechatAPI.Client.login.LoginMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.base.Section.start_pos\">start_pos（WechatAPI.Client.base.Section\n                                            属性）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\">stop_auto_heartbeat()\n                                            （WechatAPI.Client.login.LoginMixin 方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.message.MessageMixin.sync_message\">sync_message()\n                                            （WechatAPI.Client.message.MessageMixin 方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n                    <section class=\"genindex-section\" id=\"T\">\n                        <h2>T</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.tool.ToolMixin\">ToolMixin（WechatAPI.Client.tool\n                                            中的类）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n                    <section class=\"genindex-section\" id=\"U\">\n                        <h2>U</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.protect.Protect.update_login_status\">update_login_status()\n                                            （WechatAPI.Client.protect.Protect 方法）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.user.UserMixin\">UserMixin（WechatAPI.Client.user\n                                            中的类）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.base.Proxy.username\">username（WechatAPI.Client.base.Proxy\n                                            属性）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n                    <section class=\"genindex-section\" id=\"W\">\n                        <h2>W</h2>\n                        <table class=\"indextable genindextable\" style=\"width: 100%\">\n                            <tr>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li><a href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\">wav_byte_to_amr_base64()（WechatAPI.Client.tool.ToolMixin\n                                            静态方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\">wav_byte_to_amr_byte()（WechatAPI.Client.tool.ToolMixin\n                                            静态方法）</a>\n                                        </li>\n                                        <li>\n                                            <a href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\">wav_byte_to_silk_base64()（WechatAPI.Client.tool.ToolMixin\n                                                静态方法）</a>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\">wav_byte_to_silk_byte()（WechatAPI.Client.tool.ToolMixin\n                                            静态方法）</a>\n                                        </li>\n                                        <li>\n                                            WechatAPI.Client.base\n\n                                            <ul>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.base\">module</a>\n                                                </li>\n                                            </ul>\n                                        </li>\n                                        <li>\n                                            WechatAPI.Client.chatroom\n\n                                            <ul>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.chatroom\">module</a>\n                                                </li>\n                                            </ul>\n                                        </li>\n                                        <li>\n                                            WechatAPI.Client.friend\n\n                                            <ul>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.friend\">module</a>\n                                                </li>\n                                            </ul>\n                                        </li>\n                                        <li>\n                                            WechatAPI.Client.hongbao\n\n                                            <ul>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.hongbao\">module</a>\n                                                </li>\n                                            </ul>\n                                        </li>\n                                    </ul>\n                                </td>\n                                <td style=\"width: 33%; vertical-align: top;\">\n                                    <ul>\n                                        <li>\n                                            WechatAPI.Client.login\n\n                                            <ul>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.login\">module</a>\n                                                </li>\n                                            </ul>\n                                        </li>\n                                        <li>\n                                            WechatAPI.Client.message\n\n                                            <ul>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.message\">module</a>\n                                                </li>\n                                            </ul>\n                                        </li>\n                                        <li>\n                                            WechatAPI.Client.protect\n\n                                            <ul>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.protect\">module</a>\n                                                </li>\n                                            </ul>\n                                        </li>\n                                        <li>\n                                            WechatAPI.Client.tool\n\n                                            <ul>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.tool\">module</a>\n                                                </li>\n                                            </ul>\n                                        </li>\n                                        <li>\n                                            WechatAPI.Client.user\n\n                                            <ul>\n                                                <li><a href=\"index.html#module-WechatAPI.Client.user\">module</a>\n                                                </li>\n                                            </ul>\n                                        </li>\n                                        <li><a href=\"index.html#WechatAPI.Client.base.WechatAPIClientBase\">WechatAPIClientBase（WechatAPI.Client.base\n                                            中的类）</a>\n                                        </li>\n                                    </ul>\n                                </td>\n                            </tr>\n                        </table>\n                    </section>\n\n\n                </article>\n            </div>\n            <footer>\n\n                <div class=\"related-pages\">\n\n\n                </div>\n                <div class=\"bottom-of-page\">\n                    <div class=\"left-details\">\n                        <div class=\"copyright\">\n                            Copyright &#169; 2025, HenryXiaoYang\n                        </div>\n                        Made with <a href=\"https://www.sphinx-doc.org/\">Sphinx</a> and <a class=\"muted-link\"\n                                                                                          href=\"https://pradyunsg.me\">@pradyunsg</a>'s\n\n                        <a href=\"https://github.com/pradyunsg/furo\">Furo</a>\n\n                    </div>\n                    <div class=\"right-details\">\n\n                    </div>\n                </div>\n\n            </footer>\n        </div>\n        <aside class=\"toc-drawer no-toc\">\n\n\n        </aside>\n    </div>\n</div>\n<script src=\"_static/documentation_options.js?v=91bfbbb6\"></script>\n<script src=\"_static/doctools.js?v=9bcbadda\"></script>\n<script src=\"_static/sphinx_highlight.js?v=dc90522c\"></script>\n<script src=\"_static/scripts/furo.js?v=5fa4622c\"></script>\n<script src=\"_static/translations.js?v=beaddf03\"></script>\n</body>\n</html>"
  },
  {
    "path": "docs/WechatAPIClient/index.html",
    "content": "<!doctype html>\n<html class=\"no-js\" data-content_root=\"./\" lang=\"zh-CN\">\n<head>\n    <meta charset=\"utf-8\"/>\n    <meta content=\"width=device-width,initial-scale=1\" name=\"viewport\"/>\n    <meta content=\"light dark\" name=\"color-scheme\">\n    <meta content=\"width=device-width, initial-scale=1\" name=\"viewport\"/>\n    <link href=\"genindex.html\" rel=\"index\" title=\"索引\"/>\n    <link href=\"search.html\" rel=\"search\" title=\"搜索\"/>\n\n    <!-- Generated with Sphinx 8.1.3 and Furo 2024.08.06 -->\n    <title>XYBotV2</title>\n    <link href=\"_static/pygments.css?v=8f2a1f02\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"_static/styles/furo.css?v=354aac6f\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"_static/styles/furo-extensions.css?v=302659d7\" rel=\"stylesheet\" type=\"text/css\"/>\n\n\n    <style>\n        body {\n            --color-code-background: #f8f8f8;\n            --color-code-foreground: black;\n            --color-brand-primary: #2962ff;\n            --color-brand-content: #2962ff;\n\n        }\n\n        @media not print {\n            body[data-theme=\"dark\"] {\n                --color-code-background: #202020;\n                --color-code-foreground: #d0d0d0;\n\n            }\n\n            @media (prefers-color-scheme: dark) {\n                body:not([data-theme=\"light\"]) {\n                    --color-code-background: #202020;\n                    --color-code-foreground: #d0d0d0;\n\n                }\n            }\n        }\n    </style>\n</head>\n<body>\n\n<script>\n    document.body.dataset.theme = localStorage.getItem(\"theme\") || \"auto\";\n</script>\n\n\n<svg style=\"display: none;\" xmlns=\"http://www.w3.org/2000/svg\">\n    <symbol id=\"svg-toc\" viewBox=\"0 0 24 24\">\n        <title>Contents</title>\n        <svg fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 1024 1024\">\n            <path d=\"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-menu\" viewBox=\"0 0 24 24\">\n        <title>Menu</title>\n        <svg class=\"feather-menu\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-arrow-right\" viewBox=\"0 0 24 24\">\n        <title>Expand</title>\n        <svg class=\"feather-chevron-right\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <polyline points=\"9 18 15 12 9 6\"></polyline>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun\" viewBox=\"0 0 24 24\">\n        <title>Light mode</title>\n        <svg class=\"feather-sun\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\n            <line x1=\"12\" x2=\"12\" y1=\"1\" y2=\"3\"></line>\n            <line x1=\"12\" x2=\"12\" y1=\"21\" y2=\"23\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"4.22\" y2=\"5.64\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"18.36\" y2=\"19.78\"></line>\n            <line x1=\"1\" x2=\"3\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"21\" x2=\"23\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"19.78\" y2=\"18.36\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"5.64\" y2=\"4.22\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon\" viewBox=\"0 0 24 24\">\n        <title>Dark mode</title>\n        <svg class=\"icon-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun-with-moon\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in light mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 5.411 14.504 C 5.471 14.504 5.532 14.504 5.591 14.504 C 3.639 16.319 4.383 19.569 6.931 20.352 C 7.693 20.586 8.512 20.551 9.25 20.252 C 8.023 23.207 4.056 23.725 2.11 21.184 C 0.166 18.642 1.702 14.949 4.874 14.536 C 5.051 14.512 5.231 14.5 5.411 14.5 L 5.411 14.504 Z\"\n                  style=\"opacity: 50%\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"3.25\" y2=\"1.25\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"15.85\" y2=\"17.85\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"5.094\" y2=\"3.68\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"14.05\" y2=\"15.464\"/>\n            <line x1=\"8.2\" x2=\"6.2\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"20.8\" x2=\"22.8\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"14.006\" y2=\"15.42\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"5.05\" y2=\"3.636\"/>\n            <circle cx=\"14.5\" cy=\"9.55\" r=\"3.6\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon-with-sun\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in dark mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 8.282 7.007 C 8.385 7.007 8.494 7.007 8.595 7.007 C 5.18 10.184 6.481 15.869 10.942 17.24 C 12.275 17.648 13.706 17.589 15 17.066 C 12.851 22.236 5.91 23.143 2.505 18.696 C -0.897 14.249 1.791 7.786 7.342 7.063 C 7.652 7.021 7.965 7 8.282 7 L 8.282 7.007 Z\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"3.705\" y2=\"2.5\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"11.295\" y2=\"12.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"4.816\" y2=\"3.964\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"10.212\" y2=\"11.063\"/>\n            <line style=\"opacity: 50%\" x1=\"14.205\" x2=\"13.001\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"21.795\" x2=\"23\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"10.184\" y2=\"11.036\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"4.789\" y2=\"3.937\"/>\n            <circle cx=\"18\" cy=\"7.5\" r=\"2.169\" style=\"opacity: 50%\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-pencil\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-pencil-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4\"/>\n            <path d=\"M13.5 6.5l4 4\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-eye\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-eye-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\"/>\n            <path\n                    d=\"M11.11 17.958c-3.209 -.307 -5.91 -2.293 -8.11 -5.958c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6c-.21 .352 -.427 .688 -.647 1.008\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n</svg>\n\n<input class=\"sidebar-toggle\" id=\"__navigation\" name=\"__navigation\" type=\"checkbox\">\n<input class=\"sidebar-toggle\" id=\"__toc\" name=\"__toc\" type=\"checkbox\">\n<label class=\"overlay sidebar-overlay\" for=\"__navigation\">\n    <div class=\"visually-hidden\">Hide navigation sidebar</div>\n</label>\n<label class=\"overlay toc-overlay\" for=\"__toc\">\n    <div class=\"visually-hidden\">Hide table of contents sidebar</div>\n</label>\n\n<a class=\"skip-to-content muted-link\" href=\"#furo-main-content\">Skip to content</a>\n\n\n\n<div class=\"page\">\n    <header class=\"mobile-header\">\n        <div class=\"header-left\">\n            <label class=\"nav-overlay-icon\" for=\"__navigation\">\n                <div class=\"visually-hidden\">Toggle site navigation sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-menu\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n        <div class=\"header-center\">\n            <a href=\"#\">\n                <div class=\"brand\">XYBotV2</div>\n            </a>\n        </div>\n        <div class=\"header-right\">\n            <div class=\"theme-toggle-container theme-toggle-header\">\n                <button class=\"theme-toggle\">\n                    <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                    <svg class=\"theme-icon-when-auto-light\">\n                        <use href=\"#svg-sun-with-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-auto-dark\">\n                        <use href=\"#svg-moon-with-sun\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-dark\">\n                        <use href=\"#svg-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-light\">\n                        <use href=\"#svg-sun\"></use>\n                    </svg>\n                </button>\n            </div>\n            <label class=\"toc-overlay-icon toc-header-icon\" for=\"__toc\">\n                <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-toc\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n    </header>\n    <aside class=\"sidebar-drawer\">\n        <div class=\"sidebar-container\">\n\n            <div class=\"sidebar-sticky\">\n                <div class=\"sidebar-scroll\"><a class=\"sidebar-brand\" href=\"#\">\n\n\n                    <span class=\"sidebar-brand-text\">XYBotV2</span>\n\n                </a>\n                    <form action=\"search.html\" class=\"sidebar-search-container\" method=\"get\" role=\"search\">\n                        <input aria-label=\"搜索\" class=\"sidebar-search\" name=\"q\" placeholder=\"搜索\">\n                        <input name=\"check_keywords\" type=\"hidden\" value=\"yes\">\n                        <input name=\"area\" type=\"hidden\" value=\"default\">\n                    </form>\n                    <div id=\"searchbox\"></div>\n                    <div class=\"sidebar-tree\">\n\n                    </div>\n                    <div class=\"sidebar-tree\">\n                        <p class=\"caption\"><span class=\"caption-text\">重要函数导航</span></p>\n                        <ul>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">登录</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.is_running\">检查WechatAPI是否在运行</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_qr_code\">获取登录二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.awaken_login\">二次登录(唤醒登录)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.check_login_uuid\">检查登录的UUID状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_cached_info\">获取登录缓存信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.log_out\">登出当前账号</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.heartbeat\">发送心跳包</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\">开始自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\">停止自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\">获取自动心跳状态</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">消息</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.sync_message\">同步消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_text_message\">发送文本消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_image_message\">发送图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_voice_message\">发送语音消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_video_message\">发送视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_link_message\">发送链接消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_card_message\">发送名片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_app_message\">发送应用(xml)消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_emoji_message\">发送表情消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\">转发图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\">转发视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\">转发文件消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.revoke_message\">撤回消息</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">用户</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_my_qrcode\">获取个人二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_profile\">获取用户信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.is_logged_in\">检查是否登录</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">群聊</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\">获取群聊信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\">获取群聊公告</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\">获取群聊成员列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\">获取群聊二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\">添加群成员(群聊最多40人)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\">邀请群聊成员(群聊大于40人)</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">好友</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contact\">获取联系人信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_detail\">获取联系人详情</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_list\">获取联系人列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_nickname\">获取用户昵称</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.accept_friend\">接受好友请求</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">红包</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\">获取红包详情</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">工具</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.check_database\">检查数据库状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.set_step\">设置步数</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_image\">下载高清图片</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_video\">下载视频</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_voice\">下载语音文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_attach\">下载附件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_byte\">base64转字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_file\">base64转文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.byte_to_base64\">字节转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.file_to_base64\">文件转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\">silk的base64转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\">silk字节转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\">WAV字节转AMR的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\">WAV字节转AMR字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\">WAV字节转silk的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\">WAV字节转silk字节</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                        </ul>\n                    </div>\n                </div>\n            </div>\n\n        </div>\n    </aside>\n    <div class=\"main\">\n        <div class=\"content\">\n            <div class=\"article-container\">\n                <a class=\"back-to-top muted-link\" href=\"#\">\n                    <svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n                        <path d=\"M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z\"></path>\n                    </svg>\n                    <span>Back to top</span>\n                </a>\n                <div class=\"content-icon-container\">\n                    <div class=\"view-this-page\">\n                        <a class=\"muted-link\" href=\"_sources/index.rst.txt\" title=\"View this page\">\n                            <svg>\n                                <use href=\"#svg-eye\"></use>\n                            </svg>\n                            <span class=\"visually-hidden\">View this page</span>\n                        </a>\n                    </div>\n                    <div class=\"theme-toggle-container theme-toggle-content\">\n                        <button class=\"theme-toggle\">\n                            <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                            <svg class=\"theme-icon-when-auto-light\">\n                                <use href=\"#svg-sun-with-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-auto-dark\">\n                                <use href=\"#svg-moon-with-sun\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-dark\">\n                                <use href=\"#svg-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-light\">\n                                <use href=\"#svg-sun\"></use>\n                            </svg>\n                        </button>\n                    </div>\n                    <label class=\"toc-overlay-icon toc-content-icon\" for=\"__toc\">\n                        <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                        <i class=\"icon\">\n                            <svg>\n                                <use href=\"#svg-toc\"></use>\n                            </svg>\n                        </i>\n                    </label>\n                </div>\n                <article id=\"furo-main-content\" role=\"main\">\n                    <section id=\"wechatapiclient\">\n                        <h1>WechatAPIClient<a class=\"headerlink\" href=\"#wechatapiclient\"\n                                              title=\"Link to this heading\">¶</a></h1>\n                        <section id=\"module-WechatAPI.Client.base\">\n                            <span id=\"id1\"></span>\n                            <h2>基础<a class=\"headerlink\" href=\"#module-WechatAPI.Client.base\"\n                                       title=\"Link to this heading\">¶</a></h2>\n                            <dl class=\"py class\">\n                                <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.base.Proxy\">\n                                    <em class=\"property\"><span class=\"pre\">class</span><span\n                                            class=\"w\"> </span></em><span class=\"sig-prename descclassname\"><span\n                                        class=\"pre\">WechatAPI.Client.base.</span></span><span class=\"sig-name descname\"><span\n                                        class=\"pre\">Proxy</span></span><span class=\"sig-paren\">(</span><em\n                                        class=\"sig-param\"><span class=\"n\"><span class=\"pre\">ip</span></span><span\n                                        class=\"p\"><span class=\"pre\">:</span></span><span class=\"w\"> </span><span\n                                        class=\"n\"><span class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                        class=\"n\"><span class=\"pre\">port</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                        class=\"w\"> </span><span class=\"n\"><span class=\"pre\">int</span></span></em>, <em\n                                        class=\"sig-param\"><span class=\"n\"><span class=\"pre\">username</span></span><span\n                                        class=\"p\"><span class=\"pre\">:</span></span><span class=\"w\"> </span><span\n                                        class=\"n\"><span class=\"pre\">str</span></span><span class=\"w\"> </span><span\n                                        class=\"o\"><span class=\"pre\">=</span></span><span class=\"w\"> </span><span\n                                        class=\"default_value\"><span class=\"pre\">''</span></span></em>, <em\n                                        class=\"sig-param\"><span class=\"n\"><span class=\"pre\">password</span></span><span\n                                        class=\"p\"><span class=\"pre\">:</span></span><span class=\"w\"> </span><span\n                                        class=\"n\"><span class=\"pre\">str</span></span><span class=\"w\"> </span><span\n                                        class=\"o\"><span class=\"pre\">=</span></span><span class=\"w\"> </span><span\n                                        class=\"default_value\"><span class=\"pre\">''</span></span></em><span\n                                        class=\"sig-paren\">)</span><a class=\"reference internal\"\n                                                                     href=\"_modules/WechatAPI/Client/base.html#Proxy\"><span\n                                        class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                        class=\"headerlink\" href=\"#WechatAPI.Client.base.Proxy\"\n                                        title=\"Link to this definition\">¶</a></dt>\n                                <dd><p>基类：<code class=\"xref py py-class docutils literal notranslate\"><span\n                                        class=\"pre\">object</span></code></p>\n                                    <p>代理(无效果，别用！)</p>\n                                    <dl class=\"field-list simple\">\n                                        <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                        <dd class=\"field-odd\">\n                                            <ul class=\"simple\">\n                                                <li><p><strong>ip</strong> (<em>str</em>) -- 代理服务器IP地址</p></li>\n                                                <li><p><strong>port</strong> (<em>int</em>) -- 代理服务器端口</p></li>\n                                                <li><p><strong>username</strong>\n                                                    (<em>str</em><em>, </em><em>optional</em>) -- 代理认证用户名.\n                                                    默认为空字符串</p></li>\n                                                <li><p><strong>password</strong>\n                                                    (<em>str</em><em>, </em><em>optional</em>) -- 代理认证密码. 默认为空字符串\n                                                </p></li>\n                                            </ul>\n                                        </dd>\n                                    </dl>\n                                    <dl class=\"py attribute\">\n                                        <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.base.Proxy.ip\">\n                                            <span class=\"sig-name descname\"><span class=\"pre\">ip</span></span><em\n                                                class=\"property\"><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"pre\">str</span></em><a class=\"headerlink\"\n                                                                                                      href=\"#WechatAPI.Client.base.Proxy.ip\"\n                                                                                                      title=\"Link to this definition\">¶</a>\n                                        </dt>\n                                        <dd></dd>\n                                    </dl>\n\n                                    <dl class=\"py attribute\">\n                                        <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.base.Proxy.password\">\n                                            <span class=\"sig-name descname\"><span class=\"pre\">password</span></span><em\n                                                class=\"property\"><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"pre\">str</span></em><em class=\"property\"><span\n                                                class=\"w\"> </span><span class=\"p\"><span class=\"pre\">=</span></span><span\n                                                class=\"w\"> </span><span class=\"pre\">''</span></em><a class=\"headerlink\"\n                                                                                                     href=\"#WechatAPI.Client.base.Proxy.password\"\n                                                                                                     title=\"Link to this definition\">¶</a>\n                                        </dt>\n                                        <dd></dd>\n                                    </dl>\n\n                                    <dl class=\"py attribute\">\n                                        <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.base.Proxy.port\">\n                                            <span class=\"sig-name descname\"><span class=\"pre\">port</span></span><em\n                                                class=\"property\"><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"pre\">int</span></em><a class=\"headerlink\"\n                                                                                                      href=\"#WechatAPI.Client.base.Proxy.port\"\n                                                                                                      title=\"Link to this definition\">¶</a>\n                                        </dt>\n                                        <dd></dd>\n                                    </dl>\n\n                                    <dl class=\"py attribute\">\n                                        <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.base.Proxy.username\">\n                                            <span class=\"sig-name descname\"><span class=\"pre\">username</span></span><em\n                                                class=\"property\"><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"pre\">str</span></em><em class=\"property\"><span\n                                                class=\"w\"> </span><span class=\"p\"><span class=\"pre\">=</span></span><span\n                                                class=\"w\"> </span><span class=\"pre\">''</span></em><a class=\"headerlink\"\n                                                                                                     href=\"#WechatAPI.Client.base.Proxy.username\"\n                                                                                                     title=\"Link to this definition\">¶</a>\n                                        </dt>\n                                        <dd></dd>\n                                    </dl>\n\n                                </dd>\n                            </dl>\n\n                            <dl class=\"py class\">\n                                <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.base.Section\">\n                                    <em class=\"property\"><span class=\"pre\">class</span><span\n                                            class=\"w\"> </span></em><span class=\"sig-prename descclassname\"><span\n                                        class=\"pre\">WechatAPI.Client.base.</span></span><span class=\"sig-name descname\"><span\n                                        class=\"pre\">Section</span></span><span class=\"sig-paren\">(</span><em\n                                        class=\"sig-param\"><span class=\"n\"><span class=\"pre\">data_len</span></span><span\n                                        class=\"p\"><span class=\"pre\">:</span></span><span class=\"w\"> </span><span\n                                        class=\"n\"><span class=\"pre\">int</span></span></em>, <em class=\"sig-param\"><span\n                                        class=\"n\"><span class=\"pre\">start_pos</span></span><span class=\"p\"><span\n                                        class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                        class=\"pre\">int</span></span></em><span class=\"sig-paren\">)</span><a\n                                        class=\"reference internal\"\n                                        href=\"_modules/WechatAPI/Client/base.html#Section\"><span\n                                        class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                        class=\"headerlink\" href=\"#WechatAPI.Client.base.Section\"\n                                        title=\"Link to this definition\">¶</a></dt>\n                                <dd><p>基类：<code class=\"xref py py-class docutils literal notranslate\"><span\n                                        class=\"pre\">object</span></code></p>\n                                    <p>数据段配置类</p>\n                                    <dl class=\"field-list simple\">\n                                        <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                        <dd class=\"field-odd\">\n                                            <ul class=\"simple\">\n                                                <li><p><strong>data_len</strong> (<em>int</em>) -- 数据长度</p></li>\n                                                <li><p><strong>start_pos</strong> (<em>int</em>) -- 起始位置</p></li>\n                                            </ul>\n                                        </dd>\n                                    </dl>\n                                    <dl class=\"py attribute\">\n                                        <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.base.Section.data_len\">\n                                            <span class=\"sig-name descname\"><span class=\"pre\">data_len</span></span><em\n                                                class=\"property\"><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"pre\">int</span></em><a class=\"headerlink\"\n                                                                                                      href=\"#WechatAPI.Client.base.Section.data_len\"\n                                                                                                      title=\"Link to this definition\">¶</a>\n                                        </dt>\n                                        <dd></dd>\n                                    </dl>\n\n                                    <dl class=\"py attribute\">\n                                        <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.base.Section.start_pos\">\n                                            <span class=\"sig-name descname\"><span class=\"pre\">start_pos</span></span><em\n                                                class=\"property\"><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"pre\">int</span></em><a class=\"headerlink\"\n                                                                                                      href=\"#WechatAPI.Client.base.Section.start_pos\"\n                                                                                                      title=\"Link to this definition\">¶</a>\n                                        </dt>\n                                        <dd></dd>\n                                    </dl>\n\n                                </dd>\n                            </dl>\n\n                            <dl class=\"py class\">\n                                <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.base.WechatAPIClientBase\">\n                                    <em class=\"property\"><span class=\"pre\">class</span><span\n                                            class=\"w\"> </span></em><span class=\"sig-prename descclassname\"><span\n                                        class=\"pre\">WechatAPI.Client.base.</span></span><span class=\"sig-name descname\"><span\n                                        class=\"pre\">WechatAPIClientBase</span></span><span class=\"sig-paren\">(</span><em\n                                        class=\"sig-param\"><span class=\"n\"><span class=\"pre\">ip</span></span><span\n                                        class=\"p\"><span class=\"pre\">:</span></span><span class=\"w\"> </span><span\n                                        class=\"n\"><span class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                        class=\"n\"><span class=\"pre\">port</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                        class=\"w\"> </span><span class=\"n\"><span class=\"pre\">int</span></span></em><span\n                                        class=\"sig-paren\">)</span><a class=\"reference internal\"\n                                                                     href=\"_modules/WechatAPI/Client/base.html#WechatAPIClientBase\"><span\n                                        class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                        class=\"headerlink\" href=\"#WechatAPI.Client.base.WechatAPIClientBase\"\n                                        title=\"Link to this definition\">¶</a></dt>\n                                <dd><p>基类：<code class=\"xref py py-class docutils literal notranslate\"><span\n                                        class=\"pre\">object</span></code></p>\n                                    <p>微信API客户端基类</p>\n                                    <dl class=\"field-list simple\">\n                                        <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                        <dd class=\"field-odd\">\n                                            <ul class=\"simple\">\n                                                <li><p><strong>ip</strong> (<em>str</em>) -- 服务器IP地址</p></li>\n                                                <li><p><strong>port</strong> (<em>int</em>) -- 服务器端口</p></li>\n                                            </ul>\n                                        </dd>\n                                        <dt class=\"field-even\">变量<span class=\"colon\">:</span></dt>\n                                        <dd class=\"field-even\">\n                                            <ul class=\"simple\">\n                                                <li><p><strong>wxid</strong> (<em>str</em>) -- 微信ID</p></li>\n                                                <li><p><strong>nickname</strong> (<em>str</em>) -- 昵称</p></li>\n                                                <li><p><strong>alias</strong> (<em>str</em>) -- 别名</p></li>\n                                                <li><p><strong>phone</strong> (<em>str</em>) -- 手机号</p></li>\n                                                <li><p><strong>ignore_protect</strong> (<em>bool</em>) -- 是否忽略保护机制\n                                                </p></li>\n                                            </ul>\n                                        </dd>\n                                    </dl>\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.base.WechatAPIClientBase.error_handler\">\n                                            <em class=\"property\"><span class=\"pre\">static</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">error_handler</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">json_resp</span></span></em><span class=\"sig-paren\">)</span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/base.html#WechatAPIClientBase.error_handler\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.base.WechatAPIClientBase.error_handler\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>处理API响应中的错误码</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>json_resp</strong> (<em>dict</em>) --\n                                                    API响应的JSON数据</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>ValueError</strong> -- 参数错误时抛出</p></li>\n                                                        <li><p><strong>MarshallingError</strong> -- 序列化错误时抛出</p>\n                                                        </li>\n                                                        <li><p><strong>UnmarshallingError</strong> -- 反序列化错误时抛出\n                                                        </p></li>\n                                                        <li><p><strong>MMTLSError</strong> -- MMTLS初始化错误时抛出</p>\n                                                        </li>\n                                                        <li><p><strong>PacketError</strong> -- 数据包长度错误时抛出</p>\n                                                        </li>\n                                                        <li><p><strong>UserLoggedOut</strong> -- 用户已退出登录时抛出\n                                                        </p></li>\n                                                        <li><p><strong>ParsePacketError</strong> -- 解析数据包错误时抛出\n                                                        </p></li>\n                                                        <li><p><strong>DatabaseError</strong> -- 数据库错误时抛出</p>\n                                                        </li>\n                                                        <li><p><strong>Exception</strong> -- 其他类型错误时抛出</p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                </dd>\n                            </dl>\n\n                        </section>\n                        <section id=\"module-WechatAPI.Client.login\">\n                            <span id=\"id2\"></span>\n                            <h2>登录<a class=\"headerlink\" href=\"#module-WechatAPI.Client.login\"\n                                       title=\"Link to this heading\">¶</a></h2>\n                            <dl class=\"py class\">\n                                <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.login.LoginMixin\">\n                                    <em class=\"property\"><span class=\"pre\">class</span><span\n                                            class=\"w\"> </span></em><span class=\"sig-prename descclassname\"><span\n                                        class=\"pre\">WechatAPI.Client.login.</span></span><span\n                                        class=\"sig-name descname\"><span class=\"pre\">LoginMixin</span></span><span\n                                        class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                        class=\"pre\">ip</span></span><span class=\"p\"><span\n                                        class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                        class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span class=\"n\"><span\n                                        class=\"pre\">port</span></span><span class=\"p\"><span\n                                        class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                        class=\"pre\">int</span></span></em><span class=\"sig-paren\">)</span><a\n                                        class=\"reference internal\"\n                                        href=\"_modules/WechatAPI/Client/login.html#LoginMixin\"><span\n                                        class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                        class=\"headerlink\" href=\"#WechatAPI.Client.login.LoginMixin\"\n                                        title=\"Link to this definition\">¶</a></dt>\n                                <dd><p>基类：<a class=\"reference internal\"\n                                               href=\"#WechatAPI.Client.base.WechatAPIClientBase\"\n                                               title=\"WechatAPI.Client.base.WechatAPIClientBase\"><code\n                                        class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">WechatAPIClientBase</span></code></a>\n                                </p>\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.login.LoginMixin.awaken_login\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">awaken_login</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span><span class=\"w\"> </span><span\n                                                class=\"o\"><span class=\"pre\">=</span></span><span class=\"w\"> </span><span\n                                                class=\"default_value\"><span class=\"pre\">''</span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">str</span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/login.html#LoginMixin.awaken_login\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.login.LoginMixin.awaken_login\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>唤醒登录。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>wxid</strong>\n                                                    (<em>str</em><em>, </em><em>optional</em>) -- 要唤醒的微信ID.\n                                                    Defaults to &quot;&quot;.</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>返回新的登录UUID</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>str</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>Exception</strong> -- 如果未提供wxid且未登录</p>\n                                                        </li>\n                                                        <li><p><strong>LoginError</strong> -- 如果无法获取UUID</p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.login.LoginMixin.check_login_uuid\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">check_login_uuid</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">uuid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">device_id</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span><span class=\"w\"> </span><span\n                                                class=\"o\"><span class=\"pre\">=</span></span><span class=\"w\"> </span><span\n                                                class=\"default_value\"><span class=\"pre\">''</span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">tuple</span><span\n                                                class=\"p\"><span class=\"pre\">[</span></span><span class=\"pre\">bool</span><span\n                                                class=\"p\"><span class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">dict</span><span class=\"w\"> </span><span class=\"p\"><span\n                                                class=\"pre\">|</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span class=\"pre\">]</span></span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/login.html#LoginMixin.check_login_uuid\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.login.LoginMixin.check_login_uuid\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>检查登录的UUID状态。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>uuid</strong> (<em>str</em>) -- 登录的UUID</p>\n                                                        </li>\n                                                        <li><p><strong>device_id</strong> (<em>str</em><em>, </em><em>optional</em>)\n                                                            -- 设备ID. Defaults to &quot;&quot;.</p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>如果登录成功返回(True, 用户信息)，否则返回(False,\n                                                    过期时间)</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>tuple[bool, Union[dict, int]]</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p><strong>根据error_handler处理错误</strong> --\n                                                </p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.login.LoginMixin.create_device_id\">\n                                            <em class=\"property\"><span class=\"pre\">static</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">create_device_id</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">s</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span><span class=\"w\"> </span><span\n                                                class=\"o\"><span class=\"pre\">=</span></span><span class=\"w\"> </span><span\n                                                class=\"default_value\"><span class=\"pre\">''</span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">str</span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/login.html#LoginMixin.create_device_id\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.login.LoginMixin.create_device_id\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>生成设备ID。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>s</strong> (<em>str</em><em>, </em><em>optional</em>)\n                                                    -- 用于生成ID的字符串. Defaults to &quot;&quot;.</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>返回生成的设备ID</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>str</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.login.LoginMixin.create_device_name\">\n                                            <em class=\"property\"><span class=\"pre\">static</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">create_device_name</span></span><span\n                                                class=\"sig-paren\">(</span><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">str</span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/login.html#LoginMixin.create_device_name\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.login.LoginMixin.create_device_name\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>生成一个随机的设备名。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>返回生成的设备名</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>str</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">get_auto_heartbeat_status</span></span><span\n                                                class=\"sig-paren\">(</span><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">bool</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/login.html#LoginMixin.get_auto_heartbeat_status\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>获取自动心跳状态。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>如果正在运行返回True，否则返回False</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>bool</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 如果未登录时调用</p>\n                                                        </li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.login.LoginMixin.get_cached_info\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">get_cached_info</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span><span class=\"w\"> </span><span\n                                                class=\"o\"><span class=\"pre\">=</span></span><span class=\"w\"> </span><span\n                                                class=\"default_value\"><span class=\"pre\">None</span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">dict</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/login.html#LoginMixin.get_cached_info\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.login.LoginMixin.get_cached_info\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>获取登录缓存信息。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>wxid</strong>\n                                                    (<em>str</em><em>, </em><em>optional</em>) -- 要查询的微信ID.\n                                                    Defaults to None.</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>\n                                                    返回缓存信息，如果未提供wxid且未登录返回空字典</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>dict</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.login.LoginMixin.get_qr_code\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">get_qr_code</span></span><span class=\"sig-paren\">(</span><em\n                                                class=\"sig-param\"><span class=\"n\"><span class=\"pre\">device_name:</span> <span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">device_id:</span> <span\n                                                class=\"pre\">str</span> <span class=\"pre\">=</span> <span\n                                                class=\"pre\">''</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">proxy:</span> <span class=\"pre\">~WechatAPI.Client.base.Proxy</span> <span\n                                                class=\"pre\">=</span> <span class=\"pre\">None</span></span></em>, <em\n                                                class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">print_qr:</span> <span class=\"pre\">bool</span> <span\n                                                class=\"pre\">=</span> <span class=\"pre\">False)</span> <span class=\"pre\">-&gt;</span> <span\n                                                class=\"pre\">(&lt;class</span> <span class=\"pre\">'str'&gt;</span></span></em>,\n                                            <em class=\"sig-param\"><span class=\"n\"><span\n                                                    class=\"pre\">&lt;class</span> <span\n                                                    class=\"pre\">'str'&gt;</span></span></em><span\n                                                class=\"sig-paren\">)</span><a class=\"reference internal\"\n                                                                             href=\"_modules/WechatAPI/Client/login.html#LoginMixin.get_qr_code\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\" href=\"#WechatAPI.Client.login.LoginMixin.get_qr_code\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>获取登录二维码。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>device_name</strong> (<em>str</em>) -- 设备名称\n                                                        </p></li>\n                                                        <li><p><strong>device_id</strong> (<em>str</em><em>, </em><em>optional</em>)\n                                                            -- 设备ID. Defaults to &quot;&quot;.</p></li>\n                                                        <li><p><strong>proxy</strong> (<a class=\"reference internal\"\n                                                                                          href=\"#WechatAPI.Client.base.Proxy\"\n                                                                                          title=\"WechatAPI.Client.base.Proxy\"><em>Proxy</em></a><em>, </em><em>optional</em>)\n                                                            -- 代理信息. Defaults to None.</p></li>\n                                                        <li><p><strong>print_qr</strong> (<em>bool</em><em>, </em><em>optional</em>)\n                                                            -- 是否在控制台打印二维码. Defaults to False.</p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>返回登录二维码的UUID和URL</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>tuple[str, str]</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p><strong>根据error_handler处理错误</strong> --\n                                                </p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.login.LoginMixin.heartbeat\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span class=\"pre\">heartbeat</span></span><span\n                                                class=\"sig-paren\">(</span><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">bool</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/login.html#LoginMixin.heartbeat\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\" href=\"#WechatAPI.Client.login.LoginMixin.heartbeat\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>发送心跳包。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>成功返回True，否则返回False</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>bool</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 如果未登录时调用</p>\n                                                        </li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.login.LoginMixin.is_running\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">is_running</span></span><span\n                                                class=\"sig-paren\">(</span><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">bool</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/login.html#LoginMixin.is_running\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\" href=\"#WechatAPI.Client.login.LoginMixin.is_running\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>检查WechatAPI是否在运行。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>\n                                                    如果WechatAPI正在运行返回True，否则返回False。</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>bool</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.login.LoginMixin.log_out\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span class=\"pre\">log_out</span></span><span\n                                                class=\"sig-paren\">(</span><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">bool</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/login.html#LoginMixin.log_out\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\" href=\"#WechatAPI.Client.login.LoginMixin.log_out\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>登出当前账号。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>登出成功返回True，否则返回False</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>bool</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 如果未登录时调用</p>\n                                                        </li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span class=\"pre\">start_auto_heartbeat</span></span><span\n                                                class=\"sig-paren\">(</span><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">bool</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/login.html#LoginMixin.start_auto_heartbeat\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>开始自动心跳。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>成功返回True，否则返回False</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>bool</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 如果未登录时调用</p>\n                                                        </li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">stop_auto_heartbeat</span></span><span\n                                                class=\"sig-paren\">(</span><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">bool</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/login.html#LoginMixin.stop_auto_heartbeat\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>停止自动心跳。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>成功返回True，否则返回False</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>bool</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 如果未登录时调用</p>\n                                                        </li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                </dd>\n                            </dl>\n\n                        </section>\n                        <section id=\"module-WechatAPI.Client.message\">\n                            <span id=\"id3\"></span>\n                            <h2>消息<a class=\"headerlink\" href=\"#module-WechatAPI.Client.message\"\n                                       title=\"Link to this heading\">¶</a></h2>\n                            <dl class=\"py class\">\n                                <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.message.MessageMixin\">\n                                    <em class=\"property\"><span class=\"pre\">class</span><span\n                                            class=\"w\"> </span></em><span class=\"sig-prename descclassname\"><span\n                                        class=\"pre\">WechatAPI.Client.message.</span></span><span\n                                        class=\"sig-name descname\"><span class=\"pre\">MessageMixin</span></span><span\n                                        class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                        class=\"pre\">ip</span></span><span class=\"p\"><span\n                                        class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                        class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span class=\"n\"><span\n                                        class=\"pre\">port</span></span><span class=\"p\"><span\n                                        class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                        class=\"pre\">int</span></span></em><span class=\"sig-paren\">)</span><a\n                                        class=\"reference internal\"\n                                        href=\"_modules/WechatAPI/Client/message.html#MessageMixin\"><span\n                                        class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                        class=\"headerlink\" href=\"#WechatAPI.Client.message.MessageMixin\"\n                                        title=\"Link to this definition\">¶</a></dt>\n                                <dd><p>基类：<a class=\"reference internal\"\n                                               href=\"#WechatAPI.Client.base.WechatAPIClientBase\"\n                                               title=\"WechatAPI.Client.base.WechatAPIClientBase\"><code\n                                        class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">WechatAPIClientBase</span></code></a>\n                                </p>\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.message.MessageMixin._process_message_queue\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">_process_message_queue</span></span><span class=\"sig-paren\">(</span><span\n                                                class=\"sig-paren\">)</span><a class=\"reference internal\"\n                                                                             href=\"_modules/WechatAPI/Client/message.html#MessageMixin._process_message_queue\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.message.MessageMixin._process_message_queue\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>处理消息队列的异步方法</p>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.message.MessageMixin._queue_message\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span class=\"pre\">_queue_message</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">func</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"o\"><span class=\"pre\">*</span></span><span class=\"n\"><span\n                                                class=\"pre\">args</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"o\"><span class=\"pre\">**</span></span><span class=\"n\"><span\n                                                class=\"pre\">kwargs</span></span></em><span class=\"sig-paren\">)</span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/message.html#MessageMixin._queue_message\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.message.MessageMixin._queue_message\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>将消息添加到队列</p>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.message.MessageMixin._send_text_message\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">_send_text_message</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">content</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">at</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">list</span><span class=\"p\"><span class=\"pre\">[</span></span><span\n                                                class=\"pre\">str</span><span class=\"p\"><span class=\"pre\">]</span></span></span><span\n                                                class=\"w\"> </span><span class=\"o\"><span class=\"pre\">=</span></span><span\n                                                class=\"w\"> </span><span class=\"default_value\"><span\n                                                class=\"pre\">None</span></span></em><span class=\"sig-paren\">)</span>\n                                            <span class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                    class=\"sig-return-typehint\"><span class=\"pre\">tuple</span><span\n                                                    class=\"p\"><span class=\"pre\">[</span></span><span\n                                                    class=\"pre\">int</span><span class=\"p\"><span\n                                                    class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                    class=\"pre\">int</span><span class=\"p\"><span\n                                                    class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                    class=\"pre\">int</span><span class=\"p\"><span\n                                                    class=\"pre\">]</span></span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/message.html#MessageMixin._send_text_message\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.message.MessageMixin._send_text_message\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>实际发送文本消息的方法</p>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.message.MessageMixin.revoke_message\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span class=\"pre\">revoke_message</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">client_msg_id</span></span><span\n                                                class=\"p\"><span class=\"pre\">:</span></span><span class=\"w\"> </span><span\n                                                class=\"n\"><span class=\"pre\">int</span></span></em>, <em\n                                                class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">create_time</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">int</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">new_msg_id</span></span><span\n                                                class=\"p\"><span class=\"pre\">:</span></span><span class=\"w\"> </span><span\n                                                class=\"n\"><span class=\"pre\">int</span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">bool</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/message.html#MessageMixin.revoke_message\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.message.MessageMixin.revoke_message\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>撤回消息。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>wxid</strong> (<em>str</em>) -- 接收人wxid</p>\n                                                        </li>\n                                                        <li><p><strong>client_msg_id</strong> (<em>int</em>) -- 发送消息的返回值\n                                                        </p></li>\n                                                        <li><p><strong>create_time</strong> (<em>int</em>) -- 发送消息的返回值\n                                                        </p></li>\n                                                        <li><p><strong>new_msg_id</strong> (<em>int</em>) -- 发送消息的返回值\n                                                        </p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>成功返回True，失败返回False</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>bool</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>BanProtection</strong> -- 登录新设备后4小时内操作\n                                                        </p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.message.MessageMixin.send_app_message\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">send_app_message</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">xml</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">type</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">int</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">tuple</span><span\n                                                class=\"p\"><span class=\"pre\">[</span></span><span\n                                                class=\"pre\">str</span><span class=\"p\"><span\n                                                class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span\n                                                class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span class=\"pre\">]</span></span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/message.html#MessageMixin.send_app_message\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.message.MessageMixin.send_app_message\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>发送应用消息。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>wxid</strong> (<em>str</em>) -- 接收人wxid</p>\n                                                        </li>\n                                                        <li><p><strong>xml</strong> (<em>str</em>) -- 应用消息的xml内容\n                                                        </p></li>\n                                                        <li><p><strong>type</strong> (<em>int</em>) -- 应用消息类型</p>\n                                                        </li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>返回(ClientMsgid, CreateTime, NewMsgId)</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>tuple[str, int, int]</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>BanProtection</strong> -- 登录新设备后4小时内操作\n                                                        </p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.message.MessageMixin.send_card_message\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">send_card_message</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">card_wxid</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">card_nickname</span></span><span\n                                                class=\"p\"><span class=\"pre\">:</span></span><span class=\"w\"> </span><span\n                                                class=\"n\"><span class=\"pre\">str</span></span></em>, <em\n                                                class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">card_alias</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span><span class=\"w\"> </span><span\n                                                class=\"o\"><span class=\"pre\">=</span></span><span class=\"w\"> </span><span\n                                                class=\"default_value\"><span class=\"pre\">''</span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">tuple</span><span\n                                                class=\"p\"><span class=\"pre\">[</span></span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span\n                                                class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span\n                                                class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span class=\"pre\">]</span></span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/message.html#MessageMixin.send_card_message\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.message.MessageMixin.send_card_message\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>发送名片消息。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>wxid</strong> (<em>str</em>) -- 接收人wxid</p>\n                                                        </li>\n                                                        <li><p><strong>card_wxid</strong> (<em>str</em>) -- 名片用户的wxid\n                                                        </p></li>\n                                                        <li><p><strong>card_nickname</strong> (<em>str</em>) -- 名片用户的昵称\n                                                        </p></li>\n                                                        <li><p><strong>card_alias</strong> (<em>str</em><em>, </em><em>optional</em>)\n                                                            -- 名片用户的备注. Defaults to &quot;&quot;.</p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>返回(ClientMsgid, CreateTime, NewMsgId)</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>tuple[int, int, int]</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>BanProtection</strong> -- 登录新设备后4小时内操作\n                                                        </p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">send_cdn_file_msg</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">xml</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">tuple</span><span\n                                                class=\"p\"><span class=\"pre\">[</span></span><span\n                                                class=\"pre\">str</span><span class=\"p\"><span\n                                                class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span\n                                                class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span class=\"pre\">]</span></span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/message.html#MessageMixin.send_cdn_file_msg\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>转发文件消息。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>wxid</strong> (<em>str</em>) -- 接收人wxid</p>\n                                                        </li>\n                                                        <li><p><strong>xml</strong> (<em>str</em>) -- 要转发的文件消息xml内容\n                                                        </p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>返回(ClientMsgid, CreateTime, NewMsgId)</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>tuple[str, int, int]</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>BanProtection</strong> -- 登录新设备后4小时内操作\n                                                        </p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">send_cdn_img_msg</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">xml</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">tuple</span><span\n                                                class=\"p\"><span class=\"pre\">[</span></span><span\n                                                class=\"pre\">str</span><span class=\"p\"><span\n                                                class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span\n                                                class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span class=\"pre\">]</span></span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/message.html#MessageMixin.send_cdn_img_msg\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>转发图片消息。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>wxid</strong> (<em>str</em>) -- 接收人wxid</p>\n                                                        </li>\n                                                        <li><p><strong>xml</strong> (<em>str</em>) -- 要转发的图片消息xml内容\n                                                        </p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>返回(ClientImgId, CreateTime, NewMsgId)</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>tuple[str, int, int]</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>BanProtection</strong> -- 登录新设备后4小时内操作\n                                                        </p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">send_cdn_video_msg</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">xml</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">tuple</span><span\n                                                class=\"p\"><span class=\"pre\">[</span></span><span\n                                                class=\"pre\">str</span><span class=\"p\"><span\n                                                class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span class=\"pre\">]</span></span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/message.html#MessageMixin.send_cdn_video_msg\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>转发视频消息。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>wxid</strong> (<em>str</em>) -- 接收人wxid</p>\n                                                        </li>\n                                                        <li><p><strong>xml</strong> (<em>str</em>) -- 要转发的视频消息xml内容\n                                                        </p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>返回(ClientMsgid, NewMsgId)</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>tuple[str, int]</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>BanProtection</strong> -- 登录新设备后4小时内操作\n                                                        </p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.message.MessageMixin.send_emoji_message\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">send_emoji_message</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">md5</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">total_length</span></span><span\n                                                class=\"p\"><span class=\"pre\">:</span></span><span class=\"w\"> </span><span\n                                                class=\"n\"><span class=\"pre\">int</span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">list</span><span\n                                                class=\"p\"><span class=\"pre\">[</span></span><span class=\"pre\">dict</span><span\n                                                class=\"p\"><span class=\"pre\">]</span></span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/message.html#MessageMixin.send_emoji_message\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.message.MessageMixin.send_emoji_message\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>发送表情消息。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>wxid</strong> (<em>str</em>) -- 接收人wxid</p>\n                                                        </li>\n                                                        <li><p><strong>md5</strong> (<em>str</em>) -- 表情md5值</p></li>\n                                                        <li><p><strong>total_length</strong> (<em>int</em>) -- 表情总长度\n                                                        </p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>返回表情项列表(list of emojiItem)</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>list[dict]</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>BanProtection</strong> -- 登录新设备后4小时内操作\n                                                        </p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.message.MessageMixin.send_image_message\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">send_image_message</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">image</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span><span class=\"w\"> </span><span class=\"p\"><span\n                                                class=\"pre\">|</span></span><span class=\"w\"> </span><span class=\"pre\">bytes</span><span\n                                                class=\"w\"> </span><span class=\"p\"><span class=\"pre\">|</span></span><span\n                                                class=\"w\"> </span><span class=\"pre\">PathLike</span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">tuple</span><span\n                                                class=\"p\"><span class=\"pre\">[</span></span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span\n                                                class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span\n                                                class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span class=\"pre\">]</span></span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/message.html#MessageMixin.send_image_message\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.message.MessageMixin.send_image_message\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>发送图片消息。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>wxid</strong> (<em>str</em>) -- 接收人wxid</p>\n                                                        </li>\n                                                        <li><p><strong>image</strong>\n                                                            (<em>str</em><em>, </em><em>byte</em><em>, </em><em>os.PathLike</em>)\n                                                            -- 图片，支持base64字符串，图片byte，图片路径</p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>返回(ClientImgId, CreateTime, NewMsgId)</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>tuple[int, int, int]</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>BanProtection</strong> -- 登录新设备后4小时内操作\n                                                        </p></li>\n                                                        <li><p><strong>ValueError</strong> --\n                                                            image_path和image_base64都为空或都不为空时</p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.message.MessageMixin.send_link_message\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">send_link_message</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">url</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">title</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span><span class=\"w\"> </span><span\n                                                class=\"o\"><span class=\"pre\">=</span></span><span class=\"w\"> </span><span\n                                                class=\"default_value\"><span class=\"pre\">''</span></span></em>, <em\n                                                class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">description</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span><span class=\"w\"> </span><span\n                                                class=\"o\"><span class=\"pre\">=</span></span><span class=\"w\"> </span><span\n                                                class=\"default_value\"><span class=\"pre\">''</span></span></em>, <em\n                                                class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">thumb_url</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span><span class=\"w\"> </span><span\n                                                class=\"o\"><span class=\"pre\">=</span></span><span class=\"w\"> </span><span\n                                                class=\"default_value\"><span class=\"pre\">''</span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">tuple</span><span\n                                                class=\"p\"><span class=\"pre\">[</span></span><span\n                                                class=\"pre\">str</span><span class=\"p\"><span\n                                                class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span\n                                                class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span class=\"pre\">]</span></span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/message.html#MessageMixin.send_link_message\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.message.MessageMixin.send_link_message\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>发送链接消息。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>wxid</strong> (<em>str</em>) -- 接收人wxid</p>\n                                                        </li>\n                                                        <li><p><strong>url</strong> (<em>str</em>) -- 跳转链接</p></li>\n                                                        <li><p><strong>title</strong> (<em>str</em><em>, </em><em>optional</em>)\n                                                            -- 标题. Defaults to &quot;&quot;.</p></li>\n                                                        <li><p><strong>description</strong> (<em>str</em><em>, </em><em>optional</em>)\n                                                            -- 描述. Defaults to &quot;&quot;.</p></li>\n                                                        <li><p><strong>thumb_url</strong> (<em>str</em><em>, </em><em>optional</em>)\n                                                            -- 缩略图链接. Defaults to &quot;&quot;.</p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>返回(ClientMsgid, CreateTime, NewMsgId)</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>tuple[str, int, int]</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>BanProtection</strong> -- 登录新设备后4小时内操作\n                                                        </p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.message.MessageMixin.send_text_message\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">send_text_message</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">content</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">at</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">list</span><span class=\"w\"> </span><span class=\"p\"><span\n                                                class=\"pre\">|</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">str</span></span><span class=\"w\"> </span><span\n                                                class=\"o\"><span class=\"pre\">=</span></span><span class=\"w\"> </span><span\n                                                class=\"default_value\"><span class=\"pre\">''</span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">tuple</span><span\n                                                class=\"p\"><span class=\"pre\">[</span></span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span\n                                                class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span\n                                                class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">int</span><span class=\"p\"><span class=\"pre\">]</span></span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/message.html#MessageMixin.send_text_message\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.message.MessageMixin.send_text_message\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>发送文本消息。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>wxid</strong> (<em>str</em>) -- 接收人wxid</p>\n                                                        </li>\n                                                        <li><p><strong>content</strong> (<em>str</em>) -- 消息内容</p>\n                                                        </li>\n                                                        <li><p><strong>at</strong> (<em>list</em><em>, </em><em>str</em><em>, </em><em>optional</em>)\n                                                            -- 要&#64;的用户</p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>返回(ClientMsgid, CreateTime, NewMsgId)</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>tuple[int, int, int]</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>BanProtection</strong> -- 登录新设备后4小时内操作\n                                                        </p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.message.MessageMixin.send_video_message\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">send_video_message</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid:</span> <span class=\"pre\">str</span></span></em>, <em\n                                                class=\"sig-param\"><span class=\"n\"><span class=\"pre\">video:</span> <span\n                                                class=\"pre\">str</span> <span class=\"pre\">|</span> <span class=\"pre\">bytes</span> <span\n                                                class=\"pre\">|</span> <span class=\"pre\">~os.PathLike</span></span></em>,\n                                            <em class=\"sig-param\"><span class=\"n\"><span class=\"pre\">image:</span> <span\n                                                    class=\"pre\">[&lt;class</span> <span\n                                                    class=\"pre\">'str'&gt;</span></span></em>, <em\n                                                class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">&lt;class</span> <span class=\"pre\">'bytes'&gt;</span></span></em>,\n                                            <em class=\"sig-param\"><span class=\"n\"><span\n                                                    class=\"pre\">&lt;class</span> <span\n                                                    class=\"pre\">'os.PathLike'&gt;]</span> <span\n                                                    class=\"pre\">=</span> <span class=\"pre\">None</span></span></em><span\n                                                class=\"sig-paren\">)</span><a class=\"reference internal\"\n                                                                             href=\"_modules/WechatAPI/Client/message.html#MessageMixin.send_video_message\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.message.MessageMixin.send_video_message\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>\n                                            发送视频消息。不推荐使用，上传速度很慢300KB/s。如要使用，可压缩视频，或者发送链接卡片而不是视频。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>wxid</strong> (<em>str</em>) -- 接收人wxid</p>\n                                                        </li>\n                                                        <li><p><strong>video</strong>\n                                                            (<em>str</em><em>, </em><em>bytes</em><em>, </em><em>os.PathLike</em>)\n                                                            -- 视频 接受base64字符串，字节，文件路径</p></li>\n                                                        <li><p><strong>image</strong>\n                                                            (<em>str</em><em>, </em><em>bytes</em><em>, </em><em>os.PathLike</em>)\n                                                            -- 视频封面图片 接受base64字符串，字节，文件路径</p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>返回(ClientMsgid, NewMsgId)</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>tuple[int, int]</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>BanProtection</strong> -- 登录新设备后4小时内操作\n                                                        </p></li>\n                                                        <li><p><strong>ValueError</strong> -- 视频或图片参数都为空或都不为空时\n                                                        </p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.message.MessageMixin.send_voice_message\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">send_voice_message</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">voice</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span><span class=\"w\"> </span><span class=\"p\"><span\n                                                class=\"pre\">|</span></span><span class=\"w\"> </span><span class=\"pre\">bytes</span><span\n                                                class=\"w\"> </span><span class=\"p\"><span class=\"pre\">|</span></span><span\n                                                class=\"w\"> </span><span class=\"pre\">PathLike</span></span></em>, <em\n                                                class=\"sig-param\"><span class=\"n\"><span class=\"pre\">format</span></span><span\n                                                class=\"p\"><span class=\"pre\">:</span></span><span class=\"w\"> </span><span\n                                                class=\"n\"><span class=\"pre\">str</span></span><span\n                                                class=\"w\"> </span><span class=\"o\"><span class=\"pre\">=</span></span><span\n                                                class=\"w\"> </span><span class=\"default_value\"><span\n                                                class=\"pre\">'amr'</span></span></em><span class=\"sig-paren\">)</span>\n                                            <span class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                    class=\"sig-return-typehint\"><span class=\"pre\">tuple</span><span\n                                                    class=\"p\"><span class=\"pre\">[</span></span><span\n                                                    class=\"pre\">int</span><span class=\"p\"><span\n                                                    class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                    class=\"pre\">int</span><span class=\"p\"><span\n                                                    class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                    class=\"pre\">int</span><span class=\"p\"><span\n                                                    class=\"pre\">]</span></span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/message.html#MessageMixin.send_voice_message\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.message.MessageMixin.send_voice_message\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>发送语音消息。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>wxid</strong> (<em>str</em>) -- 接收人wxid</p>\n                                                        </li>\n                                                        <li><p><strong>voice</strong>\n                                                            (<em>str</em><em>, </em><em>bytes</em><em>, </em><em>os.PathLike</em>)\n                                                            -- 语音 接受base64字符串，字节，文件路径</p></li>\n                                                        <li><p><strong>format</strong> (<em>str</em><em>, </em><em>optional</em>)\n                                                            -- 语音格式，支持amr/wav/mp3. Defaults to &quot;amr&quot;.\n                                                        </p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>返回(ClientMsgid, CreateTime, NewMsgId)</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>tuple[int, int, int]</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>BanProtection</strong> -- 登录新设备后4小时内操作\n                                                        </p></li>\n                                                        <li><p><strong>ValueError</strong> --\n                                                            voice_path和voice_base64都为空或都不为空时，或format不支持时\n                                                        </p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.message.MessageMixin.sync_message\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">sync_message</span></span><span\n                                                class=\"sig-paren\">(</span><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">dict</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/message.html#MessageMixin.sync_message\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.message.MessageMixin.sync_message\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>同步消息。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>返回同步到的消息数据</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>dict</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                </dd>\n                            </dl>\n\n                        </section>\n                        <section id=\"module-WechatAPI.Client.user\">\n                            <span id=\"id4\"></span>\n                            <h2>用户<a class=\"headerlink\" href=\"#module-WechatAPI.Client.user\"\n                                       title=\"Link to this heading\">¶</a></h2>\n                            <dl class=\"py class\">\n                                <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.user.UserMixin\">\n                                    <em class=\"property\"><span class=\"pre\">class</span><span\n                                            class=\"w\"> </span></em><span class=\"sig-prename descclassname\"><span\n                                        class=\"pre\">WechatAPI.Client.user.</span></span><span class=\"sig-name descname\"><span\n                                        class=\"pre\">UserMixin</span></span><span class=\"sig-paren\">(</span><em\n                                        class=\"sig-param\"><span class=\"n\"><span class=\"pre\">ip</span></span><span\n                                        class=\"p\"><span class=\"pre\">:</span></span><span class=\"w\"> </span><span\n                                        class=\"n\"><span class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                        class=\"n\"><span class=\"pre\">port</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                        class=\"w\"> </span><span class=\"n\"><span class=\"pre\">int</span></span></em><span\n                                        class=\"sig-paren\">)</span><a class=\"reference internal\"\n                                                                     href=\"_modules/WechatAPI/Client/user.html#UserMixin\"><span\n                                        class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                        class=\"headerlink\" href=\"#WechatAPI.Client.user.UserMixin\"\n                                        title=\"Link to this definition\">¶</a></dt>\n                                <dd><p>基类：<a class=\"reference internal\"\n                                               href=\"#WechatAPI.Client.base.WechatAPIClientBase\"\n                                               title=\"WechatAPI.Client.base.WechatAPIClientBase\"><code\n                                        class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">WechatAPIClientBase</span></code></a>\n                                </p>\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.user.UserMixin.get_my_qrcode\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">get_my_qrcode</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">style</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">int</span></span><span class=\"w\"> </span><span\n                                                class=\"o\"><span class=\"pre\">=</span></span><span class=\"w\"> </span><span\n                                                class=\"default_value\"><span class=\"pre\">0</span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">str</span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/user.html#UserMixin.get_my_qrcode\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\" href=\"#WechatAPI.Client.user.UserMixin.get_my_qrcode\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>获取个人二维码。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>style</strong> (<em>int</em><em>, </em><em>optional</em>)\n                                                    -- 二维码样式. Defaults to 0.</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>图片的base64编码字符串</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>str</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>BanProtection</strong> -- 风控保护: 新设备登录后4小时内请挂机\n                                                        </p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.user.UserMixin.get_profile\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">get_profile</span></span><span class=\"sig-paren\">(</span><em\n                                                class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span><span class=\"w\"> </span><span\n                                                class=\"o\"><span class=\"pre\">=</span></span><span class=\"w\"> </span><span\n                                                class=\"default_value\"><span class=\"pre\">None</span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">dict</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/user.html#UserMixin.get_profile\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\" href=\"#WechatAPI.Client.user.UserMixin.get_profile\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>获取用户信息。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>wxid</strong>\n                                                    (<em>str</em><em>, </em><em>optional</em>) -- 用户wxid. Defaults to\n                                                    None.</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>用户信息字典</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>dict</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.user.UserMixin.is_logged_in\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">is_logged_in</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span><span class=\"w\"> </span><span\n                                                class=\"o\"><span class=\"pre\">=</span></span><span class=\"w\"> </span><span\n                                                class=\"default_value\"><span class=\"pre\">None</span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">bool</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/user.html#UserMixin.is_logged_in\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\" href=\"#WechatAPI.Client.user.UserMixin.is_logged_in\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>检查是否登录。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>wxid</strong>\n                                                    (<em>str</em><em>, </em><em>optional</em>) -- 用户wxid. Defaults to\n                                                    None.</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>已登录返回True，未登录返回False</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>bool</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                </dd>\n                            </dl>\n\n                        </section>\n                        <section id=\"module-WechatAPI.Client.chatroom\">\n                            <span id=\"id5\"></span>\n                            <h2>群聊<a class=\"headerlink\" href=\"#module-WechatAPI.Client.chatroom\"\n                                       title=\"Link to this heading\">¶</a></h2>\n                            <dl class=\"py class\">\n                                <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.chatroom.ChatroomMixin\">\n                                    <em class=\"property\"><span class=\"pre\">class</span><span\n                                            class=\"w\"> </span></em><span class=\"sig-prename descclassname\"><span\n                                        class=\"pre\">WechatAPI.Client.chatroom.</span></span><span\n                                        class=\"sig-name descname\"><span class=\"pre\">ChatroomMixin</span></span><span\n                                        class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                        class=\"pre\">ip</span></span><span class=\"p\"><span\n                                        class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                        class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span class=\"n\"><span\n                                        class=\"pre\">port</span></span><span class=\"p\"><span\n                                        class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                        class=\"pre\">int</span></span></em><span class=\"sig-paren\">)</span><a\n                                        class=\"reference internal\"\n                                        href=\"_modules/WechatAPI/Client/chatroom.html#ChatroomMixin\"><span\n                                        class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                        class=\"headerlink\" href=\"#WechatAPI.Client.chatroom.ChatroomMixin\"\n                                        title=\"Link to this definition\">¶</a></dt>\n                                <dd><p>基类：<a class=\"reference internal\"\n                                               href=\"#WechatAPI.Client.base.WechatAPIClientBase\"\n                                               title=\"WechatAPI.Client.base.WechatAPIClientBase\"><code\n                                        class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">WechatAPIClientBase</span></code></a>\n                                </p>\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">add_chatroom_member</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">chatroom</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">wxid</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">bool</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/chatroom.html#ChatroomMixin.add_chatroom_member\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>添加群成员(群聊最多40人)</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>chatroom</strong> -- 群聊wxid</p></li>\n                                                        <li><p><strong>wxid</strong> -- 要添加的wxid</p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>成功返回True, 失败False或者报错</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>bool</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span class=\"pre\">get_chatroom_announce</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">chatroom</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">dict</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/chatroom.html#ChatroomMixin.get_chatroom_announce\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>获取群聊公告</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>chatroom</strong> -- 群聊id</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>群聊信息字典</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>dict</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">get_chatroom_info</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">chatroom</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">dict</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/chatroom.html#ChatroomMixin.get_chatroom_info\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>获取群聊信息</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>chatroom</strong> -- 群聊id</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>群聊信息字典</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>dict</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">get_chatroom_member_list</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">chatroom</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">list</span><span\n                                                class=\"p\"><span class=\"pre\">[</span></span><span class=\"pre\">dict</span><span\n                                                class=\"p\"><span class=\"pre\">]</span></span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/chatroom.html#ChatroomMixin.get_chatroom_member_list\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>获取群聊成员列表</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>chatroom</strong> -- 群聊id</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>群聊成员列表</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>list[dict]</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">get_chatroom_qrcode</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">chatroom</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">dict</span><span\n                                                class=\"p\"><span class=\"pre\">[</span></span><span\n                                                class=\"pre\">str</span><span class=\"p\"><span\n                                                class=\"pre\">,</span></span><span class=\"w\"> </span><span\n                                                class=\"pre\">Any</span><span class=\"p\"><span class=\"pre\">]</span></span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/chatroom.html#ChatroomMixin.get_chatroom_qrcode\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>获取群聊二维码</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>chatroom</strong> -- 群聊id</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>{&quot;base64&quot;: 二维码的base64, &quot;description&quot;:\n                                                    二维码描述}</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>dict</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">invite_chatroom_member</span></span><span class=\"sig-paren\">(</span><em\n                                                class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span class=\"pre\">str</span><span\n                                                class=\"w\"> </span><span class=\"p\"><span class=\"pre\">|</span></span><span\n                                                class=\"w\"> </span><span class=\"pre\">list</span></span></em>, <em\n                                                class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">chatroom</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">bool</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/chatroom.html#ChatroomMixin.invite_chatroom_member\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>邀请群聊成员(群聊大于40人)</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>wxid</strong> -- 要邀请的用户wxid或wxid列表</p>\n                                                        </li>\n                                                        <li><p><strong>chatroom</strong> -- 群聊id</p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>成功返回True, 失败False或者报错</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>bool</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                </dd>\n                            </dl>\n\n                        </section>\n                        <section id=\"module-WechatAPI.Client.friend\">\n                            <span id=\"id6\"></span>\n                            <h2>好友<a class=\"headerlink\" href=\"#module-WechatAPI.Client.friend\"\n                                       title=\"Link to this heading\">¶</a></h2>\n                            <dl class=\"py class\">\n                                <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.friend.FriendMixin\">\n                                    <em class=\"property\"><span class=\"pre\">class</span><span\n                                            class=\"w\"> </span></em><span class=\"sig-prename descclassname\"><span\n                                        class=\"pre\">WechatAPI.Client.friend.</span></span><span\n                                        class=\"sig-name descname\"><span class=\"pre\">FriendMixin</span></span><span\n                                        class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                        class=\"pre\">ip</span></span><span class=\"p\"><span\n                                        class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                        class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span class=\"n\"><span\n                                        class=\"pre\">port</span></span><span class=\"p\"><span\n                                        class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                        class=\"pre\">int</span></span></em><span class=\"sig-paren\">)</span><a\n                                        class=\"reference internal\"\n                                        href=\"_modules/WechatAPI/Client/friend.html#FriendMixin\"><span\n                                        class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                        class=\"headerlink\" href=\"#WechatAPI.Client.friend.FriendMixin\"\n                                        title=\"Link to this definition\">¶</a></dt>\n                                <dd><p>基类：<a class=\"reference internal\"\n                                               href=\"#WechatAPI.Client.base.WechatAPIClientBase\"\n                                               title=\"WechatAPI.Client.base.WechatAPIClientBase\"><code\n                                        class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">WechatAPIClientBase</span></code></a>\n                                </p>\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.friend.FriendMixin.accept_friend\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">accept_friend</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">scene</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">int</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">v1</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">v2</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">bool</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/friend.html#FriendMixin.accept_friend\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.friend.FriendMixin.accept_friend\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>接受好友请求</p>\n                                            <p>主动添加好友单天上限如下所示：1小时内上限为\n                                                5个，超过上限时，无法发出好友请求，也收不到好友请求。</p>\n                                            <ul class=\"simple\">\n                                                <li><p>新账号：5/天</p></li>\n                                                <li><p>注册超过7天：10个/天</p></li>\n                                                <li><p>注册满3个月&amp;&amp;近期登录过该电脑：15/天</p></li>\n                                                <li><p>注册满6个月&amp;&amp;近期经常登录过该电脑：20/天</p></li>\n                                                <li><p>注册满6个月&amp;&amp;近期频繁登陆过该电脑：30/天</p></li>\n                                                <li><p>注册1年以上&amp;&amp;一直登录：50/天</p></li>\n                                                <li><p>上一次通过好友到下一次通过间隔20-40s</p></li>\n                                                <li><p>\n                                                    收到加人申请，到通过好友申请（每天最多通过300个好友申请），间隔30s+（随机时间）</p>\n                                                </li>\n                                            </ul>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>scene</strong> -- 来源 在消息的xml获取</p></li>\n                                                        <li><p><strong>v1</strong> -- v1key</p></li>\n                                                        <li><p><strong>v2</strong> -- v2key</p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>操作是否成功</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>bool</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.friend.FriendMixin.get_contact\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">get_contact</span></span><span class=\"sig-paren\">(</span><em\n                                                class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span class=\"pre\">str</span><span\n                                                class=\"w\"> </span><span class=\"p\"><span class=\"pre\">|</span></span><span\n                                                class=\"w\"> </span><span class=\"pre\">list</span><span class=\"p\"><span\n                                                class=\"pre\">[</span></span><span class=\"pre\">str</span><span\n                                                class=\"p\"><span class=\"pre\">]</span></span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">dict</span><span\n                                                class=\"w\"> </span><span class=\"p\"><span class=\"pre\">|</span></span><span\n                                                class=\"w\"> </span><span class=\"pre\">list</span><span class=\"p\"><span\n                                                class=\"pre\">[</span></span><span class=\"pre\">dict</span><span class=\"p\"><span\n                                                class=\"pre\">]</span></span></span></span><a class=\"reference internal\"\n                                                                                            href=\"_modules/WechatAPI/Client/friend.html#FriendMixin.get_contact\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.friend.FriendMixin.get_contact\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>获取联系人信息</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>wxid</strong> -- 联系人wxid,\n                                                    可以是多个wxid在list里，也可查询chatroom</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>\n                                                    单个联系人返回dict，多个联系人返回list[dict]</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>Union[dict, list[dict]]</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.friend.FriendMixin.get_contract_detail\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">get_contract_detail</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span class=\"pre\">str</span><span\n                                                class=\"w\"> </span><span class=\"p\"><span class=\"pre\">|</span></span><span\n                                                class=\"w\"> </span><span class=\"pre\">list</span><span class=\"p\"><span\n                                                class=\"pre\">[</span></span><span class=\"pre\">str</span><span\n                                                class=\"p\"><span class=\"pre\">]</span></span></span></em>, <em\n                                                class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">chatroom</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span><span class=\"w\"> </span><span\n                                                class=\"o\"><span class=\"pre\">=</span></span><span class=\"w\"> </span><span\n                                                class=\"default_value\"><span class=\"pre\">''</span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">list</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/friend.html#FriendMixin.get_contract_detail\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.friend.FriendMixin.get_contract_detail\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>获取联系人详情</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>wxid</strong> -- 联系人wxid</p></li>\n                                                        <li><p><strong>chatroom</strong> -- 群聊wxid</p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>联系人详情列表</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>list</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.friend.FriendMixin.get_contract_list\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">get_contract_list</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wx_seq</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">int</span></span><span class=\"w\"> </span><span\n                                                class=\"o\"><span class=\"pre\">=</span></span><span class=\"w\"> </span><span\n                                                class=\"default_value\"><span class=\"pre\">0</span></span></em>, <em\n                                                class=\"sig-param\"><span class=\"n\"><span class=\"pre\">chatroom_seq</span></span><span\n                                                class=\"p\"><span class=\"pre\">:</span></span><span class=\"w\"> </span><span\n                                                class=\"n\"><span class=\"pre\">int</span></span><span\n                                                class=\"w\"> </span><span class=\"o\"><span class=\"pre\">=</span></span><span\n                                                class=\"w\"> </span><span class=\"default_value\"><span class=\"pre\">0</span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">dict</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/friend.html#FriendMixin.get_contract_list\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.friend.FriendMixin.get_contract_list\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>获取联系人列表</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>wx_seq</strong> -- 联系人序列</p></li>\n                                                        <li><p><strong>chatroom_seq</strong> -- 群聊序列</p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>联系人列表数据</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>dict</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.friend.FriendMixin.get_nickname\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">get_nickname</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wxid</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span class=\"pre\">str</span><span\n                                                class=\"w\"> </span><span class=\"p\"><span class=\"pre\">|</span></span><span\n                                                class=\"w\"> </span><span class=\"pre\">list</span><span class=\"p\"><span\n                                                class=\"pre\">[</span></span><span class=\"pre\">str</span><span\n                                                class=\"p\"><span class=\"pre\">]</span></span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">str</span><span\n                                                class=\"w\"> </span><span class=\"p\"><span class=\"pre\">|</span></span><span\n                                                class=\"w\"> </span><span class=\"pre\">list</span><span class=\"p\"><span\n                                                class=\"pre\">[</span></span><span class=\"pre\">str</span><span\n                                                class=\"p\"><span class=\"pre\">]</span></span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/friend.html#FriendMixin.get_nickname\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.friend.FriendMixin.get_nickname\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>获取用户昵称</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>wxid</strong> --\n                                                    用户wxid，可以是单个wxid或最多20个wxid的列表</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>\n                                                    如果输入单个wxid返回str，如果输入wxid列表则返回对应的昵称列表</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>Union[str, list[str]]</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                </dd>\n                            </dl>\n\n                        </section>\n                        <section id=\"module-WechatAPI.Client.hongbao\">\n                            <span id=\"id7\"></span>\n                            <h2>红包<a class=\"headerlink\" href=\"#module-WechatAPI.Client.hongbao\"\n                                       title=\"Link to this heading\">¶</a></h2>\n                            <dl class=\"py class\">\n                                <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.hongbao.HongBaoMixin\">\n                                    <em class=\"property\"><span class=\"pre\">class</span><span\n                                            class=\"w\"> </span></em><span class=\"sig-prename descclassname\"><span\n                                        class=\"pre\">WechatAPI.Client.hongbao.</span></span><span\n                                        class=\"sig-name descname\"><span class=\"pre\">HongBaoMixin</span></span><span\n                                        class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                        class=\"pre\">ip</span></span><span class=\"p\"><span\n                                        class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                        class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span class=\"n\"><span\n                                        class=\"pre\">port</span></span><span class=\"p\"><span\n                                        class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                        class=\"pre\">int</span></span></em><span class=\"sig-paren\">)</span><a\n                                        class=\"reference internal\"\n                                        href=\"_modules/WechatAPI/Client/hongbao.html#HongBaoMixin\"><span\n                                        class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                        class=\"headerlink\" href=\"#WechatAPI.Client.hongbao.HongBaoMixin\"\n                                        title=\"Link to this definition\">¶</a></dt>\n                                <dd><p>基类：<a class=\"reference internal\"\n                                               href=\"#WechatAPI.Client.base.WechatAPIClientBase\"\n                                               title=\"WechatAPI.Client.base.WechatAPIClientBase\"><code\n                                        class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">WechatAPIClientBase</span></code></a>\n                                </p>\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">get_hongbao_detail</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">xml</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">encrypt_key</span></span><span\n                                                class=\"p\"><span class=\"pre\">:</span></span><span class=\"w\"> </span><span\n                                                class=\"n\"><span class=\"pre\">str</span></span></em>, <em\n                                                class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">encrypt_userinfo</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">dict</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/hongbao.html#HongBaoMixin.get_hongbao_detail\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>获取红包详情</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>xml</strong> -- 红包 XML 数据</p></li>\n                                                        <li><p><strong>encrypt_key</strong> -- 加密密钥</p></li>\n                                                        <li><p><strong>encrypt_userinfo</strong> -- 加密的用户信息</p>\n                                                        </li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>红包详情数据</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>dict</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                </dd>\n                            </dl>\n\n                        </section>\n                        <section id=\"module-WechatAPI.Client.protect\">\n                            <span id=\"id8\"></span>\n                            <h2>保护<a class=\"headerlink\" href=\"#module-WechatAPI.Client.protect\"\n                                       title=\"Link to this heading\">¶</a></h2>\n                            <dl class=\"py class\">\n                                <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.protect.Protect\">\n                                    <em class=\"property\"><span class=\"pre\">class</span><span\n                                            class=\"w\"> </span></em><span class=\"sig-prename descclassname\"><span\n                                        class=\"pre\">WechatAPI.Client.protect.</span></span><span\n                                        class=\"sig-name descname\"><span class=\"pre\">Protect</span></span><span\n                                        class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"o\"><span\n                                        class=\"pre\">*</span></span><span class=\"n\"><span class=\"pre\">args</span></span></em>,\n                                    <em class=\"sig-param\"><span class=\"o\"><span class=\"pre\">**</span></span><span\n                                            class=\"n\"><span class=\"pre\">kwargs</span></span></em><span\n                                        class=\"sig-paren\">)</span><a class=\"reference internal\"\n                                                                     href=\"_modules/WechatAPI/Client/protect.html#Protect\"><span\n                                        class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                        class=\"headerlink\" href=\"#WechatAPI.Client.protect.Protect\"\n                                        title=\"Link to this definition\">¶</a></dt>\n                                <dd><p>基类：<code class=\"xref py py-class docutils literal notranslate\"><span\n                                        class=\"pre\">object</span></code></p>\n                                    <p>保护类，风控保护机制。</p>\n                                    <p>使用单例模式确保全局只有一个实例。</p>\n                                    <dl class=\"field-list simple\">\n                                        <dt class=\"field-odd\">变量<span class=\"colon\">:</span></dt>\n                                        <dd class=\"field-odd\">\n                                            <ul class=\"simple\">\n                                                <li><p><strong>login_stat_path</strong> (<em>str</em>) -- 登录状态文件的路径\n                                                </p></li>\n                                                <li><p><strong>login_stat</strong> (<em>dict</em>) -- 登录状态信息</p>\n                                                </li>\n                                                <li><p><strong>login_time</strong> (<em>int</em>) -- 最后登录时间戳</p>\n                                                </li>\n                                                <li><p><strong>login_device_id</strong> (<em>str</em>) -- 最后登录的设备ID\n                                                </p></li>\n                                            </ul>\n                                        </dd>\n                                    </dl>\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.protect.Protect.__init__\">\n                                            <span class=\"sig-name descname\"><span\n                                                    class=\"pre\">__init__</span></span><span\n                                                class=\"sig-paren\">(</span><span class=\"sig-paren\">)</span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/protect.html#Protect.__init__\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\" href=\"#WechatAPI.Client.protect.Protect.__init__\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>初始化保护类实例。</p>\n                                            <p>创建或加载登录状态文件，初始化登录时间和设备ID。</p>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.protect.Protect.check\">\n                                            <span class=\"sig-name descname\"><span class=\"pre\">check</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">second</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">int</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">bool</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/protect.html#Protect.check\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\" href=\"#WechatAPI.Client.protect.Protect.check\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>检查是否在指定时间内，风控保护。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>second</strong> (<em>int</em>) -- 指定的秒数\n                                                </p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>\n                                                    如果当前时间与上次登录时间的差小于指定秒数，返回True；否则返回False</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>bool</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.protect.Protect.update_login_status\">\n                                            <span class=\"sig-name descname\"><span class=\"pre\">update_login_status</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">device_id</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span><span class=\"w\"> </span><span\n                                                class=\"o\"><span class=\"pre\">=</span></span><span class=\"w\"> </span><span\n                                                class=\"default_value\"><span class=\"pre\">''</span></span></em><span\n                                                class=\"sig-paren\">)</span><a class=\"reference internal\"\n                                                                             href=\"_modules/WechatAPI/Client/protect.html#Protect.update_login_status\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.protect.Protect.update_login_status\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>更新登录状态。</p>\n                                            <p>如果设备ID发生变化，更新登录时间和设备ID，并保存到文件。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>device_id</strong>\n                                                    (<em>str</em><em>, </em><em>optional</em>) -- 设备ID. Defaults to\n                                                    &quot;&quot;.</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                </dd>\n                            </dl>\n\n                            <dl class=\"py class\">\n                                <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.protect.Singleton\">\n                                    <em class=\"property\"><span class=\"pre\">class</span><span\n                                            class=\"w\"> </span></em><span class=\"sig-prename descclassname\"><span\n                                        class=\"pre\">WechatAPI.Client.protect.</span></span><span\n                                        class=\"sig-name descname\"><span class=\"pre\">Singleton</span></span><a\n                                        class=\"reference internal\"\n                                        href=\"_modules/WechatAPI/Client/protect.html#Singleton\"><span\n                                        class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                        class=\"headerlink\" href=\"#WechatAPI.Client.protect.Singleton\"\n                                        title=\"Link to this definition\">¶</a></dt>\n                                <dd><p>基类：<code class=\"xref py py-class docutils literal notranslate\"><span\n                                        class=\"pre\">type</span></code></p>\n                                    <p>单例模式的元类。</p>\n                                    <p>用于确保一个类只有一个实例。</p>\n                                    <dl class=\"field-list simple\">\n                                        <dt class=\"field-odd\">变量<span class=\"colon\">:</span></dt>\n                                        <dd class=\"field-odd\"><p><strong>_instances</strong> (<em>dict</em>) --\n                                            存储类的实例的字典</p>\n                                        </dd>\n                                    </dl>\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.protect.Singleton.__call__\">\n                                            <span class=\"sig-name descname\"><span\n                                                    class=\"pre\">__call__</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"o\"><span\n                                                class=\"pre\">*</span></span><span class=\"n\"><span class=\"pre\">args</span></span></em>,\n                                            <em class=\"sig-param\"><span class=\"o\"><span\n                                                    class=\"pre\">**</span></span><span class=\"n\"><span\n                                                    class=\"pre\">kwargs</span></span></em><span\n                                                class=\"sig-paren\">)</span><a class=\"reference internal\"\n                                                                             href=\"_modules/WechatAPI/Client/protect.html#Singleton.__call__\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\" href=\"#WechatAPI.Client.protect.Singleton.__call__\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>创建或返回类的单例实例。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>*args</strong> -- 位置参数</p></li>\n                                                        <li><p><strong>**kwargs</strong> -- 关键字参数</p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>类的单例实例</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>object</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                </dd>\n                            </dl>\n\n                        </section>\n                        <section id=\"module-WechatAPI.Client.tool\">\n                            <span id=\"id9\"></span>\n                            <h2>工具<a class=\"headerlink\" href=\"#module-WechatAPI.Client.tool\"\n                                       title=\"Link to this heading\">¶</a></h2>\n                            <dl class=\"py class\">\n                                <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.tool.ToolMixin\">\n                                    <em class=\"property\"><span class=\"pre\">class</span><span\n                                            class=\"w\"> </span></em><span class=\"sig-prename descclassname\"><span\n                                        class=\"pre\">WechatAPI.Client.tool.</span></span><span class=\"sig-name descname\"><span\n                                        class=\"pre\">ToolMixin</span></span><span class=\"sig-paren\">(</span><em\n                                        class=\"sig-param\"><span class=\"n\"><span class=\"pre\">ip</span></span><span\n                                        class=\"p\"><span class=\"pre\">:</span></span><span class=\"w\"> </span><span\n                                        class=\"n\"><span class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                        class=\"n\"><span class=\"pre\">port</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                        class=\"w\"> </span><span class=\"n\"><span class=\"pre\">int</span></span></em><span\n                                        class=\"sig-paren\">)</span><a class=\"reference internal\"\n                                                                     href=\"_modules/WechatAPI/Client/tool.html#ToolMixin\"><span\n                                        class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                        class=\"headerlink\" href=\"#WechatAPI.Client.tool.ToolMixin\"\n                                        title=\"Link to this definition\">¶</a></dt>\n                                <dd><p>基类：<a class=\"reference internal\"\n                                               href=\"#WechatAPI.Client.base.WechatAPIClientBase\"\n                                               title=\"WechatAPI.Client.base.WechatAPIClientBase\"><code\n                                        class=\"xref py py-class docutils literal notranslate\"><span class=\"pre\">WechatAPIClientBase</span></code></a>\n                                </p>\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.tool.ToolMixin.base64_to_byte\">\n                                            <em class=\"property\"><span class=\"pre\">static</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span class=\"pre\">base64_to_byte</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">base64_str</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">bytes</span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/tool.html#ToolMixin.base64_to_byte\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.tool.ToolMixin.base64_to_byte\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>将base64字符串转换为bytes。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>base64_str</strong> (<em>str</em>) --\n                                                    base64编码的字符串</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>解码后的字节数据</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>bytes</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.tool.ToolMixin.base64_to_file\">\n                                            <em class=\"property\"><span class=\"pre\">static</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span class=\"pre\">base64_to_file</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">base64_str</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">file_name</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">file_path</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">bool</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/tool.html#ToolMixin.base64_to_file\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.tool.ToolMixin.base64_to_file\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>将base64字符串转换为文件并保存。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>base64_str</strong> (<em>str</em>) --\n                                                            base64编码的字符串</p></li>\n                                                        <li><p><strong>file_name</strong> (<em>str</em>) -- 要保存的文件名\n                                                        </p></li>\n                                                        <li><p><strong>file_path</strong> (<em>str</em>) -- 文件保存路径\n                                                        </p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>转换成功返回True，失败返回False</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>bool</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.tool.ToolMixin.byte_to_base64\">\n                                            <em class=\"property\"><span class=\"pre\">static</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span class=\"pre\">byte_to_base64</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">byte</span></span><span class=\"p\"><span class=\"pre\">:</span></span><span\n                                                class=\"w\"> </span><span class=\"n\"><span class=\"pre\">bytes</span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">str</span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/tool.html#ToolMixin.byte_to_base64\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.tool.ToolMixin.byte_to_base64\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>将bytes转换为base64字符串。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>byte</strong> (<em>bytes</em>) -- 字节数据\n                                                </p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>base64编码的字符串</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>str</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.tool.ToolMixin.check_database\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span class=\"pre\">check_database</span></span><span\n                                                class=\"sig-paren\">(</span><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">bool</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/tool.html#ToolMixin.check_database\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.tool.ToolMixin.check_database\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>检查数据库状态。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>数据库正常返回True，否则返回False</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>bool</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.tool.ToolMixin.download_attach\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">download_attach</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">attach_id</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">dict</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/tool.html#ToolMixin.download_attach\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.tool.ToolMixin.download_attach\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>下载附件。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>attach_id</strong> (<em>str</em>) --\n                                                    附件ID</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>附件数据</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>dict</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.tool.ToolMixin.download_image\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span class=\"pre\">download_image</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">aeskey</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">cdnmidimgurl</span></span><span\n                                                class=\"p\"><span class=\"pre\">:</span></span><span class=\"w\"> </span><span\n                                                class=\"n\"><span class=\"pre\">str</span></span></em><span\n                                                class=\"sig-paren\">)</span> <span class=\"sig-return\"><span\n                                                class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">str</span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/tool.html#ToolMixin.download_image\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.tool.ToolMixin.download_image\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>CDN下载高清图片。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>aeskey</strong> (<em>str</em>) -- 图片的AES密钥\n                                                        </p></li>\n                                                        <li><p><strong>cdnmidimgurl</strong> (<em>str</em>) -- 图片的CDN\n                                                            URL</p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>图片的base64编码字符串</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>str</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.tool.ToolMixin.download_video\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span class=\"pre\">download_video</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">msg_id</span></span></em><span class=\"sig-paren\">)</span>\n                                            <span class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                    class=\"sig-return-typehint\"><span\n                                                    class=\"pre\">str</span></span></span><a class=\"reference internal\"\n                                                                                           href=\"_modules/WechatAPI/Client/tool.html#ToolMixin.download_video\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.tool.ToolMixin.download_video\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>下载视频。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>msg_id</strong> (<em>str</em>) --\n                                                    消息的msg_id</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>视频的base64编码字符串</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>str</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.tool.ToolMixin.download_voice\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span class=\"pre\">download_voice</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">msg_id</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">voiceurl</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em>, <em class=\"sig-param\"><span\n                                                class=\"n\"><span class=\"pre\">length</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">int</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">str</span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/tool.html#ToolMixin.download_voice\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.tool.ToolMixin.download_voice\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>下载语音文件。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>msg_id</strong> (<em>str</em>) -- 消息的msgid</p>\n                                                        </li>\n                                                        <li><p><strong>voiceurl</strong> (<em>str</em>) -- 语音的url，从xml获取\n                                                        </p></li>\n                                                        <li><p><strong>length</strong> (<em>int</em>) -- 语音长度，从xml获取\n                                                        </p></li>\n                                                    </ul>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>语音的base64编码字符串</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>str</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.tool.ToolMixin.file_to_base64\">\n                                            <em class=\"property\"><span class=\"pre\">static</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span class=\"pre\">file_to_base64</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">file_path</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">str</span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/tool.html#ToolMixin.file_to_base64\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.tool.ToolMixin.file_to_base64\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>将文件转换为base64字符串。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>file_path</strong> (<em>str</em>) --\n                                                    文件路径</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>base64编码的字符串</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>str</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.tool.ToolMixin.set_proxy\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span class=\"pre\">set_proxy</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">proxy</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><a\n                                                class=\"reference internal\" href=\"#WechatAPI.Client.base.Proxy\"\n                                                title=\"WechatAPI.Client.base.Proxy\"><span\n                                                class=\"pre\">Proxy</span></a></span></em><span class=\"sig-paren\">)</span>\n                                            <span class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                    class=\"sig-return-typehint\"><span\n                                                    class=\"pre\">bool</span></span></span><a class=\"reference internal\"\n                                                                                            href=\"_modules/WechatAPI/Client/tool.html#ToolMixin.set_proxy\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\" href=\"#WechatAPI.Client.tool.ToolMixin.set_proxy\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>设置代理。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>proxy</strong> (<a\n                                                        class=\"reference internal\" href=\"#WechatAPI.Client.base.Proxy\"\n                                                        title=\"WechatAPI.Client.base.Proxy\"><em>Proxy</em></a>) --\n                                                    代理配置对象</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>成功返回True，失败返回False</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>bool</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\" id=\"WechatAPI.Client.tool.ToolMixin.set_step\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span class=\"pre\">set_step</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">count</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">int</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span\n                                                class=\"pre\">bool</span></span></span><a class=\"reference internal\"\n                                                                                        href=\"_modules/WechatAPI/Client/tool.html#ToolMixin.set_step\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\" href=\"#WechatAPI.Client.tool.ToolMixin.set_step\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>设置步数。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>count</strong> (<em>int</em>) -- 要设置的步数\n                                                </p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>成功返回True，失败返回False</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>bool</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\">\n                                                    <ul class=\"simple\">\n                                                        <li><p><strong>UserLoggedOut</strong> -- 未登录时调用</p></li>\n                                                        <li><p><strong>BanProtection</strong> -- 风控保护: 新设备登录后4小时内请挂机\n                                                        </p></li>\n                                                        <li><p><strong>根据error_handler处理错误</strong> -- </p></li>\n                                                    </ul>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span\n                                                    class=\"w\"> </span><span class=\"pre\">static</span><span\n                                                    class=\"w\"> </span></em><span class=\"sig-name descname\"><span\n                                                class=\"pre\">silk_base64_to_wav_byte</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">silk_base64</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">str</span></span></em><span class=\"sig-paren\">)</span> <span\n                                                class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                class=\"sig-return-typehint\"><span class=\"pre\">bytes</span></span></span><a\n                                                class=\"reference internal\"\n                                                href=\"_modules/WechatAPI/Client/tool.html#ToolMixin.silk_base64_to_wav_byte\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>将silk格式的base64字符串转换为WAV字节数据。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>silk_base64</strong> (<em>str</em>) --\n                                                    silk格式的base64编码字符串</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>WAV格式的字节数据</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>bytes</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span\n                                                    class=\"w\"> </span><span class=\"pre\">static</span><span\n                                                    class=\"w\"> </span></em><span class=\"sig-name descname\"><span\n                                                class=\"pre\">silk_byte_to_byte_wav_byte</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">silk_byte</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">bytes</span></span></em><span class=\"sig-paren\">)</span>\n                                            <span class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                    class=\"sig-return-typehint\"><span\n                                                    class=\"pre\">bytes</span></span></span><a class=\"reference internal\"\n                                                                                             href=\"_modules/WechatAPI/Client/tool.html#ToolMixin.silk_byte_to_byte_wav_byte\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>将silk字节转换为wav字节。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>silk_byte</strong> (<em>bytes</em>) --\n                                                    silk格式的字节数据</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>wav格式的字节数据</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>bytes</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\">\n                                            <em class=\"property\"><span class=\"pre\">static</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span\n                                                class=\"pre\">wav_byte_to_amr_base64</span></span><span class=\"sig-paren\">(</span><em\n                                                class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wav_byte</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">bytes</span></span></em><span class=\"sig-paren\">)</span>\n                                            <span class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                    class=\"sig-return-typehint\"><span\n                                                    class=\"pre\">str</span></span></span><a class=\"reference internal\"\n                                                                                           href=\"_modules/WechatAPI/Client/tool.html#ToolMixin.wav_byte_to_amr_base64\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>将WAV字节数据转换为AMR格式的base64字符串。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>wav_byte</strong> (<em>bytes</em>) --\n                                                    WAV格式的字节数据</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>AMR格式的base64编码字符串</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>str</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\">\n                                            <em class=\"property\"><span class=\"pre\">static</span><span class=\"w\"> </span></em><span\n                                                class=\"sig-name descname\"><span class=\"pre\">wav_byte_to_amr_byte</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wav_byte</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">bytes</span></span></em><span class=\"sig-paren\">)</span>\n                                            <span class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                    class=\"sig-return-typehint\"><span\n                                                    class=\"pre\">bytes</span></span></span><a class=\"reference internal\"\n                                                                                             href=\"_modules/WechatAPI/Client/tool.html#ToolMixin.wav_byte_to_amr_byte\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>将WAV字节数据转换为AMR格式。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>wav_byte</strong> (<em>bytes</em>) --\n                                                    WAV格式的字节数据</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>AMR格式的字节数据</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>bytes</p>\n                                                </dd>\n                                                <dt class=\"field-even\">抛出<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p><strong>Exception</strong> -- 转换失败时抛出异常\n                                                </p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span\n                                                    class=\"w\"> </span><span class=\"pre\">static</span><span\n                                                    class=\"w\"> </span></em><span class=\"sig-name descname\"><span\n                                                class=\"pre\">wav_byte_to_silk_base64</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wav_byte</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">bytes</span></span></em><span class=\"sig-paren\">)</span>\n                                            <span class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                    class=\"sig-return-typehint\"><span\n                                                    class=\"pre\">str</span></span></span><a class=\"reference internal\"\n                                                                                           href=\"_modules/WechatAPI/Client/tool.html#ToolMixin.wav_byte_to_silk_base64\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>将WAV字节数据转换为silk格式的base64字符串。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>wav_byte</strong> (<em>bytes</em>) --\n                                                    WAV格式的字节数据</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>silk格式的base64编码字符串</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>str</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                    <dl class=\"py method\">\n                                        <dt class=\"sig sig-object py\"\n                                            id=\"WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\">\n                                            <em class=\"property\"><span class=\"pre\">async</span><span\n                                                    class=\"w\"> </span><span class=\"pre\">static</span><span\n                                                    class=\"w\"> </span></em><span class=\"sig-name descname\"><span\n                                                class=\"pre\">wav_byte_to_silk_byte</span></span><span\n                                                class=\"sig-paren\">(</span><em class=\"sig-param\"><span class=\"n\"><span\n                                                class=\"pre\">wav_byte</span></span><span class=\"p\"><span\n                                                class=\"pre\">:</span></span><span class=\"w\"> </span><span class=\"n\"><span\n                                                class=\"pre\">bytes</span></span></em><span class=\"sig-paren\">)</span>\n                                            <span class=\"sig-return\"><span class=\"sig-return-icon\">&#x2192;</span> <span\n                                                    class=\"sig-return-typehint\"><span\n                                                    class=\"pre\">bytes</span></span></span><a class=\"reference internal\"\n                                                                                             href=\"_modules/WechatAPI/Client/tool.html#ToolMixin.wav_byte_to_silk_byte\"><span\n                                                class=\"viewcode-link\"><span class=\"pre\">[源代码]</span></span></a><a\n                                                class=\"headerlink\"\n                                                href=\"#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\"\n                                                title=\"Link to this definition\">¶</a></dt>\n                                        <dd><p>将WAV字节数据转换为silk格式。</p>\n                                            <dl class=\"field-list simple\">\n                                                <dt class=\"field-odd\">参数<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p><strong>wav_byte</strong> (<em>bytes</em>) --\n                                                    WAV格式的字节数据</p>\n                                                </dd>\n                                                <dt class=\"field-even\">返回<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-even\"><p>silk格式的字节数据</p>\n                                                </dd>\n                                                <dt class=\"field-odd\">返回类型<span class=\"colon\">:</span></dt>\n                                                <dd class=\"field-odd\"><p>bytes</p>\n                                                </dd>\n                                            </dl>\n                                        </dd>\n                                    </dl>\n\n                                </dd>\n                            </dl>\n\n                            <section id=\"id10\">\n                                <h3>索引<a class=\"headerlink\" href=\"#id10\" title=\"Link to this heading\">¶</a></h3>\n                                <ul class=\"simple\">\n                                    <li><p><a class=\"reference internal\" href=\"search.html\"><span class=\"std std-ref\">搜索页面</span></a>\n                                    </p></li>\n                                </ul>\n                            </section>\n                        </section>\n                    </section>\n\n                </article>\n            </div>\n            <footer>\n\n                <div class=\"related-pages\">\n\n\n                </div>\n                <div class=\"bottom-of-page\">\n                    <div class=\"left-details\">\n                        <div class=\"copyright\">\n                            Copyright &#169; 2025, HenryXiaoYang\n                        </div>\n                        Made with <a href=\"https://www.sphinx-doc.org/\">Sphinx</a> and <a class=\"muted-link\"\n                                                                                          href=\"https://pradyunsg.me\">@pradyunsg</a>'s\n\n                        <a href=\"https://github.com/pradyunsg/furo\">Furo</a>\n\n                    </div>\n                    <div class=\"right-details\">\n\n                    </div>\n                </div>\n\n            </footer>\n        </div>\n        <aside class=\"toc-drawer\">\n\n\n            <div class=\"toc-sticky toc-scroll\">\n                <div class=\"toc-title-container\">\n          <span class=\"toc-title\">\n            On this page\n          </span>\n                </div>\n                <div class=\"toc-tree-container\">\n                    <div class=\"toc-tree\">\n                        <ul>\n                            <li><a class=\"reference internal\" href=\"#\">WechatAPIClient</a>\n                                <ul>\n                                    <li><a class=\"reference internal\" href=\"#module-WechatAPI.Client.base\">基础</a>\n                                        <ul>\n                                            <li><a class=\"reference internal\" href=\"#WechatAPI.Client.base.Proxy\"><code\n                                                    class=\"docutils literal notranslate\"><span class=\"pre\">Proxy</span></code></a>\n                                                <ul>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.base.Proxy.ip\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">Proxy.ip</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.base.Proxy.password\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">Proxy.password</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.base.Proxy.port\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">Proxy.port</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.base.Proxy.username\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">Proxy.username</span></code></a>\n                                                    </li>\n                                                </ul>\n                                            </li>\n                                            <li><a class=\"reference internal\"\n                                                   href=\"#WechatAPI.Client.base.Section\"><code\n                                                    class=\"docutils literal notranslate\"><span\n                                                    class=\"pre\">Section</span></code></a>\n                                                <ul>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.base.Section.data_len\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">Section.data_len</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.base.Section.start_pos\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">Section.start_pos</span></code></a>\n                                                    </li>\n                                                </ul>\n                                            </li>\n                                            <li><a class=\"reference internal\"\n                                                   href=\"#WechatAPI.Client.base.WechatAPIClientBase\"><code\n                                                    class=\"docutils literal notranslate\"><span class=\"pre\">WechatAPIClientBase</span></code></a>\n                                                <ul>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.base.WechatAPIClientBase.error_handler\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">WechatAPIClientBase.error_handler()</span></code></a>\n                                                    </li>\n                                                </ul>\n                                            </li>\n                                        </ul>\n                                    </li>\n                                    <li><a class=\"reference internal\" href=\"#module-WechatAPI.Client.login\">登录</a>\n                                        <ul>\n                                            <li><a class=\"reference internal\" href=\"#WechatAPI.Client.login.LoginMixin\"><code\n                                                    class=\"docutils literal notranslate\"><span\n                                                    class=\"pre\">LoginMixin</span></code></a>\n                                                <ul>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.login.LoginMixin.awaken_login\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">LoginMixin.awaken_login()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.login.LoginMixin.check_login_uuid\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">LoginMixin.check_login_uuid()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.login.LoginMixin.create_device_id\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">LoginMixin.create_device_id()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.login.LoginMixin.create_device_name\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">LoginMixin.create_device_name()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">LoginMixin.get_auto_heartbeat_status()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.login.LoginMixin.get_cached_info\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">LoginMixin.get_cached_info()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.login.LoginMixin.get_qr_code\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">LoginMixin.get_qr_code()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.login.LoginMixin.heartbeat\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">LoginMixin.heartbeat()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.login.LoginMixin.is_running\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">LoginMixin.is_running()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.login.LoginMixin.log_out\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">LoginMixin.log_out()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">LoginMixin.start_auto_heartbeat()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">LoginMixin.stop_auto_heartbeat()</span></code></a>\n                                                    </li>\n                                                </ul>\n                                            </li>\n                                        </ul>\n                                    </li>\n                                    <li><a class=\"reference internal\" href=\"#module-WechatAPI.Client.message\">消息</a>\n                                        <ul>\n                                            <li><a class=\"reference internal\"\n                                                   href=\"#WechatAPI.Client.message.MessageMixin\"><code\n                                                    class=\"docutils literal notranslate\"><span\n                                                    class=\"pre\">MessageMixin</span></code></a>\n                                                <ul>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.message.MessageMixin._process_message_queue\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">MessageMixin._process_message_queue()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.message.MessageMixin._queue_message\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">MessageMixin._queue_message()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.message.MessageMixin._send_text_message\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">MessageMixin._send_text_message()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.message.MessageMixin.revoke_message\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">MessageMixin.revoke_message()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.message.MessageMixin.send_app_message\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">MessageMixin.send_app_message()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.message.MessageMixin.send_card_message\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">MessageMixin.send_card_message()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">MessageMixin.send_cdn_file_msg()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">MessageMixin.send_cdn_img_msg()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">MessageMixin.send_cdn_video_msg()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.message.MessageMixin.send_emoji_message\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">MessageMixin.send_emoji_message()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.message.MessageMixin.send_image_message\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">MessageMixin.send_image_message()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.message.MessageMixin.send_link_message\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">MessageMixin.send_link_message()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.message.MessageMixin.send_text_message\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">MessageMixin.send_text_message()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.message.MessageMixin.send_video_message\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">MessageMixin.send_video_message()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.message.MessageMixin.send_voice_message\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">MessageMixin.send_voice_message()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.message.MessageMixin.sync_message\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">MessageMixin.sync_message()</span></code></a>\n                                                    </li>\n                                                </ul>\n                                            </li>\n                                        </ul>\n                                    </li>\n                                    <li><a class=\"reference internal\" href=\"#module-WechatAPI.Client.user\">用户</a>\n                                        <ul>\n                                            <li><a class=\"reference internal\"\n                                                   href=\"#WechatAPI.Client.user.UserMixin\"><code\n                                                    class=\"docutils literal notranslate\"><span\n                                                    class=\"pre\">UserMixin</span></code></a>\n                                                <ul>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.user.UserMixin.get_my_qrcode\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">UserMixin.get_my_qrcode()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.user.UserMixin.get_profile\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">UserMixin.get_profile()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.user.UserMixin.is_logged_in\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">UserMixin.is_logged_in()</span></code></a>\n                                                    </li>\n                                                </ul>\n                                            </li>\n                                        </ul>\n                                    </li>\n                                    <li><a class=\"reference internal\" href=\"#module-WechatAPI.Client.chatroom\">群聊</a>\n                                        <ul>\n                                            <li><a class=\"reference internal\"\n                                                   href=\"#WechatAPI.Client.chatroom.ChatroomMixin\"><code\n                                                    class=\"docutils literal notranslate\"><span\n                                                    class=\"pre\">ChatroomMixin</span></code></a>\n                                                <ul>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ChatroomMixin.add_chatroom_member()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ChatroomMixin.get_chatroom_announce()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ChatroomMixin.get_chatroom_info()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ChatroomMixin.get_chatroom_member_list()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ChatroomMixin.get_chatroom_qrcode()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ChatroomMixin.invite_chatroom_member()</span></code></a>\n                                                    </li>\n                                                </ul>\n                                            </li>\n                                        </ul>\n                                    </li>\n                                    <li><a class=\"reference internal\" href=\"#module-WechatAPI.Client.friend\">好友</a>\n                                        <ul>\n                                            <li><a class=\"reference internal\"\n                                                   href=\"#WechatAPI.Client.friend.FriendMixin\"><code\n                                                    class=\"docutils literal notranslate\"><span\n                                                    class=\"pre\">FriendMixin</span></code></a>\n                                                <ul>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.friend.FriendMixin.accept_friend\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">FriendMixin.accept_friend()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.friend.FriendMixin.get_contact\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">FriendMixin.get_contact()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.friend.FriendMixin.get_contract_detail\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">FriendMixin.get_contract_detail()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.friend.FriendMixin.get_contract_list\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">FriendMixin.get_contract_list()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.friend.FriendMixin.get_nickname\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">FriendMixin.get_nickname()</span></code></a>\n                                                    </li>\n                                                </ul>\n                                            </li>\n                                        </ul>\n                                    </li>\n                                    <li><a class=\"reference internal\" href=\"#module-WechatAPI.Client.hongbao\">红包</a>\n                                        <ul>\n                                            <li><a class=\"reference internal\"\n                                                   href=\"#WechatAPI.Client.hongbao.HongBaoMixin\"><code\n                                                    class=\"docutils literal notranslate\"><span\n                                                    class=\"pre\">HongBaoMixin</span></code></a>\n                                                <ul>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">HongBaoMixin.get_hongbao_detail()</span></code></a>\n                                                    </li>\n                                                </ul>\n                                            </li>\n                                        </ul>\n                                    </li>\n                                    <li><a class=\"reference internal\" href=\"#module-WechatAPI.Client.protect\">保护</a>\n                                        <ul>\n                                            <li><a class=\"reference internal\"\n                                                   href=\"#WechatAPI.Client.protect.Protect\"><code\n                                                    class=\"docutils literal notranslate\"><span\n                                                    class=\"pre\">Protect</span></code></a>\n                                                <ul>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.protect.Protect.__init__\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">Protect.__init__()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.protect.Protect.check\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">Protect.check()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.protect.Protect.update_login_status\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">Protect.update_login_status()</span></code></a>\n                                                    </li>\n                                                </ul>\n                                            </li>\n                                            <li><a class=\"reference internal\"\n                                                   href=\"#WechatAPI.Client.protect.Singleton\"><code\n                                                    class=\"docutils literal notranslate\"><span\n                                                    class=\"pre\">Singleton</span></code></a>\n                                                <ul>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.protect.Singleton.__call__\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">Singleton.__call__()</span></code></a>\n                                                    </li>\n                                                </ul>\n                                            </li>\n                                        </ul>\n                                    </li>\n                                    <li><a class=\"reference internal\" href=\"#module-WechatAPI.Client.tool\">工具</a>\n                                        <ul>\n                                            <li><a class=\"reference internal\"\n                                                   href=\"#WechatAPI.Client.tool.ToolMixin\"><code\n                                                    class=\"docutils literal notranslate\"><span\n                                                    class=\"pre\">ToolMixin</span></code></a>\n                                                <ul>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.tool.ToolMixin.base64_to_byte\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ToolMixin.base64_to_byte()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.tool.ToolMixin.base64_to_file\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ToolMixin.base64_to_file()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.tool.ToolMixin.byte_to_base64\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ToolMixin.byte_to_base64()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.tool.ToolMixin.check_database\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ToolMixin.check_database()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.tool.ToolMixin.download_attach\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ToolMixin.download_attach()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.tool.ToolMixin.download_image\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ToolMixin.download_image()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.tool.ToolMixin.download_video\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ToolMixin.download_video()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.tool.ToolMixin.download_voice\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ToolMixin.download_voice()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.tool.ToolMixin.file_to_base64\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ToolMixin.file_to_base64()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.tool.ToolMixin.set_proxy\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ToolMixin.set_proxy()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.tool.ToolMixin.set_step\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ToolMixin.set_step()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ToolMixin.silk_base64_to_wav_byte()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ToolMixin.silk_byte_to_byte_wav_byte()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ToolMixin.wav_byte_to_amr_base64()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ToolMixin.wav_byte_to_amr_byte()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ToolMixin.wav_byte_to_silk_base64()</span></code></a>\n                                                    </li>\n                                                    <li><a class=\"reference internal\"\n                                                           href=\"#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\"><code\n                                                            class=\"docutils literal notranslate\"><span class=\"pre\">ToolMixin.wav_byte_to_silk_byte()</span></code></a>\n                                                    </li>\n                                                </ul>\n                                            </li>\n                                            <li><a class=\"reference internal\" href=\"#id10\">索引</a></li>\n                                        </ul>\n                                    </li>\n                                </ul>\n                            </li>\n                        </ul>\n\n                    </div>\n                </div>\n            </div>\n\n\n        </aside>\n    </div>\n</div>\n<script src=\"_static/documentation_options.js?v=91bfbbb6\"></script>\n<script src=\"_static/doctools.js?v=9bcbadda\"></script>\n<script src=\"_static/sphinx_highlight.js?v=dc90522c\"></script>\n<script src=\"_static/scripts/furo.js?v=5fa4622c\"></script>\n<script src=\"_static/translations.js?v=beaddf03\"></script>\n</body>\n</html>"
  },
  {
    "path": "docs/WechatAPIClient/py-modindex.html",
    "content": "<!doctype html>\n<html class=\"no-js\" data-content_root=\"./\" lang=\"zh-CN\">\n<head>\n    <meta charset=\"utf-8\"/>\n    <meta content=\"width=device-width,initial-scale=1\" name=\"viewport\"/>\n    <meta content=\"light dark\" name=\"color-scheme\">\n    <link href=\"genindex.html\" rel=\"index\" title=\"索引\"/>\n    <link href=\"search.html\" rel=\"search\" title=\"搜索\"/>\n\n    <!-- Generated with Sphinx 8.1.3 and Furo 2024.08.06 --><title>Python 模块索引 - XYBotV2</title>\n    <link href=\"_static/pygments.css?v=8f2a1f02\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"_static/styles/furo.css?v=354aac6f\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"_static/styles/furo-extensions.css?v=302659d7\" rel=\"stylesheet\" type=\"text/css\"/>\n\n\n    <style>\n        body {\n            --color-code-background: #f8f8f8;\n            --color-code-foreground: black;\n            --color-brand-primary: #2962ff;\n            --color-brand-content: #2962ff;\n\n        }\n\n        @media not print {\n            body[data-theme=\"dark\"] {\n                --color-code-background: #202020;\n                --color-code-foreground: #d0d0d0;\n\n            }\n\n            @media (prefers-color-scheme: dark) {\n                body:not([data-theme=\"light\"]) {\n                    --color-code-background: #202020;\n                    --color-code-foreground: #d0d0d0;\n\n                }\n            }\n        }\n    </style>\n</head>\n<body>\n\n<script>\n    document.body.dataset.theme = localStorage.getItem(\"theme\") || \"auto\";\n</script>\n\n\n<svg style=\"display: none;\" xmlns=\"http://www.w3.org/2000/svg\">\n    <symbol id=\"svg-toc\" viewBox=\"0 0 24 24\">\n        <title>Contents</title>\n        <svg fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 1024 1024\">\n            <path d=\"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-menu\" viewBox=\"0 0 24 24\">\n        <title>Menu</title>\n        <svg class=\"feather-menu\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-arrow-right\" viewBox=\"0 0 24 24\">\n        <title>Expand</title>\n        <svg class=\"feather-chevron-right\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <polyline points=\"9 18 15 12 9 6\"></polyline>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun\" viewBox=\"0 0 24 24\">\n        <title>Light mode</title>\n        <svg class=\"feather-sun\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\n            <line x1=\"12\" x2=\"12\" y1=\"1\" y2=\"3\"></line>\n            <line x1=\"12\" x2=\"12\" y1=\"21\" y2=\"23\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"4.22\" y2=\"5.64\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"18.36\" y2=\"19.78\"></line>\n            <line x1=\"1\" x2=\"3\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"21\" x2=\"23\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"19.78\" y2=\"18.36\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"5.64\" y2=\"4.22\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon\" viewBox=\"0 0 24 24\">\n        <title>Dark mode</title>\n        <svg class=\"icon-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun-with-moon\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in light mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 5.411 14.504 C 5.471 14.504 5.532 14.504 5.591 14.504 C 3.639 16.319 4.383 19.569 6.931 20.352 C 7.693 20.586 8.512 20.551 9.25 20.252 C 8.023 23.207 4.056 23.725 2.11 21.184 C 0.166 18.642 1.702 14.949 4.874 14.536 C 5.051 14.512 5.231 14.5 5.411 14.5 L 5.411 14.504 Z\"\n                  style=\"opacity: 50%\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"3.25\" y2=\"1.25\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"15.85\" y2=\"17.85\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"5.094\" y2=\"3.68\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"14.05\" y2=\"15.464\"/>\n            <line x1=\"8.2\" x2=\"6.2\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"20.8\" x2=\"22.8\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"14.006\" y2=\"15.42\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"5.05\" y2=\"3.636\"/>\n            <circle cx=\"14.5\" cy=\"9.55\" r=\"3.6\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon-with-sun\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in dark mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 8.282 7.007 C 8.385 7.007 8.494 7.007 8.595 7.007 C 5.18 10.184 6.481 15.869 10.942 17.24 C 12.275 17.648 13.706 17.589 15 17.066 C 12.851 22.236 5.91 23.143 2.505 18.696 C -0.897 14.249 1.791 7.786 7.342 7.063 C 7.652 7.021 7.965 7 8.282 7 L 8.282 7.007 Z\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"3.705\" y2=\"2.5\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"11.295\" y2=\"12.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"4.816\" y2=\"3.964\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"10.212\" y2=\"11.063\"/>\n            <line style=\"opacity: 50%\" x1=\"14.205\" x2=\"13.001\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"21.795\" x2=\"23\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"10.184\" y2=\"11.036\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"4.789\" y2=\"3.937\"/>\n            <circle cx=\"18\" cy=\"7.5\" r=\"2.169\" style=\"opacity: 50%\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-pencil\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-pencil-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4\"/>\n            <path d=\"M13.5 6.5l4 4\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-eye\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-eye-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\"/>\n            <path\n                    d=\"M11.11 17.958c-3.209 -.307 -5.91 -2.293 -8.11 -5.958c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6c-.21 .352 -.427 .688 -.647 1.008\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n</svg>\n\n<input class=\"sidebar-toggle\" id=\"__navigation\" name=\"__navigation\" type=\"checkbox\">\n<input class=\"sidebar-toggle\" id=\"__toc\" name=\"__toc\" type=\"checkbox\">\n<label class=\"overlay sidebar-overlay\" for=\"__navigation\">\n    <div class=\"visually-hidden\">Hide navigation sidebar</div>\n</label>\n<label class=\"overlay toc-overlay\" for=\"__toc\">\n    <div class=\"visually-hidden\">Hide table of contents sidebar</div>\n</label>\n\n<a class=\"skip-to-content muted-link\" href=\"#furo-main-content\">Skip to content</a>\n\n\n\n<div class=\"page\">\n    <header class=\"mobile-header\">\n        <div class=\"header-left\">\n            <label class=\"nav-overlay-icon\" for=\"__navigation\">\n                <div class=\"visually-hidden\">Toggle site navigation sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-menu\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n        <div class=\"header-center\">\n            <a href=\"index.html\">\n                <div class=\"brand\">XYBotV2</div>\n            </a>\n        </div>\n        <div class=\"header-right\">\n            <div class=\"theme-toggle-container theme-toggle-header\">\n                <button class=\"theme-toggle\">\n                    <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                    <svg class=\"theme-icon-when-auto-light\">\n                        <use href=\"#svg-sun-with-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-auto-dark\">\n                        <use href=\"#svg-moon-with-sun\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-dark\">\n                        <use href=\"#svg-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-light\">\n                        <use href=\"#svg-sun\"></use>\n                    </svg>\n                </button>\n            </div>\n            <label class=\"toc-overlay-icon toc-header-icon no-toc\" for=\"__toc\">\n                <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-toc\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n    </header>\n    <aside class=\"sidebar-drawer\">\n        <div class=\"sidebar-container\">\n\n            <div class=\"sidebar-sticky\">\n                <div class=\"sidebar-scroll\"><a class=\"sidebar-brand\" href=\"index.html\">\n\n\n                    <span class=\"sidebar-brand-text\">XYBotV2</span>\n\n                </a>\n                    <form action=\"search.html\" class=\"sidebar-search-container\" method=\"get\" role=\"search\">\n                        <input aria-label=\"搜索\" class=\"sidebar-search\" name=\"q\" placeholder=\"搜索\">\n                        <input name=\"check_keywords\" type=\"hidden\" value=\"yes\">\n                        <input name=\"area\" type=\"hidden\" value=\"default\">\n                    </form>\n                    <div id=\"searchbox\"></div>\n                    <div class=\"sidebar-tree\">\n\n                    </div>\n                    <div class=\"sidebar-tree\">\n                        <p class=\"caption\"><span class=\"caption-text\">重要函数导航</span></p>\n                        <ul>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">登录</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.is_running\">检查WechatAPI是否在运行</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_qr_code\">获取登录二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.awaken_login\">二次登录(唤醒登录)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.check_login_uuid\">检查登录的UUID状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_cached_info\">获取登录缓存信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.log_out\">登出当前账号</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.heartbeat\">发送心跳包</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\">开始自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\">停止自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\">获取自动心跳状态</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">消息</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.sync_message\">同步消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_text_message\">发送文本消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_image_message\">发送图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_voice_message\">发送语音消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_video_message\">发送视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_link_message\">发送链接消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_card_message\">发送名片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_app_message\">发送应用(xml)消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_emoji_message\">发送表情消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\">转发图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\">转发视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\">转发文件消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.revoke_message\">撤回消息</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">用户</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_my_qrcode\">获取个人二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_profile\">获取用户信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.is_logged_in\">检查是否登录</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">群聊</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\">获取群聊信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\">获取群聊公告</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\">获取群聊成员列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\">获取群聊二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\">添加群成员(群聊最多40人)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\">邀请群聊成员(群聊大于40人)</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">好友</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contact\">获取联系人信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_detail\">获取联系人详情</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_list\">获取联系人列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_nickname\">获取用户昵称</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.accept_friend\">接受好友请求</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">红包</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\">获取红包详情</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">工具</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.check_database\">检查数据库状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.set_step\">设置步数</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_image\">下载高清图片</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_video\">下载视频</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_voice\">下载语音文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_attach\">下载附件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_byte\">base64转字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_file\">base64转文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.byte_to_base64\">字节转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.file_to_base64\">文件转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\">silk的base64转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\">silk字节转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\">WAV字节转AMR的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\">WAV字节转AMR字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\">WAV字节转silk的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\">WAV字节转silk字节</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                        </ul>\n                    </div>\n                </div>\n            </div>\n\n        </div>\n    </aside>\n    <div class=\"main\">\n        <div class=\"content\">\n            <div class=\"article-container\">\n                <a class=\"back-to-top muted-link\" href=\"#\">\n                    <svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n                        <path d=\"M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z\"></path>\n                    </svg>\n                    <span>Back to top</span>\n                </a>\n                <div class=\"content-icon-container\">\n                    <div class=\"theme-toggle-container theme-toggle-content\">\n                        <button class=\"theme-toggle\">\n                            <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                            <svg class=\"theme-icon-when-auto-light\">\n                                <use href=\"#svg-sun-with-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-auto-dark\">\n                                <use href=\"#svg-moon-with-sun\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-dark\">\n                                <use href=\"#svg-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-light\">\n                                <use href=\"#svg-sun\"></use>\n                            </svg>\n                        </button>\n                    </div>\n                    <label class=\"toc-overlay-icon toc-content-icon no-toc\" for=\"__toc\">\n                        <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                        <i class=\"icon\">\n                            <svg>\n                                <use href=\"#svg-toc\"></use>\n                            </svg>\n                        </i>\n                    </label>\n                </div>\n                <article id=\"furo-main-content\" role=\"main\">\n\n                    <section class=\"domainindex-section\">\n                        <h1>Python 模块索引</h1>\n                        <div class=\"domainindex-jumpbox\"><a href=\"#cap-w\"><strong>w</strong></a></div>\n                    </section>\n                    <table class=\"domainindex-table\">\n                        <tr class=\"pcap\">\n                            <td></td>\n                            <td>&#160;</td>\n                            <td></td>\n                        </tr>\n                        <tr class=\"cap\" id=\"cap-w\">\n                            <td></td>\n                            <td><strong>w</strong></td>\n                            <td></td>\n                        </tr>\n                        <tr>\n                            <td><img alt=\"-\" class=\"toggler\"\n                                     id=\"toggle-1\" src=\"_static/minus.png\" style=\"display: none\"/></td>\n                            <td>\n                                <code class=\"xref\">WechatAPI</code></td>\n                            <td>\n                                <em></em></td>\n                        </tr>\n                        <tr class=\"cg-1\">\n                            <td></td>\n                            <td>&#160;&#160;&#160;\n                                <a href=\"index.html#module-WechatAPI.Client.base\"><code class=\"xref\">WechatAPI.Client.base</code></a>\n                            </td>\n                            <td>\n                                <em></em></td>\n                        </tr>\n                        <tr class=\"cg-1\">\n                            <td></td>\n                            <td>&#160;&#160;&#160;\n                                <a href=\"index.html#module-WechatAPI.Client.chatroom\"><code class=\"xref\">WechatAPI.Client.chatroom</code></a>\n                            </td>\n                            <td>\n                                <em></em></td>\n                        </tr>\n                        <tr class=\"cg-1\">\n                            <td></td>\n                            <td>&#160;&#160;&#160;\n                                <a href=\"index.html#module-WechatAPI.Client.friend\"><code class=\"xref\">WechatAPI.Client.friend</code></a>\n                            </td>\n                            <td>\n                                <em></em></td>\n                        </tr>\n                        <tr class=\"cg-1\">\n                            <td></td>\n                            <td>&#160;&#160;&#160;\n                                <a href=\"index.html#module-WechatAPI.Client.hongbao\"><code class=\"xref\">WechatAPI.Client.hongbao</code></a>\n                            </td>\n                            <td>\n                                <em></em></td>\n                        </tr>\n                        <tr class=\"cg-1\">\n                            <td></td>\n                            <td>&#160;&#160;&#160;\n                                <a href=\"index.html#module-WechatAPI.Client.login\"><code class=\"xref\">WechatAPI.Client.login</code></a>\n                            </td>\n                            <td>\n                                <em></em></td>\n                        </tr>\n                        <tr class=\"cg-1\">\n                            <td></td>\n                            <td>&#160;&#160;&#160;\n                                <a href=\"index.html#module-WechatAPI.Client.message\"><code class=\"xref\">WechatAPI.Client.message</code></a>\n                            </td>\n                            <td>\n                                <em></em></td>\n                        </tr>\n                        <tr class=\"cg-1\">\n                            <td></td>\n                            <td>&#160;&#160;&#160;\n                                <a href=\"index.html#module-WechatAPI.Client.protect\"><code class=\"xref\">WechatAPI.Client.protect</code></a>\n                            </td>\n                            <td>\n                                <em></em></td>\n                        </tr>\n                        <tr class=\"cg-1\">\n                            <td></td>\n                            <td>&#160;&#160;&#160;\n                                <a href=\"index.html#module-WechatAPI.Client.tool\"><code class=\"xref\">WechatAPI.Client.tool</code></a>\n                            </td>\n                            <td>\n                                <em></em></td>\n                        </tr>\n                        <tr class=\"cg-1\">\n                            <td></td>\n                            <td>&#160;&#160;&#160;\n                                <a href=\"index.html#module-WechatAPI.Client.user\"><code class=\"xref\">WechatAPI.Client.user</code></a>\n                            </td>\n                            <td>\n                                <em></em></td>\n                        </tr>\n                    </table>\n\n                </article>\n            </div>\n            <footer>\n\n                <div class=\"related-pages\">\n\n\n                </div>\n                <div class=\"bottom-of-page\">\n                    <div class=\"left-details\">\n                        <div class=\"copyright\">\n                            Copyright &#169; 2025, HenryXiaoYang\n                        </div>\n                        Made with <a href=\"https://www.sphinx-doc.org/\">Sphinx</a> and <a class=\"muted-link\"\n                                                                                          href=\"https://pradyunsg.me\">@pradyunsg</a>'s\n\n                        <a href=\"https://github.com/pradyunsg/furo\">Furo</a>\n\n                    </div>\n                    <div class=\"right-details\">\n\n                    </div>\n                </div>\n\n            </footer>\n        </div>\n        <aside class=\"toc-drawer no-toc\">\n\n\n        </aside>\n    </div>\n</div>\n<script src=\"_static/documentation_options.js?v=91bfbbb6\"></script>\n<script src=\"_static/doctools.js?v=9bcbadda\"></script>\n<script src=\"_static/sphinx_highlight.js?v=dc90522c\"></script>\n<script src=\"_static/scripts/furo.js?v=5fa4622c\"></script>\n<script src=\"_static/translations.js?v=beaddf03\"></script>\n</body>\n</html>"
  },
  {
    "path": "docs/WechatAPIClient/search.html",
    "content": "<!doctype html>\n<html class=\"no-js\" data-content_root=\"./\" lang=\"zh-CN\">\n<head>\n    <meta charset=\"utf-8\"/>\n    <meta content=\"width=device-width,initial-scale=1\" name=\"viewport\"/>\n    <meta content=\"light dark\" name=\"color-scheme\">\n    <link href=\"genindex.html\" rel=\"index\" title=\"索引\"/>\n    <link href=\"#\" rel=\"search\" title=\"搜索\"/>\n\n    <!-- Generated with Sphinx 8.1.3 and Furo 2024.08.06 -->\n    <meta content=\"noindex\" name=\"robots\"/>\n    <title>搜索 - XYBotV2</title>\n    <link href=\"_static/pygments.css?v=8f2a1f02\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"_static/styles/furo.css?v=354aac6f\" rel=\"stylesheet\" type=\"text/css\"/>\n    <link href=\"_static/styles/furo-extensions.css?v=302659d7\" rel=\"stylesheet\" type=\"text/css\"/>\n\n\n    <style>\n        body {\n            --color-code-background: #f8f8f8;\n            --color-code-foreground: black;\n            --color-brand-primary: #2962ff;\n            --color-brand-content: #2962ff;\n\n        }\n\n        @media not print {\n            body[data-theme=\"dark\"] {\n                --color-code-background: #202020;\n                --color-code-foreground: #d0d0d0;\n\n            }\n\n            @media (prefers-color-scheme: dark) {\n                body:not([data-theme=\"light\"]) {\n                    --color-code-background: #202020;\n                    --color-code-foreground: #d0d0d0;\n\n                }\n            }\n        }\n    </style>\n</head>\n<body>\n\n<script>\n    document.body.dataset.theme = localStorage.getItem(\"theme\") || \"auto\";\n</script>\n\n\n<svg style=\"display: none;\" xmlns=\"http://www.w3.org/2000/svg\">\n    <symbol id=\"svg-toc\" viewBox=\"0 0 24 24\">\n        <title>Contents</title>\n        <svg fill=\"currentColor\" stroke=\"currentColor\" stroke-width=\"0\" viewBox=\"0 0 1024 1024\">\n            <path d=\"M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-menu\" viewBox=\"0 0 24 24\">\n        <title>Menu</title>\n        <svg class=\"feather-menu\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <line x1=\"3\" x2=\"21\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"6\" y2=\"6\"></line>\n            <line x1=\"3\" x2=\"21\" y1=\"18\" y2=\"18\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-arrow-right\" viewBox=\"0 0 24 24\">\n        <title>Expand</title>\n        <svg class=\"feather-chevron-right\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"2\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <polyline points=\"9 18 15 12 9 6\"></polyline>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun\" viewBox=\"0 0 24 24\">\n        <title>Light mode</title>\n        <svg class=\"feather-sun\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\n            <line x1=\"12\" x2=\"12\" y1=\"1\" y2=\"3\"></line>\n            <line x1=\"12\" x2=\"12\" y1=\"21\" y2=\"23\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"4.22\" y2=\"5.64\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"18.36\" y2=\"19.78\"></line>\n            <line x1=\"1\" x2=\"3\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"21\" x2=\"23\" y1=\"12\" y2=\"12\"></line>\n            <line x1=\"4.22\" x2=\"5.64\" y1=\"19.78\" y2=\"18.36\"></line>\n            <line x1=\"18.36\" x2=\"19.78\" y1=\"5.64\" y2=\"4.22\"></line>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon\" viewBox=\"0 0 24 24\">\n        <title>Dark mode</title>\n        <svg class=\"icon-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-sun-with-moon\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in light mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 5.411 14.504 C 5.471 14.504 5.532 14.504 5.591 14.504 C 3.639 16.319 4.383 19.569 6.931 20.352 C 7.693 20.586 8.512 20.551 9.25 20.252 C 8.023 23.207 4.056 23.725 2.11 21.184 C 0.166 18.642 1.702 14.949 4.874 14.536 C 5.051 14.512 5.231 14.5 5.411 14.5 L 5.411 14.504 Z\"\n                  style=\"opacity: 50%\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"3.25\" y2=\"1.25\"/>\n            <line x1=\"14.5\" x2=\"14.5\" y1=\"15.85\" y2=\"17.85\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"5.094\" y2=\"3.68\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"14.05\" y2=\"15.464\"/>\n            <line x1=\"8.2\" x2=\"6.2\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"20.8\" x2=\"22.8\" y1=\"9.55\" y2=\"9.55\"/>\n            <line x1=\"10.044\" x2=\"8.63\" y1=\"14.006\" y2=\"15.42\"/>\n            <line x1=\"19\" x2=\"20.414\" y1=\"5.05\" y2=\"3.636\"/>\n            <circle cx=\"14.5\" cy=\"9.55\" r=\"3.6\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-moon-with-sun\" viewBox=\"0 0 24 24\">\n        <title>Auto light/dark, in dark mode</title>\n        <svg class=\"icon-custom-derived-from-feather-sun-and-tabler-moon\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\"\n             xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M 8.282 7.007 C 8.385 7.007 8.494 7.007 8.595 7.007 C 5.18 10.184 6.481 15.869 10.942 17.24 C 12.275 17.648 13.706 17.589 15 17.066 C 12.851 22.236 5.91 23.143 2.505 18.696 C -0.897 14.249 1.791 7.786 7.342 7.063 C 7.652 7.021 7.965 7 8.282 7 L 8.282 7.007 Z\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"3.705\" y2=\"2.5\"/>\n            <line style=\"opacity: 50%\" x1=\"18\" x2=\"18\" y1=\"11.295\" y2=\"12.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"4.816\" y2=\"3.964\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"10.212\" y2=\"11.063\"/>\n            <line style=\"opacity: 50%\" x1=\"14.205\" x2=\"13.001\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"21.795\" x2=\"23\" y1=\"7.5\" y2=\"7.5\"/>\n            <line style=\"opacity: 50%\" x1=\"15.316\" x2=\"14.464\" y1=\"10.184\" y2=\"11.036\"/>\n            <line style=\"opacity: 50%\" x1=\"20.711\" x2=\"21.563\" y1=\"4.789\" y2=\"3.937\"/>\n            <circle cx=\"18\" cy=\"7.5\" r=\"2.169\" style=\"opacity: 50%\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-pencil\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-pencil-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4\"/>\n            <path d=\"M13.5 6.5l4 4\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n    <symbol id=\"svg-eye\" viewBox=\"0 0 24 24\">\n        <svg class=\"icon-tabler-eye-code\" fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"\n             stroke-linejoin=\"round\" stroke-width=\"1\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n            <path d=\"M0 0h24v24H0z\" fill=\"none\" stroke=\"none\"/>\n            <path d=\"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\"/>\n            <path\n                    d=\"M11.11 17.958c-3.209 -.307 -5.91 -2.293 -8.11 -5.958c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6c-.21 .352 -.427 .688 -.647 1.008\"/>\n            <path d=\"M20 21l2 -2l-2 -2\"/>\n            <path d=\"M17 17l-2 2l2 2\"/>\n        </svg>\n    </symbol>\n</svg>\n\n<input class=\"sidebar-toggle\" id=\"__navigation\" name=\"__navigation\" type=\"checkbox\">\n<input class=\"sidebar-toggle\" id=\"__toc\" name=\"__toc\" type=\"checkbox\">\n<label class=\"overlay sidebar-overlay\" for=\"__navigation\">\n    <div class=\"visually-hidden\">Hide navigation sidebar</div>\n</label>\n<label class=\"overlay toc-overlay\" for=\"__toc\">\n    <div class=\"visually-hidden\">Hide table of contents sidebar</div>\n</label>\n\n<a class=\"skip-to-content muted-link\" href=\"#furo-main-content\">Skip to content</a>\n\n\n\n<div class=\"page\">\n    <header class=\"mobile-header\">\n        <div class=\"header-left\">\n            <label class=\"nav-overlay-icon\" for=\"__navigation\">\n                <div class=\"visually-hidden\">Toggle site navigation sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-menu\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n        <div class=\"header-center\">\n            <a href=\"index.html\">\n                <div class=\"brand\">XYBotV2</div>\n            </a>\n        </div>\n        <div class=\"header-right\">\n            <div class=\"theme-toggle-container theme-toggle-header\">\n                <button class=\"theme-toggle\">\n                    <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                    <svg class=\"theme-icon-when-auto-light\">\n                        <use href=\"#svg-sun-with-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-auto-dark\">\n                        <use href=\"#svg-moon-with-sun\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-dark\">\n                        <use href=\"#svg-moon\"></use>\n                    </svg>\n                    <svg class=\"theme-icon-when-light\">\n                        <use href=\"#svg-sun\"></use>\n                    </svg>\n                </button>\n            </div>\n            <label class=\"toc-overlay-icon toc-header-icon no-toc\" for=\"__toc\">\n                <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                <i class=\"icon\">\n                    <svg>\n                        <use href=\"#svg-toc\"></use>\n                    </svg>\n                </i>\n            </label>\n        </div>\n    </header>\n    <aside class=\"sidebar-drawer\">\n        <div class=\"sidebar-container\">\n\n            <div class=\"sidebar-sticky\">\n                <div class=\"sidebar-scroll\"><a class=\"sidebar-brand\" href=\"index.html\">\n\n\n                    <span class=\"sidebar-brand-text\">XYBotV2</span>\n\n                </a>\n                    <form action=\"#\" class=\"sidebar-search-container\" method=\"get\" role=\"search\">\n                        <input aria-label=\"搜索\" class=\"sidebar-search\" name=\"q\" placeholder=\"搜索\">\n                        <input name=\"check_keywords\" type=\"hidden\" value=\"yes\">\n                        <input name=\"area\" type=\"hidden\" value=\"default\">\n                    </form>\n                    <div id=\"searchbox\"></div>\n                    <div class=\"sidebar-tree\">\n\n                    </div>\n                    <div class=\"sidebar-tree\">\n                        <p class=\"caption\"><span class=\"caption-text\">重要函数导航</span></p>\n                        <ul>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">登录</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.is_running\">检查WechatAPI是否在运行</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_qr_code\">获取登录二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.awaken_login\">二次登录(唤醒登录)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.check_login_uuid\">检查登录的UUID状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_cached_info\">获取登录缓存信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.log_out\">登出当前账号</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.heartbeat\">发送心跳包</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\">开始自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\">停止自动心跳</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\">获取自动心跳状态</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">消息</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.sync_message\">同步消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_text_message\">发送文本消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_image_message\">发送图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_voice_message\">发送语音消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_video_message\">发送视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_link_message\">发送链接消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_card_message\">发送名片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_app_message\">发送应用(xml)消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_emoji_message\">发送表情消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\">转发图片消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\">转发视频消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\">转发文件消息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.message.MessageMixin.revoke_message\">撤回消息</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">用户</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_my_qrcode\">获取个人二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.get_profile\">获取用户信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.user.UserMixin.is_logged_in\">检查是否登录</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">群聊</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\">获取群聊信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\">获取群聊公告</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\">获取群聊成员列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\">获取群聊二维码</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\">添加群成员(群聊最多40人)</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\">邀请群聊成员(群聊大于40人)</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">好友</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contact\">获取联系人信息</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_detail\">获取联系人详情</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_contract_list\">获取联系人列表</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.get_nickname\">获取用户昵称</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.friend.FriendMixin.accept_friend\">接受好友请求</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">红包</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\">获取红包详情</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                            <li class=\"toctree-l1\">\n                                <p class=\"caption\"><span class=\"caption-text\">工具</span></p>\n                                <ul>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.check_database\">检查数据库状态</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.set_step\">设置步数</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_image\">下载高清图片</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_video\">下载视频</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_voice\">下载语音文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.download_attach\">下载附件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_byte\">base64转字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.base64_to_file\">base64转文件</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.byte_to_base64\">字节转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.file_to_base64\">文件转base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\">silk的base64转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\">silk字节转wav字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\">WAV字节转AMR的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\">WAV字节转AMR字节</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\">WAV字节转silk的base64</a>\n                                    </li>\n\n                                    <li class=\"toctree-l2\">\n                                        <a class=\"reference internal\"\n                                           href=\"index.html#WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\">WAV字节转silk字节</a>\n                                    </li>\n\n                                </ul>\n                            </li>\n\n                        </ul>\n                    </div>\n                </div>\n            </div>\n\n        </div>\n    </aside>\n    <div class=\"main\">\n        <div class=\"content\">\n            <div class=\"article-container\">\n                <a class=\"back-to-top muted-link\" href=\"#\">\n                    <svg viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\">\n                        <path d=\"M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z\"></path>\n                    </svg>\n                    <span>Back to top</span>\n                </a>\n                <div class=\"content-icon-container\">\n                    <div class=\"theme-toggle-container theme-toggle-content\">\n                        <button class=\"theme-toggle\">\n                            <div class=\"visually-hidden\">Toggle Light / Dark / Auto color theme</div>\n                            <svg class=\"theme-icon-when-auto-light\">\n                                <use href=\"#svg-sun-with-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-auto-dark\">\n                                <use href=\"#svg-moon-with-sun\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-dark\">\n                                <use href=\"#svg-moon\"></use>\n                            </svg>\n                            <svg class=\"theme-icon-when-light\">\n                                <use href=\"#svg-sun\"></use>\n                            </svg>\n                        </button>\n                    </div>\n                    <label class=\"toc-overlay-icon toc-content-icon no-toc\" for=\"__toc\">\n                        <div class=\"visually-hidden\">Toggle table of contents sidebar</div>\n                        <i class=\"icon\">\n                            <svg>\n                                <use href=\"#svg-toc\"></use>\n                            </svg>\n                        </i>\n                    </label>\n                </div>\n                <article id=\"furo-main-content\" role=\"main\">\n\n                    <noscript>\n                        <div class=\"admonition error\">\n                            <p class=\"admonition-title\">错误</p>\n                            <p>\n                                Please activate JavaScript to enable the search functionality.\n                            </p>\n                        </div>\n                    </noscript>\n\n                    <div id=\"search-results\"></div>\n\n                </article>\n            </div>\n            <footer>\n\n                <div class=\"related-pages\">\n\n\n                </div>\n                <div class=\"bottom-of-page\">\n                    <div class=\"left-details\">\n                        <div class=\"copyright\">\n                            Copyright &#169; 2025, HenryXiaoYang\n                        </div>\n                        Made with <a href=\"https://www.sphinx-doc.org/\">Sphinx</a> and <a class=\"muted-link\"\n                                                                                          href=\"https://pradyunsg.me\">@pradyunsg</a>'s\n\n                        <a href=\"https://github.com/pradyunsg/furo\">Furo</a>\n\n                    </div>\n                    <div class=\"right-details\">\n\n                    </div>\n                </div>\n\n            </footer>\n        </div>\n        <aside class=\"toc-drawer no-toc\">\n\n\n        </aside>\n    </div>\n</div>\n<script src=\"_static/documentation_options.js?v=91bfbbb6\"></script>\n<script src=\"_static/doctools.js?v=9bcbadda\"></script>\n<script src=\"_static/sphinx_highlight.js?v=dc90522c\"></script>\n<script src=\"_static/scripts/furo.js?v=5fa4622c\"></script>\n<script src=\"_static/translations.js?v=beaddf03\"></script>\n\n<script src=\"_static/searchtools.js\"></script>\n<script src=\"_static/language_data.js\"></script>\n<script src=\"searchindex.js\"></script>\n</body>\n</html>"
  },
  {
    "path": "docs/WechatAPIClient/searchindex.js",
    "content": "Search.setIndex({\n    \"alltitles\": {\n        \"WechatAPIClient\": [[0, null]],\n        \"\\u4fdd\\u62a4\": [[0, \"module-WechatAPI.Client.protect\"]],\n        \"\\u57fa\\u7840\": [[0, \"module-WechatAPI.Client.base\"]],\n        \"\\u597d\\u53cb\": [[0, \"module-WechatAPI.Client.friend\"]],\n        \"\\u5de5\\u5177\": [[0, \"module-WechatAPI.Client.tool\"]],\n        \"\\u6d88\\u606f\": [[0, \"module-WechatAPI.Client.message\"]],\n        \"\\u7528\\u6237\": [[0, \"module-WechatAPI.Client.user\"]],\n        \"\\u767b\\u5f55\": [[0, \"module-WechatAPI.Client.login\"]],\n        \"\\u7d22\\u5f15\": [[0, \"id10\"]],\n        \"\\u7ea2\\u5305\": [[0, \"module-WechatAPI.Client.hongbao\"]],\n        \"\\u7fa4\\u804a\": [[0, \"module-WechatAPI.Client.chatroom\"]]\n    },\n    \"docnames\": [\"index\"],\n    \"envversion\": {\n        \"sphinx\": 64,\n        \"sphinx.domains.c\": 3,\n        \"sphinx.domains.changeset\": 1,\n        \"sphinx.domains.citation\": 1,\n        \"sphinx.domains.cpp\": 9,\n        \"sphinx.domains.index\": 1,\n        \"sphinx.domains.javascript\": 3,\n        \"sphinx.domains.math\": 2,\n        \"sphinx.domains.python\": 4,\n        \"sphinx.domains.rst\": 2,\n        \"sphinx.domains.std\": 2,\n        \"sphinx.ext.viewcode\": 1\n    },\n    \"filenames\": [\"index.rst\"],\n    \"indexentries\": {\n        \"__call__() \\uff08wechatapi.client.protect.singleton \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.protect.Singleton.__call__\", false]],\n        \"__init__() \\uff08wechatapi.client.protect.protect \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.protect.Protect.__init__\", false]],\n        \"_process_message_queue() \\uff08wechatapi.client.message.messagemixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.message.MessageMixin._process_message_queue\", false]],\n        \"_queue_message() \\uff08wechatapi.client.message.messagemixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.message.MessageMixin._queue_message\", false]],\n        \"_send_text_message() \\uff08wechatapi.client.message.messagemixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.message.MessageMixin._send_text_message\", false]],\n        \"accept_friend() \\uff08wechatapi.client.friend.friendmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.friend.FriendMixin.accept_friend\", false]],\n        \"add_chatroom_member() \\uff08wechatapi.client.chatroom.chatroommixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.chatroom.ChatroomMixin.add_chatroom_member\", false]],\n        \"awaken_login() \\uff08wechatapi.client.login.loginmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.login.LoginMixin.awaken_login\", false]],\n        \"base64_to_byte()\\uff08wechatapi.client.tool.toolmixin \\u9759\\u6001\\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin.base64_to_byte\", false]],\n        \"base64_to_file()\\uff08wechatapi.client.tool.toolmixin \\u9759\\u6001\\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin.base64_to_file\", false]],\n        \"byte_to_base64()\\uff08wechatapi.client.tool.toolmixin \\u9759\\u6001\\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin.byte_to_base64\", false]],\n        \"chatroommixin\\uff08wechatapi.client.chatroom \\u4e2d\\u7684\\u7c7b\\uff09\": [[0, \"WechatAPI.Client.chatroom.ChatroomMixin\", false]],\n        \"check() \\uff08wechatapi.client.protect.protect \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.protect.Protect.check\", false]],\n        \"check_database() \\uff08wechatapi.client.tool.toolmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin.check_database\", false]],\n        \"check_login_uuid() \\uff08wechatapi.client.login.loginmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.login.LoginMixin.check_login_uuid\", false]],\n        \"create_device_id()\\uff08wechatapi.client.login.loginmixin \\u9759\\u6001\\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.login.LoginMixin.create_device_id\", false]],\n        \"create_device_name()\\uff08wechatapi.client.login.loginmixin \\u9759\\u6001\\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.login.LoginMixin.create_device_name\", false]],\n        \"data_len\\uff08wechatapi.client.base.section \\u5c5e\\u6027\\uff09\": [[0, \"WechatAPI.Client.base.Section.data_len\", false]],\n        \"download_attach() \\uff08wechatapi.client.tool.toolmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin.download_attach\", false]],\n        \"download_image() \\uff08wechatapi.client.tool.toolmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin.download_image\", false]],\n        \"download_video() \\uff08wechatapi.client.tool.toolmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin.download_video\", false]],\n        \"download_voice() \\uff08wechatapi.client.tool.toolmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin.download_voice\", false]],\n        \"error_handler()\\uff08wechatapi.client.base.wechatapiclientbase \\u9759\\u6001\\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.base.WechatAPIClientBase.error_handler\", false]],\n        \"file_to_base64()\\uff08wechatapi.client.tool.toolmixin \\u9759\\u6001\\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin.file_to_base64\", false]],\n        \"friendmixin\\uff08wechatapi.client.friend \\u4e2d\\u7684\\u7c7b\\uff09\": [[0, \"WechatAPI.Client.friend.FriendMixin\", false]],\n        \"get_auto_heartbeat_status() \\uff08wechatapi.client.login.loginmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.login.LoginMixin.get_auto_heartbeat_status\", false]],\n        \"get_cached_info() \\uff08wechatapi.client.login.loginmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.login.LoginMixin.get_cached_info\", false]],\n        \"get_chatroom_announce() \\uff08wechatapi.client.chatroom.chatroommixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_announce\", false]],\n        \"get_chatroom_info() \\uff08wechatapi.client.chatroom.chatroommixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_info\", false]],\n        \"get_chatroom_member_list() \\uff08wechatapi.client.chatroom.chatroommixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_member_list\", false]],\n        \"get_chatroom_qrcode() \\uff08wechatapi.client.chatroom.chatroommixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.chatroom.ChatroomMixin.get_chatroom_qrcode\", false]],\n        \"get_contact() \\uff08wechatapi.client.friend.friendmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.friend.FriendMixin.get_contact\", false]],\n        \"get_contract_detail() \\uff08wechatapi.client.friend.friendmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.friend.FriendMixin.get_contract_detail\", false]],\n        \"get_contract_list() \\uff08wechatapi.client.friend.friendmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.friend.FriendMixin.get_contract_list\", false]],\n        \"get_hongbao_detail() \\uff08wechatapi.client.hongbao.hongbaomixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.hongbao.HongBaoMixin.get_hongbao_detail\", false]],\n        \"get_my_qrcode() \\uff08wechatapi.client.user.usermixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.user.UserMixin.get_my_qrcode\", false]],\n        \"get_nickname() \\uff08wechatapi.client.friend.friendmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.friend.FriendMixin.get_nickname\", false]],\n        \"get_profile() \\uff08wechatapi.client.user.usermixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.user.UserMixin.get_profile\", false]],\n        \"get_qr_code() \\uff08wechatapi.client.login.loginmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.login.LoginMixin.get_qr_code\", false]],\n        \"heartbeat() \\uff08wechatapi.client.login.loginmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.login.LoginMixin.heartbeat\", false]],\n        \"hongbaomixin\\uff08wechatapi.client.hongbao \\u4e2d\\u7684\\u7c7b\\uff09\": [[0, \"WechatAPI.Client.hongbao.HongBaoMixin\", false]],\n        \"invite_chatroom_member() \\uff08wechatapi.client.chatroom.chatroommixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.chatroom.ChatroomMixin.invite_chatroom_member\", false]],\n        \"ip\\uff08wechatapi.client.base.proxy \\u5c5e\\u6027\\uff09\": [[0, \"WechatAPI.Client.base.Proxy.ip\", false]],\n        \"is_logged_in() \\uff08wechatapi.client.user.usermixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.user.UserMixin.is_logged_in\", false]],\n        \"is_running() \\uff08wechatapi.client.login.loginmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.login.LoginMixin.is_running\", false]],\n        \"log_out() \\uff08wechatapi.client.login.loginmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.login.LoginMixin.log_out\", false]],\n        \"loginmixin\\uff08wechatapi.client.login \\u4e2d\\u7684\\u7c7b\\uff09\": [[0, \"WechatAPI.Client.login.LoginMixin\", false]],\n        \"messagemixin\\uff08wechatapi.client.message \\u4e2d\\u7684\\u7c7b\\uff09\": [[0, \"WechatAPI.Client.message.MessageMixin\", false]],\n        \"module\": [[0, \"module-WechatAPI.Client.base\", false], [0, \"module-WechatAPI.Client.chatroom\", false], [0, \"module-WechatAPI.Client.friend\", false], [0, \"module-WechatAPI.Client.hongbao\", false], [0, \"module-WechatAPI.Client.login\", false], [0, \"module-WechatAPI.Client.message\", false], [0, \"module-WechatAPI.Client.protect\", false], [0, \"module-WechatAPI.Client.tool\", false], [0, \"module-WechatAPI.Client.user\", false]],\n        \"password\\uff08wechatapi.client.base.proxy \\u5c5e\\u6027\\uff09\": [[0, \"WechatAPI.Client.base.Proxy.password\", false]],\n        \"port\\uff08wechatapi.client.base.proxy \\u5c5e\\u6027\\uff09\": [[0, \"WechatAPI.Client.base.Proxy.port\", false]],\n        \"protect\\uff08wechatapi.client.protect \\u4e2d\\u7684\\u7c7b\\uff09\": [[0, \"WechatAPI.Client.protect.Protect\", false]],\n        \"proxy\\uff08wechatapi.client.base \\u4e2d\\u7684\\u7c7b\\uff09\": [[0, \"WechatAPI.Client.base.Proxy\", false]],\n        \"revoke_message() \\uff08wechatapi.client.message.messagemixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.message.MessageMixin.revoke_message\", false]],\n        \"section\\uff08wechatapi.client.base \\u4e2d\\u7684\\u7c7b\\uff09\": [[0, \"WechatAPI.Client.base.Section\", false]],\n        \"send_app_message() \\uff08wechatapi.client.message.messagemixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.message.MessageMixin.send_app_message\", false]],\n        \"send_card_message() \\uff08wechatapi.client.message.messagemixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.message.MessageMixin.send_card_message\", false]],\n        \"send_cdn_file_msg() \\uff08wechatapi.client.message.messagemixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.message.MessageMixin.send_cdn_file_msg\", false]],\n        \"send_cdn_img_msg() \\uff08wechatapi.client.message.messagemixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.message.MessageMixin.send_cdn_img_msg\", false]],\n        \"send_cdn_video_msg() \\uff08wechatapi.client.message.messagemixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.message.MessageMixin.send_cdn_video_msg\", false]],\n        \"send_emoji_message() \\uff08wechatapi.client.message.messagemixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.message.MessageMixin.send_emoji_message\", false]],\n        \"send_image_message() \\uff08wechatapi.client.message.messagemixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.message.MessageMixin.send_image_message\", false]],\n        \"send_link_message() \\uff08wechatapi.client.message.messagemixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.message.MessageMixin.send_link_message\", false]],\n        \"send_text_message() \\uff08wechatapi.client.message.messagemixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.message.MessageMixin.send_text_message\", false]],\n        \"send_video_message() \\uff08wechatapi.client.message.messagemixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.message.MessageMixin.send_video_message\", false]],\n        \"send_voice_message() \\uff08wechatapi.client.message.messagemixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.message.MessageMixin.send_voice_message\", false]],\n        \"set_proxy() \\uff08wechatapi.client.tool.toolmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin.set_proxy\", false]],\n        \"set_step() \\uff08wechatapi.client.tool.toolmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin.set_step\", false]],\n        \"silk_base64_to_wav_byte()\\uff08wechatapi.client.tool.toolmixin \\u9759\\u6001\\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin.silk_base64_to_wav_byte\", false]],\n        \"silk_byte_to_byte_wav_byte()\\uff08wechatapi.client.tool.toolmixin \\u9759\\u6001\\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin.silk_byte_to_byte_wav_byte\", false]],\n        \"singleton\\uff08wechatapi.client.protect \\u4e2d\\u7684\\u7c7b\\uff09\": [[0, \"WechatAPI.Client.protect.Singleton\", false]],\n        \"start_auto_heartbeat() \\uff08wechatapi.client.login.loginmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.login.LoginMixin.start_auto_heartbeat\", false]],\n        \"start_pos\\uff08wechatapi.client.base.section \\u5c5e\\u6027\\uff09\": [[0, \"WechatAPI.Client.base.Section.start_pos\", false]],\n        \"stop_auto_heartbeat() \\uff08wechatapi.client.login.loginmixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.login.LoginMixin.stop_auto_heartbeat\", false]],\n        \"sync_message() \\uff08wechatapi.client.message.messagemixin \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.message.MessageMixin.sync_message\", false]],\n        \"toolmixin\\uff08wechatapi.client.tool \\u4e2d\\u7684\\u7c7b\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin\", false]],\n        \"update_login_status() \\uff08wechatapi.client.protect.protect \\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.protect.Protect.update_login_status\", false]],\n        \"usermixin\\uff08wechatapi.client.user \\u4e2d\\u7684\\u7c7b\\uff09\": [[0, \"WechatAPI.Client.user.UserMixin\", false]],\n        \"username\\uff08wechatapi.client.base.proxy \\u5c5e\\u6027\\uff09\": [[0, \"WechatAPI.Client.base.Proxy.username\", false]],\n        \"wav_byte_to_amr_base64()\\uff08wechatapi.client.tool.toolmixin \\u9759\\u6001\\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_base64\", false]],\n        \"wav_byte_to_amr_byte()\\uff08wechatapi.client.tool.toolmixin \\u9759\\u6001\\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin.wav_byte_to_amr_byte\", false]],\n        \"wav_byte_to_silk_base64()\\uff08wechatapi.client.tool.toolmixin \\u9759\\u6001\\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_base64\", false]],\n        \"wav_byte_to_silk_byte()\\uff08wechatapi.client.tool.toolmixin \\u9759\\u6001\\u65b9\\u6cd5\\uff09\": [[0, \"WechatAPI.Client.tool.ToolMixin.wav_byte_to_silk_byte\", false]],\n        \"wechatapi.client.base\": [[0, \"module-WechatAPI.Client.base\", false]],\n        \"wechatapi.client.chatroom\": [[0, \"module-WechatAPI.Client.chatroom\", false]],\n        \"wechatapi.client.friend\": [[0, \"module-WechatAPI.Client.friend\", false]],\n        \"wechatapi.client.hongbao\": [[0, \"module-WechatAPI.Client.hongbao\", false]],\n        \"wechatapi.client.login\": [[0, \"module-WechatAPI.Client.login\", false]],\n        \"wechatapi.client.message\": [[0, \"module-WechatAPI.Client.message\", false]],\n        \"wechatapi.client.protect\": [[0, \"module-WechatAPI.Client.protect\", false]],\n        \"wechatapi.client.tool\": [[0, \"module-WechatAPI.Client.tool\", false]],\n        \"wechatapi.client.user\": [[0, \"module-WechatAPI.Client.user\", false]],\n        \"wechatapiclientbase\\uff08wechatapi.client.base \\u4e2d\\u7684\\u7c7b\\uff09\": [[0, \"WechatAPI.Client.base.WechatAPIClientBase\", false]]\n    },\n    \"objects\": {\n        \"WechatAPI.Client\": [[0, 0, 0, \"-\", \"base\"], [0, 0, 0, \"-\", \"chatroom\"], [0, 0, 0, \"-\", \"friend\"], [0, 0, 0, \"-\", \"hongbao\"], [0, 0, 0, \"-\", \"login\"], [0, 0, 0, \"-\", \"message\"], [0, 0, 0, \"-\", \"protect\"], [0, 0, 0, \"-\", \"tool\"], [0, 0, 0, \"-\", \"user\"]],\n        \"WechatAPI.Client.base\": [[0, 1, 1, \"\", \"Proxy\"], [0, 1, 1, \"\", \"Section\"], [0, 1, 1, \"\", \"WechatAPIClientBase\"]],\n        \"WechatAPI.Client.base.Proxy\": [[0, 2, 1, \"\", \"ip\"], [0, 2, 1, \"\", \"password\"], [0, 2, 1, \"\", \"port\"], [0, 2, 1, \"\", \"username\"]],\n        \"WechatAPI.Client.base.Section\": [[0, 2, 1, \"\", \"data_len\"], [0, 2, 1, \"\", \"start_pos\"]],\n        \"WechatAPI.Client.base.WechatAPIClientBase\": [[0, 3, 1, \"\", \"error_handler\"]],\n        \"WechatAPI.Client.chatroom\": [[0, 1, 1, \"\", \"ChatroomMixin\"]],\n        \"WechatAPI.Client.chatroom.ChatroomMixin\": [[0, 3, 1, \"\", \"add_chatroom_member\"], [0, 3, 1, \"\", \"get_chatroom_announce\"], [0, 3, 1, \"\", \"get_chatroom_info\"], [0, 3, 1, \"\", \"get_chatroom_member_list\"], [0, 3, 1, \"\", \"get_chatroom_qrcode\"], [0, 3, 1, \"\", \"invite_chatroom_member\"]],\n        \"WechatAPI.Client.friend\": [[0, 1, 1, \"\", \"FriendMixin\"]],\n        \"WechatAPI.Client.friend.FriendMixin\": [[0, 3, 1, \"\", \"accept_friend\"], [0, 3, 1, \"\", \"get_contact\"], [0, 3, 1, \"\", \"get_contract_detail\"], [0, 3, 1, \"\", \"get_contract_list\"], [0, 3, 1, \"\", \"get_nickname\"]],\n        \"WechatAPI.Client.hongbao\": [[0, 1, 1, \"\", \"HongBaoMixin\"]],\n        \"WechatAPI.Client.hongbao.HongBaoMixin\": [[0, 3, 1, \"\", \"get_hongbao_detail\"]],\n        \"WechatAPI.Client.login\": [[0, 1, 1, \"\", \"LoginMixin\"]],\n        \"WechatAPI.Client.login.LoginMixin\": [[0, 3, 1, \"\", \"awaken_login\"], [0, 3, 1, \"\", \"check_login_uuid\"], [0, 3, 1, \"\", \"create_device_id\"], [0, 3, 1, \"\", \"create_device_name\"], [0, 3, 1, \"\", \"get_auto_heartbeat_status\"], [0, 3, 1, \"\", \"get_cached_info\"], [0, 3, 1, \"\", \"get_qr_code\"], [0, 3, 1, \"\", \"heartbeat\"], [0, 3, 1, \"\", \"is_running\"], [0, 3, 1, \"\", \"log_out\"], [0, 3, 1, \"\", \"start_auto_heartbeat\"], [0, 3, 1, \"\", \"stop_auto_heartbeat\"]],\n        \"WechatAPI.Client.message\": [[0, 1, 1, \"\", \"MessageMixin\"]],\n        \"WechatAPI.Client.message.MessageMixin\": [[0, 3, 1, \"\", \"_process_message_queue\"], [0, 3, 1, \"\", \"_queue_message\"], [0, 3, 1, \"\", \"_send_text_message\"], [0, 3, 1, \"\", \"revoke_message\"], [0, 3, 1, \"\", \"send_app_message\"], [0, 3, 1, \"\", \"send_card_message\"], [0, 3, 1, \"\", \"send_cdn_file_msg\"], [0, 3, 1, \"\", \"send_cdn_img_msg\"], [0, 3, 1, \"\", \"send_cdn_video_msg\"], [0, 3, 1, \"\", \"send_emoji_message\"], [0, 3, 1, \"\", \"send_image_message\"], [0, 3, 1, \"\", \"send_link_message\"], [0, 3, 1, \"\", \"send_text_message\"], [0, 3, 1, \"\", \"send_video_message\"], [0, 3, 1, \"\", \"send_voice_message\"], [0, 3, 1, \"\", \"sync_message\"]],\n        \"WechatAPI.Client.protect\": [[0, 1, 1, \"\", \"Protect\"], [0, 1, 1, \"\", \"Singleton\"]],\n        \"WechatAPI.Client.protect.Protect\": [[0, 3, 1, \"\", \"__init__\"], [0, 3, 1, \"\", \"check\"], [0, 3, 1, \"\", \"update_login_status\"]],\n        \"WechatAPI.Client.protect.Singleton\": [[0, 3, 1, \"\", \"__call__\"]],\n        \"WechatAPI.Client.tool\": [[0, 1, 1, \"\", \"ToolMixin\"]],\n        \"WechatAPI.Client.tool.ToolMixin\": [[0, 3, 1, \"\", \"base64_to_byte\"], [0, 3, 1, \"\", \"base64_to_file\"], [0, 3, 1, \"\", \"byte_to_base64\"], [0, 3, 1, \"\", \"check_database\"], [0, 3, 1, \"\", \"download_attach\"], [0, 3, 1, \"\", \"download_image\"], [0, 3, 1, \"\", \"download_video\"], [0, 3, 1, \"\", \"download_voice\"], [0, 3, 1, \"\", \"file_to_base64\"], [0, 3, 1, \"\", \"set_proxy\"], [0, 3, 1, \"\", \"set_step\"], [0, 3, 1, \"\", \"silk_base64_to_wav_byte\"], [0, 3, 1, \"\", \"silk_byte_to_byte_wav_byte\"], [0, 3, 1, \"\", \"wav_byte_to_amr_base64\"], [0, 3, 1, \"\", \"wav_byte_to_amr_byte\"], [0, 3, 1, \"\", \"wav_byte_to_silk_base64\"], [0, 3, 1, \"\", \"wav_byte_to_silk_byte\"]],\n        \"WechatAPI.Client.user\": [[0, 1, 1, \"\", \"UserMixin\"]],\n        \"WechatAPI.Client.user.UserMixin\": [[0, 3, 1, \"\", \"get_my_qrcode\"], [0, 3, 1, \"\", \"get_profile\"], [0, 3, 1, \"\", \"is_logged_in\"]]\n    },\n    \"objnames\": {\n        \"0\": [\"py\", \"module\", \"Python \\u6a21\\u5757\"],\n        \"1\": [\"py\", \"class\", \"Python \\u7c7b\"],\n        \"2\": [\"py\", \"attribute\", \"Python \\u5c5e\\u6027\"],\n        \"3\": [\"py\", \"method\", \"Python \\u65b9\\u6cd5\"]\n    },\n    \"objtypes\": {\"0\": \"py:module\", \"1\": \"py:class\", \"2\": \"py:attribute\", \"3\": \"py:method\"},\n    \"terms\": {\n        \"&&\": 0,\n        \"--\": 0,\n        \"10\": 0,\n        \"15\": 0,\n        \"20\": 0,\n        \"30\": 0,\n        \"300\": 0,\n        \"300kb\": 0,\n        \"30s\": 0,\n        \"40\": 0,\n        \"40s\": 0,\n        \"50\": 0,\n        \"__\": 0,\n        \"__call__\": 0,\n        \"__init__\": 0,\n        \"_instanc\": 0,\n        \"_process_message_queu\": 0,\n        \"_queue_messag\": 0,\n        \"_send_text_messag\": 0,\n        \"accept\": 0,\n        \"accept_friend\": 0,\n        \"add\": 0,\n        \"add_chatroom_memb\": 0,\n        \"aes\": 0,\n        \"aeskey\": 0,\n        \"alia\": 0,\n        \"amr\": 0,\n        \"ani\": 0,\n        \"announc\": 0,\n        \"api\": 0,\n        \"app\": 0,\n        \"arg\": 0,\n        \"async\": 0,\n        \"at\": 0,\n        \"attach\": 0,\n        \"attach_id\": 0,\n        \"auto\": 0,\n        \"awaken\": 0,\n        \"awaken_login\": 0,\n        \"banprotect\": 0,\n        \"base\": 0,\n        \"base64\": 0,\n        \"base64_str\": 0,\n        \"base64_to_byt\": 0,\n        \"base64_to_fil\": 0,\n        \"bool\": 0,\n        \"byte\": 0,\n        \"byte_to_base64\": 0,\n        \"cach\": 0,\n        \"call\": 0,\n        \"card\": 0,\n        \"card_alia\": 0,\n        \"card_nicknam\": 0,\n        \"card_wxid\": 0,\n        \"cdn\": 0,\n        \"cdnmidimgurl\": 0,\n        \"chatroom\": 0,\n        \"chatroom_seq\": 0,\n        \"chatroommixin\": 0,\n        \"check\": 0,\n        \"check_databas\": 0,\n        \"check_login_uuid\": 0,\n        \"class\": 0,\n        \"client\": 0,\n        \"client_msg_id\": 0,\n        \"clientimgid\": 0,\n        \"clientmsgid\": 0,\n        \"code\": 0,\n        \"contact\": 0,\n        \"content\": 0,\n        \"contract\": 0,\n        \"count\": 0,\n        \"creat\": 0,\n        \"create_device_id\": 0,\n        \"create_device_nam\": 0,\n        \"create_tim\": 0,\n        \"createtim\": 0,\n        \"data\": 0,\n        \"data_len\": 0,\n        \"databas\": 0,\n        \"databaseerror\": 0,\n        \"default\": 0,\n        \"descript\": 0,\n        \"detail\": 0,\n        \"devic\": 0,\n        \"device_id\": 0,\n        \"device_nam\": 0,\n        \"dict\": 0,\n        \"download\": 0,\n        \"download_attach\": 0,\n        \"download_imag\": 0,\n        \"download_video\": 0,\n        \"download_voic\": 0,\n        \"emoji\": 0,\n        \"emojiitem\": 0,\n        \"encrypt\": 0,\n        \"encrypt_key\": 0,\n        \"encrypt_userinfo\": 0,\n        \"error\": 0,\n        \"error_handl\": 0,\n        \"except\": 0,\n        \"fals\": 0,\n        \"file\": 0,\n        \"file_nam\": 0,\n        \"file_path\": 0,\n        \"file_to_base64\": 0,\n        \"format\": 0,\n        \"friend\": 0,\n        \"friendmixin\": 0,\n        \"func\": 0,\n        \"get\": 0,\n        \"get_auto_heartbeat_status\": 0,\n        \"get_cached_info\": 0,\n        \"get_chatroom_announc\": 0,\n        \"get_chatroom_info\": 0,\n        \"get_chatroom_member_list\": 0,\n        \"get_chatroom_qrcod\": 0,\n        \"get_contact\": 0,\n        \"get_contract_detail\": 0,\n        \"get_contract_list\": 0,\n        \"get_hongbao_detail\": 0,\n        \"get_my_qrcod\": 0,\n        \"get_nicknam\": 0,\n        \"get_profil\": 0,\n        \"get_qr_cod\": 0,\n        \"handler\": 0,\n        \"heartbeat\": 0,\n        \"hongbao\": 0,\n        \"hongbaomixin\": 0,\n        \"id\": 0,\n        \"ignor\": 0,\n        \"ignore_protect\": 0,\n        \"imag\": 0,\n        \"image_base64\": 0,\n        \"image_path\": 0,\n        \"img\": 0,\n        \"in\": 0,\n        \"info\": 0,\n        \"init\": 0,\n        \"instanc\": 0,\n        \"int\": 0,\n        \"invit\": 0,\n        \"invite_chatroom_memb\": 0,\n        \"ip\": 0,\n        \"ip\\u5730\\u5740\": 0,\n        \"is\": 0,\n        \"is_logged_in\": 0,\n        \"is_run\": 0,\n        \"json\": 0,\n        \"json_resp\": 0,\n        \"key\": 0,\n        \"kwarg\": 0,\n        \"len\": 0,\n        \"length\": 0,\n        \"link\": 0,\n        \"list\": 0,\n        \"log\": 0,\n        \"log_out\": 0,\n        \"login\": 0,\n        \"login_device_id\": 0,\n        \"login_stat\": 0,\n        \"login_stat_path\": 0,\n        \"login_tim\": 0,\n        \"loginerror\": 0,\n        \"loginmixin\": 0,\n        \"marshallingerror\": 0,\n        \"md5\": 0,\n        \"member\": 0,\n        \"messag\": 0,\n        \"messagemixin\": 0,\n        \"mmtls\": 0,\n        \"mmtlserror\": 0,\n        \"mp3\": 0,\n        \"msg\": 0,\n        \"msg_id\": 0,\n        \"msgid\": 0,\n        \"my\": 0,\n        \"name\": 0,\n        \"new\": 0,\n        \"new_msg_id\": 0,\n        \"newmsgid\": 0,\n        \"nicknam\": 0,\n        \"none\": 0,\n        \"object\": 0,\n        \"of\": 0,\n        \"option\": 0,\n        \"os\": 0,\n        \"out\": 0,\n        \"packeterror\": 0,\n        \"parsepacketerror\": 0,\n        \"password\": 0,\n        \"path\": 0,\n        \"pathlik\": 0,\n        \"phone\": 0,\n        \"port\": 0,\n        \"pos\": 0,\n        \"print\": 0,\n        \"print_qr\": 0,\n        \"process\": 0,\n        \"profil\": 0,\n        \"protect\": 0,\n        \"proxi\": 0,\n        \"qr\": 0,\n        \"qrcode\": 0,\n        \"queue\": 0,\n        \"resp\": 0,\n        \"revok\": 0,\n        \"revoke_messag\": 0,\n        \"run\": 0,\n        \"scene\": 0,\n        \"second\": 0,\n        \"section\": 0,\n        \"send\": 0,\n        \"send_app_messag\": 0,\n        \"send_card_messag\": 0,\n        \"send_cdn_file_msg\": 0,\n        \"send_cdn_img_msg\": 0,\n        \"send_cdn_video_msg\": 0,\n        \"send_emoji_messag\": 0,\n        \"send_image_messag\": 0,\n        \"send_link_messag\": 0,\n        \"send_text_messag\": 0,\n        \"send_video_messag\": 0,\n        \"send_voice_messag\": 0,\n        \"seq\": 0,\n        \"set\": 0,\n        \"set_proxi\": 0,\n        \"set_step\": 0,\n        \"silk\": 0,\n        \"silk_base64\": 0,\n        \"silk_base64_to_wav_byt\": 0,\n        \"silk_byt\": 0,\n        \"silk_byte_to_byte_wav_byt\": 0,\n        \"singleton\": 0,\n        \"start\": 0,\n        \"start_auto_heartbeat\": 0,\n        \"start_po\": 0,\n        \"stat\": 0,\n        \"static\": 0,\n        \"status\": 0,\n        \"step\": 0,\n        \"stop\": 0,\n        \"stop_auto_heartbeat\": 0,\n        \"str\": 0,\n        \"style\": 0,\n        \"sync\": 0,\n        \"sync_messag\": 0,\n        \"text\": 0,\n        \"thumb\": 0,\n        \"thumb_url\": 0,\n        \"time\": 0,\n        \"titl\": 0,\n        \"to\": 0,\n        \"tool\": 0,\n        \"toolmixin\": 0,\n        \"total\": 0,\n        \"total_length\": 0,\n        \"true\": 0,\n        \"tupl\": 0,\n        \"type\": 0,\n        \"union\": 0,\n        \"unmarshallingerror\": 0,\n        \"updat\": 0,\n        \"update_login_status\": 0,\n        \"url\": 0,\n        \"user\": 0,\n        \"userinfo\": 0,\n        \"userloggedout\": 0,\n        \"usermixin\": 0,\n        \"usernam\": 0,\n        \"uuid\": 0,\n        \"v1\": 0,\n        \"v1key\": 0,\n        \"v2\": 0,\n        \"v2key\": 0,\n        \"valueerror\": 0,\n        \"video\": 0,\n        \"voic\": 0,\n        \"voice_base64\": 0,\n        \"voice_path\": 0,\n        \"voiceurl\": 0,\n        \"wav\": 0,\n        \"wav_byt\": 0,\n        \"wav_byte_to_amr_base64\": 0,\n        \"wav_byte_to_amr_byt\": 0,\n        \"wav_byte_to_silk_base64\": 0,\n        \"wav_byte_to_silk_byt\": 0,\n        \"wechatapi\": 0,\n        \"wechatapiclientbas\": 0,\n        \"wx\": 0,\n        \"wx_seq\": 0,\n        \"wxid\": 0,\n        \"xml\": 0,\n        \"\\u4e00\\u4e2a\": 0,\n        \"\\u4e00\\u6b21\": 0,\n        \"\\u4e00\\u76f4\": 0,\n        \"\\u4e0a\\u4f20\": 0,\n        \"\\u4e0a\\u6b21\": 0,\n        \"\\u4e0a\\u9650\": 0,\n        \"\\u4e0b\\u8f7d\": 0,\n        \"\\u4e0d\\u5230\": 0,\n        \"\\u4e0d\\u662f\": 0,\n        \"\\u4e2a\\u4eba\": 0,\n        \"\\u4e3a\\u7a7a\": 0,\n        \"\\u4e3b\\u52a8\": 0,\n        \"\\u4e8c\\u7ef4\": 0,\n        \"\\u4e8c\\u7ef4\\u7801\": 0,\n        \"\\u4ee3\\u7406\": 0,\n        \"\\u4ee3\\u7406\\u670d\\u52a1\\u5668\": 0,\n        \"\\u4ee3\\u7801\": 0,\n        \"\\u4ee5\\u4e0a\": 0,\n        \"\\u4f4d\\u7f6e\": 0,\n        \"\\u4f7f\\u7528\": 0,\n        \"\\u4fdd\\u5b58\": 0,\n        \"\\u4fe1\\u606f\": 0,\n        \"\\u505c\\u6b62\": 0,\n        \"\\u5143\\u7c7b\": 0,\n        \"\\u5168\\u5c40\": 0,\n        \"\\u516c\\u544a\": 0,\n        \"\\u5173\\u952e\": 0,\n        \"\\u5173\\u952e\\u5b57\": 0,\n        \"\\u5176\\u4ed6\": 0,\n        \"\\u5185\\u5bb9\": 0,\n        \"\\u5185\\u8bf7\": 0,\n        \"\\u5217\\u8868\": 0,\n        \"\\u521b\\u5efa\": 0,\n        \"\\u521d\\u59cb\": 0,\n        \"\\u521d\\u59cb\\u5316\": 0,\n        \"\\u522b\\u540d\": 0,\n        \"\\u522b\\u7528\": 0,\n        \"\\u52a0\\u5bc6\": 0,\n        \"\\u52a0\\u8f7d\": 0,\n        \"\\u52a1\\u5668\": 0,\n        \"\\u5355\\u4e2a\": 0,\n        \"\\u5355\\u4f8b\": 0,\n        \"\\u5355\\u5929\": 0,\n        \"\\u5361\\u7247\": 0,\n        \"\\u538b\\u7f29\": 0,\n        \"\\u53c2\\u6570\": 0,\n        \"\\u53d1\\u51fa\": 0,\n        \"\\u53d1\\u751f\": 0,\n        \"\\u53d1\\u751f\\u53d8\\u5316\": 0,\n        \"\\u53d1\\u9001\": 0,\n        \"\\u53d8\\u5316\": 0,\n        \"\\u53d8\\u91cf\": 0,\n        \"\\u53ea\\u6709\": 0,\n        \"\\u53ef\\u4ee5\": 0,\n        \"\\u53ef\\u538b\": 0,\n        \"\\u53ef\\u538b\\u7f29\": 0,\n        \"\\u540c\\u6b65\": 0,\n        \"\\u540d\\u7247\": 0,\n        \"\\u540d\\u79f0\": 0,\n        \"\\u5426\\u5219\": 0,\n        \"\\u54cd\\u5e94\": 0,\n        \"\\u5524\\u9192\": 0,\n        \"\\u56fe\\u7247\": 0,\n        \"\\u5730\\u5740\": 0,\n        \"\\u57fa\\u7c7b\": 0,\n        \"\\u5904\\u7406\": 0,\n        \"\\u5904\\u7406\\u9519\\u8bef\": 0,\n        \"\\u5907\\u6ce8\": 0,\n        \"\\u591a\\u4e2a\": 0,\n        \"\\u5927\\u4e8e\": 0,\n        \"\\u5931\\u8d25\": 0,\n        \"\\u5982\\u4e0b\": 0,\n        \"\\u5982\\u679c\": 0,\n        \"\\u5b57\\u5178\": 0,\n        \"\\u5b57\\u7b26\": 0,\n        \"\\u5b57\\u7b26\\u4e32\": 0,\n        \"\\u5b57\\u8282\": 0,\n        \"\\u5b58\\u50a8\": 0,\n        \"\\u5b9e\\u4f8b\": 0,\n        \"\\u5b9e\\u9645\": 0,\n        \"\\u5ba2\\u6237\": 0,\n        \"\\u5ba2\\u6237\\u7aef\": 0,\n        \"\\u5bc6\\u7801\": 0,\n        \"\\u5bc6\\u94a5\": 0,\n        \"\\u5bf9\\u5e94\": 0,\n        \"\\u5bf9\\u8c61\": 0,\n        \"\\u5c01\\u9762\": 0,\n        \"\\u5c0f\\u4e8e\": 0,\n        \"\\u5c0f\\u65f6\": 0,\n        \"\\u5e8f\\u5217\": 0,\n        \"\\u5e8f\\u5217\\u5316\": 0,\n        \"\\u5e94\\u7528\": 0,\n        \"\\u5f00\\u59cb\": 0,\n        \"\\u5f02\\u5e38\": 0,\n        \"\\u5f02\\u6b65\": 0,\n        \"\\u5f53\\u524d\": 0,\n        \"\\u5f88\\u6162\": 0,\n        \"\\u5fae\\u4fe1\": 0,\n        \"\\u5fc3\\u8df3\": 0,\n        \"\\u5ffd\\u7565\": 0,\n        \"\\u603b\\u957f\": 0,\n        \"\\u603b\\u957f\\u5ea6\": 0,\n        \"\\u6210\\u529f\": 0,\n        \"\\u6210\\u5458\": 0,\n        \"\\u6216\\u8005\": 0,\n        \"\\u6237\\u540d\": 0,\n        \"\\u6240\\u793a\": 0,\n        \"\\u624b\\u673a\": 0,\n        \"\\u624b\\u673a\\u53f7\": 0,\n        \"\\u6253\\u5370\": 0,\n        \"\\u629b\\u51fa\": 0,\n        \"\\u62a5\\u9519\": 0,\n        \"\\u6302\\u673a\": 0,\n        \"\\u6307\\u5b9a\": 0,\n        \"\\u636e\\u5e93\": 0,\n        \"\\u63a5\\u53d7\": 0,\n        \"\\u63a5\\u6536\": 0,\n        \"\\u63a7\\u5236\": 0,\n        \"\\u63a7\\u5236\\u53f0\": 0,\n        \"\\u63a8\\u8350\": 0,\n        \"\\u63cf\\u8ff0\": 0,\n        \"\\u63d0\\u4f9b\": 0,\n        \"\\u641c\\u7d22\": 0,\n        \"\\u64a4\\u56de\": 0,\n        \"\\u64cd\\u4f5c\": 0,\n        \"\\u652f\\u6301\": 0,\n        \"\\u6536\\u4e0d\\u5230\": 0,\n        \"\\u6536\\u5230\": 0,\n        \"\\u6548\\u679c\": 0,\n        \"\\u6570\\u636e\": 0,\n        \"\\u6570\\u636e\\u5305\": 0,\n        \"\\u6570\\u636e\\u5e93\": 0,\n        \"\\u6587\\u4ef6\": 0,\n        \"\\u6587\\u4ef6\\u540d\": 0,\n        \"\\u6587\\u672c\": 0,\n        \"\\u65b9\\u6cd5\": 0,\n        \"\\u65e0\\u6cd5\": 0,\n        \"\\u65f6\\u95f4\": 0,\n        \"\\u662f\\u5426\": 0,\n        \"\\u6635\\u79f0\": 0,\n        \"\\u66f4\\u65b0\": 0,\n        \"\\u6700\\u540e\": 0,\n        \"\\u6700\\u591a\": 0,\n        \"\\u670d\\u52a1\": 0,\n        \"\\u670d\\u52a1\\u5668\": 0,\n        \"\\u670d\\u52a1\\u5668\\u7aef\": 0,\n        \"\\u673a\\u5236\": 0,\n        \"\\u6765\\u6e90\": 0,\n        \"\\u67e5\\u8be2\": 0,\n        \"\\u6807\\u9898\": 0,\n        \"\\u6837\\u5f0f\": 0,\n        \"\\u6839\\u636e\": 0,\n        \"\\u683c\\u5f0f\": 0,\n        \"\\u68c0\\u67e5\": 0,\n        \"\\u6a21\\u5f0f\": 0,\n        \"\\u6b63\\u5728\": 0,\n        \"\\u6b63\\u5e38\": 0,\n        \"\\u6b65\\u6570\": 0,\n        \"\\u6bcf\\u5929\": 0,\n        \"\\u6ce8\\u518c\": 0,\n        \"\\u6dfb\\u52a0\": 0,\n        \"\\u6e90\\u4ee3\\u7801\": 0,\n        \"\\u72b6\\u6001\": 0,\n        \"\\u751f\\u53d8\": 0,\n        \"\\u751f\\u6210\": 0,\n        \"\\u7528\\u4e8e\": 0,\n        \"\\u7528\\u6237\\u540d\": 0,\n        \"\\u7533\\u8bf7\": 0,\n        \"\\u7535\\u8111\": 0,\n        \"\\u7565\\u56fe\": 0,\n        \"\\u767b\\u51fa\": 0,\n        \"\\u767b\\u9646\": 0,\n        \"\\u786e\\u4fdd\": 0,\n        \"\\u79d2\\u6570\": 0,\n        \"\\u7a7a\\u65f6\": 0,\n        \"\\u7aef\\u53e3\": 0,\n        \"\\u7c7b\\u578b\": 0,\n        \"\\u7ecf\\u5e38\": 0,\n        \"\\u7f13\\u5b58\": 0,\n        \"\\u7f16\\u7801\": 0,\n        \"\\u7f29\\u7565\": 0,\n        \"\\u7f29\\u7565\\u56fe\": 0,\n        \"\\u8054\\u7cfb\": 0,\n        \"\\u8054\\u7cfb\\u4eba\": 0,\n        \"\\u81ea\\u52a8\": 0,\n        \"\\u83b7\\u53d6\": 0,\n        \"\\u8868\\u60c5\": 0,\n        \"\\u89c6\\u9891\": 0,\n        \"\\u89e3\\u6790\": 0,\n        \"\\u89e3\\u7801\": 0,\n        \"\\u8ba4\\u8bc1\": 0,\n        \"\\u8bbe\\u5907\": 0,\n        \"\\u8bbe\\u7f6e\": 0,\n        \"\\u8be6\\u60c5\": 0,\n        \"\\u8bed\\u97f3\": 0,\n        \"\\u8bef\\u7801\": 0,\n        \"\\u8bf7\\u6c42\": 0,\n        \"\\u8c03\\u7528\": 0,\n        \"\\u8d26\\u53f7\": 0,\n        \"\\u8d77\\u59cb\": 0,\n        \"\\u8d85\\u8fc7\": 0,\n        \"\\u8def\\u5f84\": 0,\n        \"\\u8df3\\u8f6c\": 0,\n        \"\\u8f6c\\u53d1\": 0,\n        \"\\u8f6c\\u6362\": 0,\n        \"\\u8f93\\u5165\": 0,\n        \"\\u8fc7\\u671f\": 0,\n        \"\\u8fc7\\u8be5\": 0,\n        \"\\u8fd0\\u884c\": 0,\n        \"\\u8fd1\\u671f\": 0,\n        \"\\u8fd4\\u56de\": 0,\n        \"\\u8fd4\\u56de\\u503c\": 0,\n        \"\\u9000\\u51fa\": 0,\n        \"\\u901a\\u8fc7\": 0,\n        \"\\u901f\\u5ea6\": 0,\n        \"\\u9080\\u8bf7\": 0,\n        \"\\u914d\\u7f6e\": 0,\n        \"\\u94fe\\u63a5\": 0,\n        \"\\u9519\\u8bef\": 0,\n        \"\\u9519\\u8bef\\u7801\": 0,\n        \"\\u957f\\u5ea6\": 0,\n        \"\\u95f4\\u9694\": 0,\n        \"\\u961f\\u5217\": 0,\n        \"\\u9644\\u4ef6\": 0,\n        \"\\u968f\\u673a\": 0,\n        \"\\u9875\\u9762\": 0,\n        \"\\u9891\\u7e41\": 0,\n        \"\\u98ce\\u63a7\": 0,\n        \"\\u9ad8\\u6e05\": 0,\n        \"\\u9ed8\\u8ba4\": 0\n    },\n    \"titles\": [\"WechatAPIClient\"],\n    \"titleterms\": {\n        \"wechatapicli\": 0,\n        \"\\u4fdd\\u62a4\": 0,\n        \"\\u57fa\\u7840\": 0,\n        \"\\u597d\\u53cb\": 0,\n        \"\\u5de5\\u5177\": 0,\n        \"\\u6d88\\u606f\": 0,\n        \"\\u7528\\u6237\": 0,\n        \"\\u767b\\u5f55\": 0,\n        \"\\u7d22\\u5f15\": 0,\n        \"\\u7ea2\\u5305\": 0,\n        \"\\u7fa4\\u804a\": 0\n    }\n})"
  },
  {
    "path": "docs/_coverpage.md",
    "content": "# XYBotV2\n\n> 🤖 功能丰富的微信机器人框架\n\n- AI对话、对接DeepSeek、积分系统、游戏互动、每日新闻、天气查询\n- 非Hook、非微信网页版\n- 支持: Windows ✅ Linux ✅ MacOS ✅\n- 全新架构解决XYBot第一代痛点！\n\n[GitHub](https://github.com/HenryXiaoYang/XYBotV2)\n[开始使用](README.md)"
  },
  {
    "path": "docs/_sidebar.md",
    "content": "* [🏠 首页](/)\n\n* 📖 部署教程\n    * [🪟 Windows部署](/zh_cn/Windows部署.md)\n    * [🐧 Linux部署](/zh_cn/Linux部署.md)\n    * [🐳 Docker部署(推荐)](/zh_cn/Docker部署.md)\n    * [🔧 配置文件](/zh_cn/配置文件.md)\n  * [⚙️ Dify配置](/zh_cn/Dify插件配置.md)\n\n* 📚 开发文档\n    * [🔌 插件开发](/zh_cn/插件开发.md)\n    * <a href=\"WechatAPIClient/index.html\" target=\"_blank\">🔗 WechatAPIClient文档</a>"
  },
  {
    "path": "docs/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>XYBotV2</title>\n    <meta content=\"IE=edge,chrome=1\" http-equiv=\"X-UA-Compatible\"/>\n    <meta content=\"XYBotV2文档\" name=\"description\">\n    <meta content=\"width=device-width, initial-scale=1.0, minimum-scale=1.0\" name=\"viewport\">\n    <link href=\"//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css\" rel=\"stylesheet\">\n</head>\n<body>\n<div id=\"app\"></div>\n<script>\n    window.$docsify = {\n        name: 'XYBotV2',\n        repo: 'HenryXiaoYang/XYBotV2',\n        loadSidebar: true,\n        subMaxLevel: 3,\n        coverpage: true,\n        auto2top: true,\n        search: {\n            maxAge: 86400000,\n            paths: 'auto',\n            placeholder: '搜索',\n            noData: '找不到结果',\n            depth: 6\n        },\n        mergeNavbar: true,\n    }\n</script>\n<!-- Docsify v4 -->\n<script src=\"//cdn.jsdelivr.net/npm/docsify@4\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-python.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify-count@latest/dist/countable.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify/lib/plugins/zoom-image.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify-pangu/lib/pangu.min.js\"></script>\n<script src=\"//cdn.jsdelivr.net/npm/docsify-tabs@1/dist/docsify-tabs.min.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "docs/zh_cn/Dify插件配置.md",
    "content": "# Dify插件配置\n\n用于接入 Dify AI 对话能力的功能模块。\n\n```toml\n# plugins/Dify/config.toml\n[Dify]\nenable = true                           # 是否启用此功能\napi-key = \"\"                           # Dify的API Key，必填\nbase-url = \"https://api.dify.ai/v1\"    # Dify API接口地址\n\n# 支持的指令列表\ncommands = [\"ai\", \"dify\", \"聊天\", \"AI\"]\n\n# 指令提示信息\ncommand-tip = \"\"\"-----XYBot-----\n💬AI聊天指令：\n聊天 请求内容\n\"\"\"\n\n# 其他插件的指令，避免冲突\nother-plugin-cmd = [\"status\", \"bot\", ...]  # 其他插件指令列表\n\nprice = 0              # 每次使用扣除的积分，0表示不扣除\nadmin_ignore = true    # 管理员是否忽略积分扣除\nwhitelist_ignore = true # 白名单用户是否忽略积分扣除\n\n# Http代理设置\n# 格式: http://用户名:密码@代理地址:代理端口\n# 例如：http://127.0.0.1:7890\nhttp-proxy = \"\"\n```\n\n## 配置说明\n\n1. 基础配置\n\n- `enable`: 是否启用Dify插件\n- `api-key`: Dify平台的API密钥，必须填写\n- `base-url`: Dify API的接口地址，默认为 https://api.dify.ai/v1\n\n2. 指令配置\n\n- `commands`: 支持的指令列表，可以添加多个指令\n- `command-tip`: 指令提示信息，可以自定义\n- `other-plugin-cmd`: 其他插件的指令，避免冲突\n\n3. 积分配置\n\n- `price`: 每次使用扣除的积分，0表示不扣除\n- `admin_ignore`: 管理员是否忽略积分扣除\n- `whitelist_ignore`: 白名单用户是否忽略积分扣除\n\n4. 代理配置\n\n- `http-proxy`: Http代理设置(可选)\n\n## 使用示例\n\n1. 基础对话\n   `帮我写一首诗`\n\n2. 群聊中使用\n   `@机器人 今天心情不好`\n\n## 注意事项\n\n- API密钥不要泄露\n- 建议配置代理以提高访问稳定性\n- 合理设置积分规则，避免滥用\n\n## 获取API密钥方法\n\n1. 登录 [Dify](https://dify.ai/) 平台\n2. 创建或选择一个应用\n    - 可导入本项目提供的 [Dify应用模版](https://github.com/HenryXiaoYang/XYBotV2/blob/main/XYBot_Dify_Template.yml)\n    - 可配置使用的AI模型\n3. 在左侧导航栏找到\"访问API\"\n4. 创建新的API密钥\n5. 将获得的密钥填入配置文件的 api-key 字段\n\nDify相关信息：\n\n- Dify官方文档: https://docs.dify.ai/zh-hans\n- CSDN的教程：https://blog.csdn.net/2301_81940605/article/details/143730438\n- 学会使用搜索引擎: https://www.bing.com/search?q=Dify+API+新手教程\n- 学会使用搜索引擎: https://www.google.com/search?q=Dify+API+新手教程\n- 学会使用Github: https://github.com/langgenius/dify\n\n# Dify\n\n## 什么是 Dify？\n\nDify 是一个开源的大语言模型（LLM）应用开发平台，旨在帮助开发者快速构建和部署 AI 应用。它提供了强大的 API\n接口，支持多种模型的接入和使用，适合各种场景的应用开发。\n\n## 如何配置Dify\n\n### 步骤 1：访问 Dify 网站\n\n1. 打开浏览器，访问 [Dify 官方网站](https://dify.ai/zh)。\n2. 点击右上角的\"开始使用\"按钮，输入你的账号信息进行登录。如果没有账号，请先注册。\n\n![Dify开始使用](assets/Dify开始使用.png)\n\n### 步骤 2：创建应用\n\n1. 登录后，在控制面板中选择\"导入DSL文件\"。\n2. 把`https://github.com/HenryXiaoYang/XYBotV2/blob/main/XYBot_Dify_Template.yml`下载下来，导入到Dify中。\n\n![Dify下载模版](assets/Dify下载模版.png)\n\n![Dify导入文件](assets/Dify导入文件.png)\n\n3. 点击\"创建\"按钮。\n\n![Dify创建应用](assets/Dify创建应用.png)\n\n### 步骤 3：获取 API 密钥\n\n1. 在应用创建成功后，会进入应用设置页面。\n2. 找到左侧栏\"访问API\"选项，点击进入。\n\n![Dify访问API](assets/Dify访问API.png)\n\n3. 保存API服务器网址，然后点击右上角\"API 密钥\"\n\n![DifyAPI密钥](assets/DifyAPI密钥.png)\n\n4. 点击\"创建密钥\"\n   ![Dify创建密钥](assets/Dify创建密钥.png)\n\n5. 请妥善保存密钥\n\n![Dify保存密钥](assets/Dify保存密钥.png)\n\n### 步骤 4：配置模型供应商\n\n1. 点击右上角你的用户名，再点击\"设置\"\n\n![Dify个人设置](assets/Dify个人设置.png)\n\n2. 点击\"模型供应商\"\n\n![Dify个人设置模型供应商](assets/Dify个人设置模型供应商.png)\n\n3. 按需求配置模型\n\n![Dify配置模型供应商](assets/Dify配置模型供应商.png)\n\n- 技巧：配置OpenAIAPI格式的模型\n\n![Dify配置OpenAI API模型](assets/DifyOpenAIAPI.png)\n\n如果模型服务不在Dify默认支持服务中，并且模型服务有OpenAI格式的API服务，那么可以使用`OpenAI-API-compatible`让Dify适配OpenAI\nAPI。\n\n### 步骤 5：配置编排\n\n1. 点击左侧栏\"编排\"选项\n\n![Dify编排](assets/Dify编排.png)\n\n2. 有三个LLM节点需要配置\n\n![Dify三个LLM节点](assets/Dify三个LLM节点.png)\n\n3. 点击一个节点，在选择右边栏的模型\n\n![Dify点击节点](assets/Dify点击节点.png)\n\n4. 选择一个合适的模型\n\n![Dify选择模型](assets/Dify选择模型.png)\n\n- 注意，这个节点选择的模型需要支持图片输入\n\n![Dify模型支持图片输入](assets/Dify模型支持图片输入.png)\n\n### 步骤 6: 发布\n\n1. 点击右上角\"发布\"，再点一次\"发布\"\n\n![Dify发布](assets/Dify发布.png)\n\n### 步骤 7: 配置XYBot\n\n1. 回到XYBot项目，在`plugins/Dify/config.toml`文件中写入你保存的API网址和密钥。\n\n```toml\n# plugins/Dify/config.toml\n[Dify]\nenable = true\napi-key = \"\"   # 写入你保存的API密钥\nbase-url = \"https://api.dify.ai/v1\" # 写入你保存的API网址\n\n#...下面略...#\n```\n\n2. 保存文件，重载Dify插件\n\n## 参考链接\n\n- [Dify 官方文档](https://docs.dify.ai/zh-hans)\n- [模型配置指南](https://docs.dify.ai/zh-hans/guides/model-configuration)\n- [工作流指南](https://docs.dify.ai/zh-hans/guides/workflow)\n- [API 开发指南](https://docs.dify.ai/zh-hans/guides/application-publishing/developing-with-apis)"
  },
  {
    "path": "docs/zh_cn/Docker部署.md",
    "content": "# 🐳 Docker 部署\n\n## 1. 🔧 准备环境\n\n需要安装 Docker 和 Docker Compose:\n\n- 🐋 Docker 安装: https://docs.docker.com/get-started/get-docker/\n- 🔄 Docker Compose 安装: https://docs.docker.com/compose/install/\n\n2. ⬇️ 拉取最新镜像\n\n```bash\n# 克隆项目\ngit clone https://github.com/HenryXiaoYang/XYBotV2.git\ncd XYBotV2\n\n# 拉取镜像\ndocker-compose pull\n```\n\n3. 🚀 启动容器\n\n```bash\n# 首次启动\ndocker-compose up -d\n\n# 查看容器状态\ndocker-compose ps\n```\n\n4. 📱 查看日志然后登录微信\n\n```bash\n# 查看日志获取登录二维码\ndocker-compose logs -f xybotv2\n```\n\n扫描终端显示的二维码完成登录。（如果扫不出来,可以打开链接扫码）。首次登录成功后,需要挂机4小时。之后机器人就会自动开始正常运行。\n\n5. ⚙️ 配置文件修改\n\n```bash\n# 查看数据卷位置\ndocker volume inspect xybotv2\n\n# 编辑对应目录下的配置文件\nxybotv2-volumes-dir/_data/main_config.toml\nxybotv2-volumes-dir/_data/plugins/all_in_one_config.toml\n```\n\n修改配置后需要重启容器使配置生效:\n\n```bash\ndocker-compose restart xybotv2\n```\n\n> 如果是修改插件配置则可使用热加载、热卸载、热重载指令，不用重启机器人。\n\n6. 🔄 访问Web界面\n\nWeb界面可通过以下地址访问：\n\n```\nhttp://服务器IP地址:9999\n```\n\nWeb界面可通过以下地址访问：\n\n```\nhttp://服务器IP地址:9999\n```\n\n## ❓ 常见问题\n\n1. 🔌 Redis 连接失败\n\n- 检查 Redis 服务是否正常运行\n- 确认 main_config.toml 中的 redis-host 配置是否正确\n\n2. ⚠️ 配置文件修改未生效\n\n- 重启容器: `docker-compose restart xybotv2`\n- 检查配置文件权限是否正确\n\n3. 📝 日志查看\n\n```bash\n# 查看实时日志\ndocker-compose logs -f xybotv2\n\n# 查看最近100行日志\ndocker-compose logs --tail=100 xybotv2\n```\n\n4. 与网络相关的报错\n\n- 检查网络连接，是否能ping通微信服务器\n- 尝试关闭代理软件，尝试重启电脑\n- 尝试重启XYBot和Redis\n- 如是Docker部署，检查Docker容器网络是否能连接到微信服务器和 Redis 数据库\n\n5. `正在运行`相关的报错\n\n- 将占用9000端口的进程强制结束\n\n6. 🌐 无法访问Web界面\n\n- 确保9999端口已在防火墙中开放\n- 检查docker-compose.yml中的端口映射配置\n"
  },
  {
    "path": "docs/zh_cn/Linux部署.md",
    "content": "#### 1. 🔧 环境准备\n\n```bash\n# Ubuntu/Debian\nsudo apt update\nsudo apt install python3.11 python3.11-venv redis-server ffmpeg\n\n# CentOS/RHEL\nsudo yum install epel-release  # 如果需要EPEL仓库\nsudo yum install python3.11 redis ffmpeg\nsudo systemctl start redis\nsudo systemctl enable redis\n\n# 设置 IMAGEIO_FFMPEG_EXE 环境变量\necho 'export IMAGEIO_FFMPEG_EXE=$(which ffmpeg)' >> ~/.bashrc\nsource ~/.bashrc\n\n# 如果使用其他shell(如zsh)，则需要：\n# echo 'export IMAGEIO_FFMPEG_EXE=$(which ffmpeg)' >> ~/.zshrc\n# source ~/.zshrc\n```\n\n#### 2. ⬇️ 下载项目\n\n```bash\n# 克隆项目\ngit clone https://github.com/HenryXiaoYang/XYBotV2.git\n# 小白：直接 Github Download ZIP\n\ncd XYBotV2\n\n# 创建虚拟环境\npython3.11 -m venv venv\nsource venv/bin/activate\n\n# 安装依赖\npip install -r requirements.txt\n\n# 安装gunicorn和eventlet\npip install gunicorn eventlet\n\n# 使用镜像源安装\npip install -r requirements.txt -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple\n```\n\n4. 🚀 启动机器人\n\n```bash\n# 确保在虚拟环境中\nsource venv/bin/activate\n\n# 检查Redis服务状态\nsystemctl status redis\n\n# 如果Redis未运行，启动服务\nsudo systemctl start redis\n\n# 设置Redis开机自启\nsudo systemctl enable redis\n\n# 验证Redis连接\nredis-cli ping\n# 如果返回PONG表示连接正常\n\n# 启动机器人WebUI\npython app.py\n```\n\n5. 进入WebUI\n\n访问 `9999` 端口。\n\n默认用户名是`admin`，密码是`admin123`\n\n6. 点击`启动`，账号信息出会出现一个二维码，微信扫码即可。\n\n\n7. 💻 不需要WebUI的简单启动方式\n\n如果你不需要WebUI界面，可以直接使用bot.py来运行机器人：\n\n```bash\n# 确保在虚拟环境中\nsource venv/bin/activate\n\n# 直接运行bot.py\npython bot.py\n```\n\n这种方式不会启动Web界面，机器人核心功能依然正常工作。使用这种方式时：\n- 二维码会直接显示在终端中\n- 所有机器人功能正常可用\n\n\n## ❓ 常见问题\n\n1. 与网络相关的报错\n\n- 检查网络连接，是否能ping通微信服务器\n- 尝试关闭代理软件，尝试重启电脑\n- 尝试重启XYBot和Redis\n- 如是Docker部署，检查Docker容器网络是否能连接到微信服务器和 Redis 数据库\n\n2. `正在运行`相关的报错\n\n- 将占用9000端口的进程强制结束\n\n3. 🌐 无法访问Web界面\n\n- 确保9999端口已在防火墙中开放\n```bash\n# Ubuntu/Debian\nsudo ufw allow 9999\n\n# CentOS\nsudo firewall-cmd --permanent --add-port=9999/tcp\nsudo firewall-cmd --reload\n```\n"
  },
  {
    "path": "docs/zh_cn/Windows部署.md",
    "content": "# 🪟 Windows 部署\n\n## 1. 🔧 环境准备\n\n- 安装 Python 3.11 (必须是3.11版本): https://www.python.org/downloads/release/python-3119/\n    - 在安装过程中勾选 \"Add Python to PATH\" 选项\n    - 或者手动添加：\n        1. 右键点击 \"此电脑\" -> \"属性\" -> \"高级系统设置\" -> \"环境变量\"\n        2. 在 \"系统变量\" 中找到 Path,点击 \"编辑\"\n        3. 添加 Python 安装目录（如 `C:\\Python311`）和 Scripts 目录（如 `C:\\Python311\\Scripts`）\n\n- 安装 ffmpeg:\n    1. 从 [ffmpeg官网](https://www.ffmpeg.org/download.html) 下载 Windows 版本\n    2. 解压到合适的目录（如 `C:\\ffmpeg`）\n    3. 添加环境变量：\n        - 右键点击 \"此电脑\" -> \"属性\" -> \"高级系统设置\" -> \"环境变量\"\n        - 在 \"系统变量\" 中找到 Path，点击 \"编辑\"\n        - 添加 ffmpeg 的 bin 目录路径（如 `C:\\ffmpeg\\bin`）\n    4. 设置 IMAGEIO_FFMPEG_EXE 环境变量：\n        - 在 \"系统变量\" 中点击 \"新建\"\n        - 变量名输入：`IMAGEIO_FFMPEG_EXE`\n        - 变量值输入 ffmpeg.exe 的完整路径（如 `C:\\ffmpeg\\bin\\ffmpeg.exe`）\n    5. 重启命令提示符或 PowerShell 使环境变量生效\n    6. 验证安装：\n        ```bash\n        ffmpeg -version\n        ```\n\n- 安装 Redis:\n    - 从 [Redis](https://github.com/tporadowski/redis/releases/tag/v5.0.14.1) 下载最新版本 (目前是7.4.2)\n    - 下载并解压 `Redis-x64-5.0.14.1.zip`\n    - 在命令行执行:\n      ```bash\n      # 进入目录\n      cd Redis-x64-5.0.14.1\n      \n      # 启动Redis服务\n      start redis-server.exe\n      ```\n\n## 2. ⬇️ 下载项目\n\n```bash\n# 克隆项目\ngit clone https://github.com/HenryXiaoYang/XYBotV2.git\n# 小白：直接 Github Download ZIP\n\ncd XYBotV2\n\n# 创建虚拟环境\npython -m venv venv\n.\\venv\\Scripts\\activate\n\n# 安装依赖\npip install -r requirements.txt\n\n# 安装gunicorn和eventlet\npip install gunicorn eventlet\n\n# 使用镜像源安装\npip install -r requirements.txt -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple\n```\n\n## 3. 🚀 启动机器人\n\n```bash\n# 确保Redis服务已启动\nredis-cli ping  # 如果返回PONG则表示Redis正常运行\n\n# 启动WebUI\npython app.py\n```\n\n5. 进入WebUI\n\n访问 `9999` 端口。\n\n默认用户名是`admin`，密码是`admin123`\n\n6. 点击`启动`，账号信息出会出现一个二维码，微信扫码即可。\n\n\n7. 💻 不需要WebUI的简单启动方式\n\n如果你不需要WebUI界面，可以直接使用bot.py来运行机器人：\n\n```bash\n# 直接运行bot.py\npython bot.py\n```\n\n这种方式不会启动Web界面，机器人核心功能依然正常工作。使用这种方式时：\n- 二维码会直接显示在终端中\n- 所有机器人功能正常可用\n\n## ❓ 常见问题\n\n1. 与网络相关的报错\n\n- 检查网络连接，是否能ping通微信服务器\n- 尝试关闭代理软件，尝试重启电脑\n- 尝试重启XYBot和Redis\n- 如是Docker部署，检查Docker容器网络是否能连接到微信服务器和 Redis 数据库\n\n2. `正在运行`相关的报错\n\n- 将占用9000端口的进程强制结束\n\n3. 🌐 无法访问Web界面\n\n- 确保Windows防火墙允许9999端口通信\n  1. 打开\"控制面板\" -> \"Windows Defender防火墙\" -> \"高级设置\"\n  2. 选择\"入站规则\" -> \"新建规则\"\n  3. 规则类型选\"端口\" -> 指定TCP和9999端口 -> 允许连接 -> 给规则起名并完成\n"
  },
  {
    "path": "docs/zh_cn/插件开发.md",
    "content": "# 🧩 XYBotV2 插件开发指南\n\n## 插件\n\n插件是XYBotV2的扩展，用于实现各种功能。\n\n所有插件都在`plugins`文件夹内。每一个文件夹都是一个插件。一个插件都要包含`main.py`文件，作为插件入口。\n\n而`main.py`里会有继承`PluginBase`的类，用来识别插件，定义插件所需要的方法、装饰器、等等。\n\n插件文件夹里的`__init__.py`文件不是必须的，也可以为空文件。插件当作一个Python包被导入时，`__init__.py` 中的代码会自动执行。\n\n如果要新加插件，可直接把插件文件放入`plugins`文件夹内。\n\n如果你不要什么功能，直接把`plugins`文件夹中对应的文件夹删掉。（没错就是这么简单）\n\n## 示例插件和插件模版\n\n示例插件位于`plugins/ExamplePlugin`。\n\n插件模版在 [XYBotV2PluginTemplate](https://github.com/HenryXiaoYang/XYBotV2PluginTemplate) Github仓库\n\n## 编写插件\n\n每个插件的`main.py`都需要继承`PluginBase`基类,并定义基本信息。以下是一个基本的插件示例:\n\n```python\n# main.py\nfrom utils.plugin_base import PluginBase\n\n\nclass ExamplePlugin(PluginBase):\n    description = \"示例插件\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n```\n\n这是创建插件的最基本结构:\n\n1. 继承 `PluginBase` 类以获取基本功能\n2. 设置插件的描述信息、作者和版本号\n3. 之后可以在这个类中添加各种事件处理函数\n\n```python\n    # ... 接上面 ... #\n# 同步初始化\ndef __init__(self):\n   super().__init__()\n   # 在这里初始化插件需要的变量\n   self.data = {}\n\n\n# 异步初始化\nasync def async_init(self):\n   # 在这里执行需要的异步初始化操作\n   pass\n```\n\n- `__init__`: 初始化函数,用于设置插件的初始状态\n- `async_init`: 异步初始化函数，用于执行插件的异步初始化操作\n\n```python\n    # ... 接上面 ... #\n@on_text_message  # 处理文本消息\nasync def on_text(self, bot: WechatAPIClient, message: dict):\n   pass\n```\n\n- `@on_text_message`: 处理文本消息的事件，所装饰的函数必须是异步函数。\n\n## 事件类型列表\n\n| 事件类型 | 装饰器                  | 触发条件      |\n|------|----------------------|-----------|\n| 文本消息 | `@on_text_message`   | 收到任意文本消息  |\n| 图片消息 | `@on_image_message`  | 收到图片消息    |\n| 语音消息 | `@on_voice_message`  | 收到语音消息    |\n| 文件消息 | `@on_file_message`   | 收到文件消息    |\n| 引用消息 | `@on_quote_message`  | 收到引用回复消息  |\n| @消息  | `@on_at_message`     | 收到包含@的群消息 |\n| 视频消息 | `@on_video_message`  | 收到视频消息    |\n| 好友请求 | `@on_friend_request` | 收到新的好友请求  |\n| 拍一拍  | `@on_pat_message`    | 收到拍一拍消息   |\n\n## 消息事件处理函数\n\n### 优先级机制\n\n优先级机制用于控制多个插件处理同一事件时的执行顺序。\n\n#### 优先级范围\n\n- 优先级数值范围为0-99\n- 数值越大,优先级越高\n- 默认优先级为50\n\n#### 设置方式\n\n在事件装饰器中通过priority参数设置:\n\n```python\n    # ... 接上面 ... #\n@on_text_message(priority=80)  # 设置较高优先级\nasync def handle_important(self, bot, message):\n   # 优先处理重要消息\n   pass\n\n\n@on_text_message(priority=20)  # 设置较低优先级\nasync def handle_normal(self, bot, message):\n   # 后处理普通消息\n   pass\n```\n\n#### 执行顺序\n\n1. 按优先级从高到低依次执行\n2. 同优先级的处理函数按注册顺序执行\n\n### 阻塞机制\n\n阻塞机制用于控制是否继续执行后续的事件处理函数。\n\n#### 基本概念\n\n- 通过插件函数返回值来控制是否继续执行后续处理\n- 返回`True`表示继续执行后续处理\n- 返回`False`表示处理完成并阻止后续执行\n\n#### 设置方式\n\n```python\n    @on_text_message\nasync def handle_sensitive(self, bot, message):\n   if \"敏感词\" in message[\"Content\"]:\n      await bot.send_text(message[\"FromWxid\"], \"检测到敏感内容\")\n      return False  # 返回False表示处理完成并阻止后续执行\n   return True  # 返回False表示继续执行后续处理\n\n\n@on_text_message\nasync def handle_normal(self, bot, message):\n   # 普通消息处理\n   pass\n   # 没有返回，默认继续执行\n```\n\n#### 注意事项\n\n1. 合理使用阻塞机制,避免不必要的阻塞\n2. 高优先级的阻塞会影响所有低优先级的处理函数\n\n### 风控保护机制\n\n风控保护机制用于保护机器人账号安全,防止触发微信的安全检测。本机器人的风控保护非常轻量，*不保证*机器人完全不会被风控。\n\n1. **新设备登录限制**\n   - 新设备登录后4小时内不可处理消息，不可调用函数，不可发送消息。只维持自动心跳和接受消息。\n\n2. **消息发送频率**\n   - 消息发送内置了队列，每秒只发一条消息。\n\n### 异步处理\n\nXYBot整个项目使用asyncio进行异步处理（个别地方除外）,所有插件函数必须是异步函数。\n\n使用阻塞函数会导致主进程阻塞，无法接受消息，无法发送消息，无法调用函数。\n\n如果需要使用阻塞函数，请使用`asyncio.run_in_executor`将其转换为异步函数。\n\n### 资源管理\n\nXYBot使用`loguru`进行日志管理，所有日志都会输出到`logs/xybot.log`文件中。\n\n请将插件会使用到的静态资源存放到`resources`文件夹中。\n\n请将临时会产生的文件存放到`resources/cache`文件夹中。\n\n## 消息对象结构\n\n### 文本消息示例\n\n```python\n{\n   \"MsgId\": 123456789,  # 消息唯一标识（可用于撤回消息）\n   \"ToWxid\": \"wxid_00000000000000\",  # 接收者微信ID（通常是机器人自身ID）\n   \"MsgType\": 1,  # 消息类型（1表示文本消息）\n   \"Content\": \"@机器人\\u2005\",  # 消息内容\n   \"Status\": 3,  # 消息状态码（3表示正常消息）\n   \"ImgStatus\": 1,  # 图片状态\n   \"ImgBuf\": {  # 图片缓冲区（通常为空）\n      \"iLen\": 0\n   },\n   \"CreateTime\": 1739878408,  # 消息创建时间戳（秒级）\n   \"MsgSource\": \"<msgsource>...</msgsource>\",  # 消息源数据（XML格式，包含@列表等信息）\n   \"PushContent\": \"机器人在群聊中@了你。\",  # 系统推送提示内容\n   \"NewMsgId\": 1145141919810,  # 消息ID（可用于撤回消息）\n   \"MsgSeq\": 114514,  # 消息序列（可用于撤回消息）\n   \"FromWxid\": \"123456789@chatroom\",  # 消息来源ID（可以是个人wxid或群聊ID）\n   \"IsGroup\": True,  # 是否来自群聊\n   \"SenderWxid\": \"wxid_11111111111111\",  # 实际发送人微信ID\n   \"Ats\": [\"wxid_00000000000000\"]  # 被@的用户列表\n}\n```\n\n### 被@消息示例\n\n```python\n{\n   \"MsgId\": 123456789,  # 消息唯一标识（可用于撤回消息）\n   \"ToWxid\": \"wxid_00000000000000\",  # 接收者微信ID（通常是机器人自身ID）\n   \"MsgType\": 1,  # 消息类型（1表示文本消息）\n   \"Content\": \"@机器人\\u2005\",  # 消息内容（\\u2005是特殊空格符）\n   \"Status\": 3,  # 消息状态码（3表示正常消息）\n   \"ImgStatus\": 1,  # 图片状态\n   \"ImgBuf\": {  # 图片缓冲区（通常为空）\n      \"iLen\": 0\n   },\n   \"CreateTime\": 1739878408,  # 消息创建时间戳（秒级）\n   \"MsgSource\": \"<msgsource>...</msgsource>\",  # 消息源数据（XML格式，包含@列表等信息）\n   \"PushContent\": \"机器人在群聊中@了你。\",  # 系统推送提示内容\n   \"NewMsgId\": 1145141919810,  # 新消息ID（可用于撤回消息）\n   \"MsgSeq\": 114514,  # 消息序列号（可用于撤回消息）\n   \"FromWxid\": \"123456789@chatroom\",  # 消息来源ID（可以是个人wxid或群聊ID）\n   \"IsGroup\": True,  # 是否群聊消息\n   \"SenderWxid\": \"wxid_11111111111111\",  # 实际发送人微信ID\n   \"Ats\": [\"wxid_00000000000000\"]  # 被@的用户列表（包含机器人自身ID）\n}\n```\n\n### 图片消息示例\n\n```python\n{\n   \"MsgId\": 123456789,  # 消息唯一标识（可用于撤回消息）\n   \"ToWxid\": \"wxid_00000000000000\",  # 接收者微信ID（通常是机器人自身ID）\n   \"MsgType\": 3,  # 消息类型（3表示图片消息）\n   \"Content\": \"/9j/4AAQSkZJ...base64content\",  # 图片的Base64编码内容\n   \"Status\": 3,  # 消息状态码（3表示正常消息）\n   \"ImgStatus\": 2,  # 图片状态（2表示图片已下载）\n   \"ImgBuf\": {  # 图片缓冲区（通常为空）\n      \"iLen\": 0\n   },\n   \"CreateTime\": 1739879142,  # 消息创建时间戳（秒级）\n   \"MsgSource\": \"<msgsource>...</msgsource>\",  # 消息源数据（XML格式，包含图片相关信息）\n   \"PushContent\": \"XYBot : [图片]\",  # 系统推送提示内容\n   \"NewMsgId\": 1145141919810,  # 新消息ID（可用于撤回消息）\n   \"MsgSeq\": 114514,  # 消息序列号（可用于撤回消息）\n   \"FromWxid\": \"wxid_11111111111111\",  # 消息发送者的微信ID\n   \"SenderWxid\": \"wxid_11111111111111\",  # 实际发送人微信ID（私聊时与FromWxid相同）\n   \"IsGroup\": False  # 是否群聊消息（这里是私聊）\n}\n```\n\n### 语音消息示例\n\n```python\n{\n   \"MsgId\": 123456789,  # 消息唯一标识（可用于撤回消息）\n   \"ToWxid\": \"wxid_00000000000000\",  # 接收者微信ID（通常是机器人自身ID）\n   \"MsgType\": 34,  # 消息类型（34表示语音消息）\n   \"Content\": b\"VoiceData...\",  # 语音数据内容（bytes类型）\n   \"Status\": 3,  # 消息状态码（3表示正常消息）\n   \"ImgStatus\": 1,  # 语音状态\n   \"ImgBuf\": {  # 语音数据缓冲区\n      \"iLen\": 1024,\n      \"buffer\": \"AudioData...\"  # 语音数据内容（base64），群聊中的语音消息这个字段为空\n   },\n   \"CreateTime\": 1739879399,  # 消息创建时间戳（秒级）\n   \"MsgSource\": \"<msgsource>...</msgsource>\",  # 消息源数据（XML格式）\n   \"PushContent\": \"XYBot : [语音]\",  # 系统推送提示内容\n   \"NewMsgId\": 1145141919810,  # 新消息ID（可用于撤回消息）\n   \"MsgSeq\": 114514,  # 消息序列号（可用于撤回消息）\n   \"FromWxid\": \"wxid_11111111111111\",  # 消息发送者的微信ID\n   \"SenderWxid\": \"wxid_11111111111111\",  # 实际发送人微信ID（私聊时与FromWxid相同）\n   \"IsGroup\": False  # 是否群聊消息（这里是私聊）\n}\n```\n\n### 文件消息示例\n\n```python\n{\n   \"MsgId\": 123456789,  # 消息唯一标识（可用于撤回消息）\n   \"ToWxid\": \"wxid_00000000000000\",  # 接收者微信ID（通常是机器人自身ID）\n   \"MsgType\": 49,  # 消息类型（49表示XML消息）\n   \"Content\": \"<?xml version=\\\"1.0\\\"?><msg><appmsg>...</appmsg></msg>\",  # 文件信息的XML内容\n   \"Status\": 3,  # 消息状态码（3表示正常消息）\n   \"ImgStatus\": 1,  # 文件状态\n   \"ImgBuf\": {  # 文件数据缓冲区（通常为空）\n      \"iLen\": 0\n   },\n   \"CreateTime\": 1739879893,  # 消息创建时间戳（秒级）\n   \"MsgSource\": \"<msgsource>...</msgsource>\",  # 消息源数据（XML格式）\n   \"PushContent\": \"XYBot : [文件]example.txt\",  # 系统推送提示内容\n   \"NewMsgId\": 1145141919810,  # 新消息ID（可用于撤回消息）\n   \"MsgSeq\": 114514,  # 消息序列号（可用于撤回消息）\n   \"FromWxid\": \"wxid_11111111111111\",  # 消息发送者的微信ID\n   \"SenderWxid\": \"wxid_11111111111111\",  # 实际发送人微信ID（私聊时与FromWxid相同）\n   \"IsGroup\": False,  # 是否群聊消息（这里是私聊）\n   \"Filename\": \"example.txt\",  # 文件名\n   \"FileExtend\": \"txt\",  # 文件扩展名\n   \"File\": \"FileData...\"  # 文件数据内容（base64编码）\n}\n```\n\n### 引用消息示例\n\n这个装饰器还不是很完善。\n\n```python\n{\n   \"MsgId\": 123456789,  # 消息唯一标识（可用于撤回消息）\n   \"ToWxid\": \"wxid_00000000000000\",  # 接收者微信ID（通常是机器人自身ID）\n   \"MsgType\": 49,  # 消息类型（49表示引用消息）\n   \"Content\": \"回复的文本内容\",  # 回复消息的文本内容\n   \"Status\": 3,  # 消息状态码（3表示正常消息）\n   \"ImgStatus\": 1,  # 消息状态\n   \"ImgBuf\": {  # 数据缓冲区（通常为空）\n      \"iLen\": 0\n   },\n   \"CreateTime\": 1739880113,  # 消息创建时间戳（秒级）\n   \"MsgSource\": \"<msgsource>...</msgsource>\",  # 消息源数据（XML格式）\n   \"PushContent\": \"回复的文本内容\",  # 系统推送提示内容\n   \"NewMsgId\": 1145141919810,  # 新消息ID（可用于撤回消息）\n   \"MsgSeq\": 114514,  # 消息序列号（可用于撤回消息）\n   \"FromWxid\": \"wxid_11111111111111\",  # 消息发送者的微信ID\n   \"SenderWxid\": \"wxid_11111111111111\",  # 实际发送人微信ID\n   \"IsGroup\": False,  # 是否群聊消息\n   \"Quote\": {  # 被引用的原始消息信息\n      \"MsgType\": 1,  # 原始消息类型（1表示文本消息）\n      \"NewMsgId\": \"1145141919811\",  # 原始消息的ID\n      \"ToWxid\": \"wxid_00000000000000\",  # 原始消息接收者ID\n      \"FromWxid\": \"wxid_11111111111111\",  # 原始消息发送者ID\n      \"Nickname\": \"XYBot\",  # 原始消息发送者昵称\n      \"MsgSource\": \"<msgsource>...</msgsource>\",  # 原始消息源数据\n      \"Content\": \"引用的消息内容\",  # 引用的消息内容\n      \"Createtime\": \"1739879158\"  # 原始消息创建时间\n   }\n}\n```\n\n### 视频消息示例\n\n```python\n{\n   \"MsgId\": 123456789,  # 消息唯一标识（可用于撤回消息）\n   \"ToWxid\": \"wxid_00000000000000\",  # 接收者微信ID（通常是机器人自身ID）\n   \"MsgType\": 43,  # 消息类型（43表示视频消息）\n   \"Content\": \"<?xml version=\\\"1.0\\\"?>\\n<msg>\\n\\t<videomsg aeskey=\\\"...\\\" cdnvideourl=\\\"...\\\" length=\\\"207801\\\" playlength=\\\"5\\\" /></msg>\",\n   # 视频信息的XML内容\n   \"Status\": 3,  # 消息状态码（3表示正常消息）\n   \"ImgStatus\": 1,  # 视频状态\n   \"ImgBuf\": {  # 视频数据缓冲区（通常为空）\n      \"iLen\": 0\n   },\n   \"CreateTime\": 1739880402,  # 消息创建时间戳（秒级）\n   \"MsgSource\": \"<msgsource>...</msgsource>\",  # 消息源数据（XML格式）\n   \"NewMsgId\": 1145141919810,  # 新消息ID（可用于撤回消息）\n   \"MsgSeq\": 114514,  # 消息序列号（可用于撤回消息）\n   \"FromWxid\": \"wxid_11111111111111\",  # 消息发送者的微信ID\n   \"SenderWxid\": \"wxid_11111111111111\",  # 实际发送人微信ID\n   \"IsGroup\": False,  # 是否群聊消息（这里是私聊）\n   \"Video\": \"VideoData...\"  # 视频数据内容（base64编码）\n}\n```\n\n### 好友请求消息示例\n\n之后写\n\n### 拍一拍消息示例\n\n```python\n{\n   \"MsgId\": 123456789,  # 消息唯一标识（可用于撤回消息）\n   \"ToWxid\": \"wxid_00000000000000\",  # 接收者微信ID（通常是机器人自身ID）\n   \"MsgType\": 10002,  # 消息类型（10002表示拍一拍消息）\n   \"Content\": \"\\n<sysmsg type=\\\"pat\\\">\\n<pat>\\n  <fromusername>wxid_11111111111111</fromusername>\\n  <chatusername>11111111111@chatroom</chatusername>\\n  <pattedusername>wxid_00000000000000</pattedusername>\\n  <patsuffix><![CDATA[拍了拍我]]></patsuffix>\\n  <template><![CDATA[\\\"${wxid_11111111111111}\\\" 拍了拍我]]></template>\\n</pat>\\n</sysmsg>\",\n   # 拍一拍消息的XML内容\n   \"Status\": 4,  # 消息状态码\n   \"ImgStatus\": 1,  # 消息状态\n   \"ImgBuf\": {  # 数据缓冲区（通常为空）\n      \"iLen\": 0\n   },\n   \"CreateTime\": 1739880846,  # 消息创建时间戳（秒级）\n   \"MsgSource\": \"<msgsource>...</msgsource>\",  # 消息源数据（XML格式）\n   \"NewMsgId\": 1145141919810,  # 新消息ID（可用于撤回消息）\n   \"MsgSeq\": 114514,  # 消息序列号（可用于撤回消息）\n   \"FromWxid\": \"11111111111@chatroom\",  # 消息来源群聊ID\n   \"IsGroup\": True,  # 是否群聊消息（这里是群聊）\n   \"SenderWxid\": \"11111111111@chatroom\",  # 群聊ID\n   \"Patter\": \"wxid_11111111111111\",  # 发起拍一拍的用户ID\n   \"Patted\": \"wxid_00000000000000\",  # 被拍的用户ID\n   \"PatSuffix\": \"拍了拍我\"  # 拍一拍的后缀文本\n}\n```\n\n## 定时任务装饰器\n\n`@schedule`装饰器用于创建定时执行的任务,支持多种触发方式。\n\n### 基本用法\n\n```python\nfrom utils.decorators import schedule\n\n\nclass TimerPlugin(PluginBase):\n   @schedule('interval', seconds=5)\n   async def periodic_task(self, bot):\n      # 每5秒执行一次\n      await bot.send_text(\"example\", \"定时消息\")\n\n   @schedule('cron', hour=8, minute=30)\n   async def daily_task(self, bot):\n      # 每天早上8:30执行\n      await bot.send_text(\"example\", \"早安\")\n\n   @schedule('date', run_date='2024-12-31 23:59:59')\n   async def one_time_task(self, bot):\n      # 在指定时间执行一次\n      await bot.send_text(\"example\", \"新年快乐\")\n```\n\n### 触发器类型\n\n1. **interval - 间隔触发**\n   对于参数请查看[apscheduler.triggers.interval](https://apscheduler.readthedocs.io/en/3.x/modules/triggers/interval.html)\n   ```python\n   @schedule('interval', seconds=30)\n   async def task(self, bot):\n      # 每1小时30分钟执行一次\n      pass\n   ```\n\n2. **cron - 定时触发**\n   参数请查看[apscheduler.triggers.cron](https://apscheduler.readthedocs.io/en/3.x/modules/triggers/cron.html)\n   ```python\n    @schedule('cron', day_of_week='mon-fri', hour='9-17')\n    async def work_time_task(self, bot):\n        # 工作日9点到17点每小时执行\n        pass\n   ```\n\n3. **date - 指定日期触发**\n   参数请查看[apscheduler.triggers.date](https://apscheduler.readthedocs.io/en/3.x/modules/triggers/date.html)\n   ```python\n    @schedule('date', run_date='2024-01-01 00:00:00')\n    async def new_year_task(self, bot):\n        # 在2024年新年时执行一次\n        pass\n   ```\n\n### 高级用法\n\n1. **组合使用多个定时任务**\n\n```python\nclass ComplexTimer(PluginBase):\n   @schedule('cron', hour=8)\n   async def morning(self, bot):\n      await bot.send_text(\"example\", \"早安\")\n\n   @schedule('cron', hour=23)\n   async def night(self, bot):\n      await bot.send_text(\"example\", \"晚安\")\n\n   @schedule('interval', minutes=30)\n   async def check_status(self, bot):\n      # 每30分钟检查一次状态\n      pass\n```\n\n## WechatAPIClient 机器人的函数\n\n在事件函数中，可以调用`bot`对象的函数。\n\n可在 [API文档](WechatAPIClient/index.html) 获取详细接口说明。\n\n"
  },
  {
    "path": "docs/zh_cn/配置文件.md",
    "content": "# XYBotV2 配置文件\n\nXYBotV2 有两个主要配置文件需要修改：\n\n- `main_config.toml`：主配置文件\n- `plugins/all_in_one_config.toml`：插件配置文件\n\n## 不同系统下的配置文件修改方法\n\n### Windows 系统\n\n1. 直接使用记事本或其他文本编辑器（推荐使用 VSCode、Sublime Text 等）打开配置文件：\n\n```bash\n# 主配置文件位置\nXYBotV2/main_config.toml\n\n# 插件配置文件位置\nXYBotV2/plugins/all_in_one_config.toml\n```\n\n### Linux 系统\n\n1. 使用命令行文本编辑器（如 vim、nano）编辑：\n\n```bash\n# 使用 vim 编辑\nvim main_config.toml\nvim plugins/all_in_one_config.toml\n\n# 或使用 nano 编辑\nnano main_config.toml\nnano plugins/all_in_one_config.toml\n```\n\n2. 也可以使用图形界面编辑器（如果是桌面环境）：\n\n```bash\n# 使用 gedit（GNOME）\ngedit main_config.toml\ngedit plugins/all_in_one_config.toml\n\n# 使用 kate（KDE）\nkate main_config.toml\nkate plugins/all_in_one_config.toml\n```\n\n### Docker 环境\n\n1. 首先找到数据卷位置：\n\n```bash\n# 查看数据卷位置\ndocker volume inspect xybotv2\n```\n\n2. 进入数据卷目录编辑配置文件：\n\n```bash\n# 配置文件通常位于：\nxybotv2-volumes-dir/_data/main_config.toml\nxybotv2-volumes-dir/_data/plugins/all_in_one_config.toml\n```\n\n3. 修改后重启容器使配置生效：\n\n```bash\ndocker-compose restart xybotv2\n```\n\n## 配置文件修改后生效方式\n\n1. 主配置文件（`main_config.toml`）修改后：\n\n- 需要重启机器人才能生效\n- Windows/Linux：按 `Ctrl+C` 停止后重新运行 `python main.py`\n- Docker：执行 `docker-compose restart xybotv2`\n\n2. 插件配置文件（`plugins/all_in_one_config.toml`）修改后：\n\n- 可以使用热重载命令，无需重启机器人\n- 在聊天中发送以下命令之一（需要机器人管理员权限）：\n    - `重载插件 插件名`\n    - `重载所有插件`\n- 也可以重启机器人来生效\n\n## 注意事项\n\n1. 确保配置文件格式正确：\n\n- 使用 `UTF-8` 编码\n- 遵循 `TOML` 格式规范\n- 修改后检查是否有语法错误\n\n2. 权限问题：\n\n- Linux/Docker 环境下确保有正确的读写权限\n- 如遇权限问题，可使用 sudo 或调整文件权限：\n\n```bash\nsudo chmod 644 main_config.toml\nsudo chmod 644 plugins/all_in_one_config.toml\n```\n\n3. 管理员权限说明：\n\n- 可在主配置文件中设置管理员\n\n4. Docker 环境特别说明：\n\n- 配置文件位于数据卷中，修改后会持久保存\n- 重建容器不会影响配置文件\n- 确保数据卷正确挂载\n\n## 配置说明\n\n# main_config.toml 配置说明\n\n```toml\n[WechatAPIServer]\nport = 9000                # WechatAPI服务器端口，默认9000，如有冲突可修改\nmode = \"release\"           # 运行模式：release(生产环境)，debug(调试模式)\nredis-host = \"127.0.0.1\"   # Redis服务器地址，本地使用127.0.0.1\nredis-port = 6379          # Redis端口，默认6379\nredis-password = \"\"        # Redis密码，如果有设置密码则填写\nredis-db = 0               # Redis数据库编号，默认0\n\n# XYBot 核心设置\n[XYBot]\nversion = \"v1.0.0\"                    # 版本号，请勿修改\nignore-protection = false             # 是否忽略风控保护机制，建议保持false\n\n# SQLite数据库地址，一般无需修改\nXYBotDB-url = \"sqlite:///database/xybot.db\"\nmsgDB-url = \"sqlite+aiosqlite:///database/message.db\"\nkeyvalDB-url = \"sqlite+aiosqlite:///database/keyval.db\"\n\n# 管理员设置\nadmins = [\"admin-wxid\", \"admin-wxid\"]  # 管理员的wxid列表，可从消息日志中获取\ndisabled-plugins = [\"ExamplePlugin\", \"TencentLke\"]   # 禁用的插件列表，不需要的插件名称填在这里\ntimezone = \"Asia/Shanghai\"             # 时区设置，中国用户使用 Asia/Shanghai\n\n# 实验性功能，如果main_config.toml配置改动，或者plugins文件夹有改动，自动重启。可以在开发时使用，不建议在生产环境使用。\nauto-restart = false                 # 仅建议在开发时启用，生产环境保持false\n\n# 消息过滤设置\nignore-mode = \"None\"            # 消息处理模式：\n# \"None\" - 处理所有消息\n# \"Whitelist\" - 仅处理白名单消息\n# \"Blacklist\" - 屏蔽黑名单消息\n\nwhitelist = [# 白名单列表\n    \"wxid_1\", # 个人用户微信ID\n    \"wxid_2\",\n    \"111@chatroom\", # 群聊ID\n    \"222@chatroom\"\n]\n\nblacklist = [# 黑名单列表\n    \"wxid_3\", # 个人用户微信ID\n    \"wxid_4\",\n    \"333@chatroom\", # 群聊ID\n    \"444@chatroom\"\n]\n```\n\n## 说明\n\n1. **管理员设置**\n    - 管理员ID获取方法：\n        1. 先启动机器人\n        2. 私聊机器人任意消息\n        3. 在日志中找到自己的 `wxid`\n\n2. **消息过滤模式**\n    - `None` 模式：处理所有消息\n    - `Whitelist` 模式：仅处理白名单中的用户/群消息\n    - `Blacklist` 模式：屏蔽黑名单中的用户/群消息\n\n3. **数据安全**\n    - 建议定期备份数据库文件(`xybot.db`)\n    - 请勿泄露配置文件中的敏感信息（如 `API` 密钥）\n\n## 插件配置\n\n每个插件现在都在单独的文件夹中，都包含 `config.toml` 插件配置文件。\n\n\n## 获取天气 [GetWeather]\n\n用于查询城市天气信息的功能模块。\n\n```toml\nenable = true      # 是否启用此功能\ncommand-format = \"\"\"⚙️获取天气：    # 支持的命令格式说明\n天气 城市名\n天气城市名\n城市名天气\n城市名 天气\"\"\"\napi-key = \"api-key\"    # 和风天气API密钥\n# 申请方法：\n# 1. 访问 https://dev.qweather.com/\n# 2. 注册账号并选择免费订阅\n# 3. 获取 Private KEY（注意不是 Public ID）\n```"
  },
  {
    "path": "main_config.toml",
    "content": "[WechatAPIServer]\nport = 9000                # WechatAPI服务器端口，默认9000，如有冲突可修改\nmode = \"release\"           # 运行模式：release(生产环境)，debug(调试模式)\nredis-host = \"127.0.0.1\"   # Redis服务器地址，本地使用127.0.0.1\nredis-port = 6379          # Redis端口，默认6379\nredis-password = \"\"        # Redis密码，如果有设置密码则填写\nredis-db = 0               # Redis数据库编号，默认0\n\n# XYBot 核心设置\n[XYBot]\nversion = \"v1.0.0\"                    # 版本号，请勿修改\nignore-protection = false             # 是否忽略风控保护机制，建议保持false\n\n# SQLite数据库地址，一般无需修改\nXYBotDB-url = \"sqlite:///database/xybot.db\"\nmsgDB-url = \"sqlite+aiosqlite:///database/message.db\"\nkeyvalDB-url = \"sqlite+aiosqlite:///database/keyval.db\"\n\n# 管理员设置\nadmins = [\"admin-wxid\", \"admin-wxid\"]  # 管理员的wxid列表，可从消息日志中获取\ndisabled-plugins = [\"ExamplePlugin\", \"TencentLke\", \"DailyBot\"]   # 禁用的插件列表，不需要的插件名称填在这里\ntimezone = \"Asia/Shanghai\"             # 时区设置，中国用户使用 Asia/Shanghai\n\n# 实验性功能，如果main_config.toml配置改动，或者plugins文件夹有改动，自动重启。可以在开发时使用，不建议在生产环境使用。\nauto-restart = false                 # 仅建议在开发时启用，生产环境保持false\n\n# 消息过滤设置\nignore-mode = \"None\"            # 消息处理模式：\n# \"None\" - 处理所有消息\n# \"Whitelist\" - 仅处理白名单消息\n# \"Blacklist\" - 屏蔽黑名单消息\n\nwhitelist = [# 白名单列表\n    \"wxid_1\", # 个人用户微信ID\n    \"wxid_2\",\n    \"111@chatroom\", # 群聊ID\n    \"222@chatroom\"\n]\n\nblacklist = [# 黑名单列表\n    \"wxid_3\", # 个人用户微信ID\n    \"wxid_4\",\n    \"333@chatroom\", # 群聊ID\n    \"444@chatroom\"\n]\n\n[WebUI]\nadmin-username = \"admin\" # 管理员账号\nadmin-password = \"admin123\" # 管理员密码（注意安全风险！）\nsession-timeout = 30 # 会话超时时间（分钟）\n\nflask-secret-key = \"\" # 如为空，会覆盖环境变量。如果覆盖环境变量也是空的则默认用\"HenryXiaoYang_XYBotV2\"\ndebug = false"
  },
  {
    "path": "plugins/AdminPoint/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/AdminPoint/config.toml",
    "content": "[AdminPoint]\nenable = true\ncommand-format = \"\"\"⚙️管理积分\n\n➕积分：\n加积分 积分 wxid/@用户\n\n➖积分：\n减积分 积分 wxid/@用户\n\n🔢设置积分：\n设置积分 积分 wxid/@用户\"\"\""
  },
  {
    "path": "plugins/AdminPoint/main.py",
    "content": "import tomllib\n\nfrom WechatAPI import WechatAPIClient\nfrom database.XYBotDB import XYBotDB\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass AdminPoint(PluginBase):\n    description = \"管理积分\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/AdminPoint/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        with open(\"main_config.toml\", \"rb\") as f:\n            main_config = tomllib.load(f)\n\n        config = plugin_config[\"AdminPoint\"]\n        main_config = main_config[\"XYBot\"]\n\n        self.enable = config[\"enable\"]\n        self.command_format = config[\"command-format\"]\n\n        self.admins = main_config[\"admins\"]\n\n        self.db = XYBotDB()\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = content.split(\" \")\n\n        if not len(command) or command[0] not in [\"加积分\", \"减积分\", \"设置积分\"]:\n            return\n\n        sender_wxid = message[\"SenderWxid\"]\n\n        if sender_wxid not in self.admins:\n            await bot.send_text_message(message[\"FromWxid\"], \"-----XYBot-----\\n❌你配用这个指令吗？😡\")\n            return\n        elif len(command) < 3 or not command[1].isdigit():\n            await bot.send_text_message(message[\"FromWxid\"], f\"-----XYBot-----\\n{self.command_format}\")\n            return\n\n        if command[0] == \"加积分\":\n            if command[2].startswith(\"@\") and len(message[\"Ats\"]) == 1:  # 判断是@还是wxid\n                change_wxid = message[\"Ats\"][0]\n            elif \"@\" not in \" \".join(command[2:]):\n                change_wxid = command[2]\n            else:\n                await bot.send_text_message(message[\"FromWxid\"], \"-----XYBot-----\\n❌请不要手动@！\")\n                return\n\n            change_point = int(command[1])\n            self.db.add_points(change_wxid, change_point)\n\n            nickname = await bot.get_nickname(change_wxid)\n            new_point = self.db.get_points(change_wxid)\n\n            output = (\n                f\"-----XYBot-----\\n\"\n                f\"成功功给 {change_wxid} {nickname if nickname else ''} 加了 {change_point} 点积分\\n\"\n                f\"他现在有 {new_point} 点积分\"\n            )\n\n            await bot.send_text_message(message[\"FromWxid\"], output)\n\n        elif command[0] == \"减积分\":\n            if command[2].startswith(\"@\") and len(message[\"Ats\"]) == 1:  # 判断是@还是wxid\n                change_wxid = message[\"Ats\"][0]\n            elif \"@\" not in \" \".join(command[2:]):\n                change_wxid = command[2]\n            else:\n                await bot.send_text_message(message[\"FromWxid\"], \"-----XYBot-----\\n❌请不要手动@！\")\n                return\n\n            change_point = int(command[1])\n            self.db.add_points(change_wxid, -change_point)\n\n            nickname = await bot.get_nickname(change_wxid)\n            new_point = self.db.get_points(change_wxid)\n\n            output = (\n                f\"-----XYBot-----\\n\"\n                f\"成功功给 {nickname if nickname else ''} {change_wxid} 减了 {change_point} 点积分\\n\"\n                f\"他现在有 {new_point} 点积分\"\n            )\n\n            await bot.send_text_message(message[\"FromWxid\"], output)\n\n        elif command[0] == \"设置积分\":\n            if command[2].startswith(\"@\") and len(message[\"Ats\"]) == 1:  # 判断是@还是wxid\n                change_wxid = message[\"Ats\"][0]\n            elif \"@\" not in \" \".join(command[2:]):\n                change_wxid = command[2]\n            else:\n                await bot.send_text_message(message[\"FromWxid\"], \"-----XYBot-----\\n❌请不要手动@！\")\n                return\n\n            change_point = int(command[1])\n            self.db.set_points(change_wxid, change_point)\n\n            nickname = await bot.get_nickname(change_wxid)\n\n            output = (\n                f\"-----XYBot-----\\n\"\n                f\"成功功将 {nickname if nickname else ''} {change_wxid} 的积分设置为 {change_point}\"\n            )\n\n            await bot.send_text_message(message[\"FromWxid\"], output)\n"
  },
  {
    "path": "plugins/AdminSigninReset/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/AdminSigninReset/config.toml",
    "content": "[AdminSignInReset]\nenable = true\ncommand = [\"重置签到\", \"重置签到状态\"]"
  },
  {
    "path": "plugins/AdminSigninReset/main.py",
    "content": "import tomllib\n\nfrom WechatAPI import WechatAPIClient\nfrom database.XYBotDB import XYBotDB\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass AdminSignInReset(PluginBase):\n    description = \"重置签到状态\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/AdminSigninReset/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        with open(\"main_config.toml\", \"rb\") as f:\n            main_config = tomllib.load(f)\n\n        config = plugin_config[\"AdminSignInReset\"]\n        main_config = main_config[\"XYBot\"]\n\n        self.enable = config[\"enable\"]\n        self.command = config[\"command\"]\n\n        self.admins = main_config[\"admins\"]\n\n        self.db = XYBotDB()\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = content.split(\" \")\n\n        if not len(command) or command[0] not in self.command:\n            return\n\n        sender_wxid = message[\"SenderWxid\"]\n\n        if sender_wxid not in self.admins:\n            await bot.send_text_message(message[\"FromWxid\"], \"-----XYBot-----\\n❌你配用这个指令吗？😡\")\n            return\n\n        self.db.reset_all_signin_stat()\n        await bot.send_text_message(message[\"FromWxid\"], \"-----XYBot-----\\n成功重置签到状态！\")\n"
  },
  {
    "path": "plugins/AdminWhitelist/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/AdminWhitelist/config.toml",
    "content": "[AdminWhitelist]\nenable = true\ncommand-format = \"\"\"⚙️管理白名单\n\n➕白名单：\n添加白名单 wxid/@用户\n\n➖白名单：\n删除白名单 wxid/@用户\n\n📋白名单列表：\n白名单列表\"\"\""
  },
  {
    "path": "plugins/AdminWhitelist/main.py",
    "content": "import tomllib\n\nfrom WechatAPI import WechatAPIClient\nfrom database.XYBotDB import XYBotDB\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass AdminWhitelist(PluginBase):\n    description = \"管理白名单\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/AdminWhitelist/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        with open(\"main_config.toml\", \"rb\") as f:\n            main_config = tomllib.load(f)\n\n        config = plugin_config[\"AdminWhitelist\"]\n        main_config = main_config[\"XYBot\"]\n\n        self.enable = config[\"enable\"]\n        self.command_format = config[\"command-format\"]\n\n        self.admins = main_config[\"admins\"]\n\n        self.db = XYBotDB()\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = content.split(\" \")\n\n        if not len(command) or command[0] not in [\"添加白名单\", \"移除白名单\", \"白名单列表\"]:\n            return\n\n        sender_wxid = message[\"SenderWxid\"]\n\n        if sender_wxid not in self.admins:\n            await bot.send_text_message(message[\"FromWxid\"], \"-----XYBot-----\\n❌你配用这个指令吗？😡\")\n            return\n\n        if command[0] == \"添加白名单\":\n            if len(command) < 2:\n                await bot.send_text_message(message[\"FromWxid\"], self.command_format)\n                return\n\n            if command[1].startswith(\"@\") and len(message[\"Ats\"]) == 1:  # 判断是@还是wxid\n                change_wxid = message[\"Ats\"][0]\n            elif \"@\" not in \" \".join(command[1:]):\n                change_wxid = command[1]\n            else:\n                await bot.send_text_message(message[\"FromWxid\"], \"-----XYBot-----\\n❌请不要手动@！\")\n                return\n\n            self.db.set_whitelist(change_wxid, True)\n\n            nickname = await bot.get_nickname(change_wxid)\n            await bot.send_text_message(message[\"FromWxid\"],\n                                        f\"-----XYBot-----\\n成功添加 {nickname if nickname else ''} {change_wxid} 到白名单\")\n\n        elif command[0] == \"移除白名单\":\n            if len(command) < 2:\n                await bot.send_text_message(message[\"FromWxid\"], self.command_format)\n                return\n\n            if command[1].startswith(\"@\") and len(message[\"Ats\"]) == 1:  # 判断是@还是wxid\n                change_wxid = message[\"Ats\"][0]\n            elif \"@\" not in \" \".join(command[1:]):\n                change_wxid = command[1]\n            else:\n                await bot.send_text_message(message[\"FromWxid\"], \"-----XYBot-----\\n❌请不要手动@！\")\n                return\n\n            self.db.set_whitelist(change_wxid, False)\n\n            nickname = await bot.get_nickname(change_wxid)\n            await bot.send_text_message(message[\"FromWxid\"],\n                                        f\"-----XYBot-----\\n成功把 {nickname if nickname else ''} {change_wxid} 移出白名单！\")\n\n        elif command[0] == \"白名单列表\":\n            whitelist = self.db.get_whitelist_list()\n            whitelist = \"\\n\".join([f\"{wxid} {await bot.get_nickname(wxid)}\" for wxid in whitelist])\n            await bot.send_text_message(message[\"FromWxid\"], f\"-----XYBot-----\\n白名单列表：\\n{whitelist}\")\n\n        else:\n            await bot.send_text_message(message[\"FromWxid\"], self.command_format)\n            return\n"
  },
  {
    "path": "plugins/BotStatus/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/BotStatus/config.toml",
    "content": "[BotStatus]\nenable = true\ncommand = [\"status\", \"bot\", \"机器人状态\", \"状态\"]\nstatus-message = \"XYBot Running! 😊\""
  },
  {
    "path": "plugins/BotStatus/main.py",
    "content": "import re\nimport tomllib\n\nfrom WechatAPI import WechatAPIClient\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass BotStatus(PluginBase):\n    description = \"机器人状态\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/BotStatus/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        with open(\"main_config.toml\", \"rb\") as f:\n            main_config = tomllib.load(f)\n\n        config = plugin_config[\"BotStatus\"]\n        main_config = main_config[\"XYBot\"]\n\n        self.enable = config[\"enable\"]\n        self.command = config[\"command\"]\n        self.version = main_config[\"version\"]\n        self.status_message = config[\"status-message\"]\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = content.split(\" \")\n\n        if not len(command) or command[0] not in self.command:\n            return\n\n        out_message = (f\"{self.status_message}\\n\"\n                       f\"当前版本: {self.version}\\n\"\n                       \"项目地址：https://github.com/HenryXiaoYang/XYBotV2\\n\")\n        await bot.send_text_message(message.get(\"FromWxid\"), out_message)\n\n    @on_at_message\n    async def handle_at(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = re.split(r'[\\s\\u2005]+', content)\n\n        if len(command) < 2 or command[1] not in self.command:\n            return\n\n        out_message = (f\"{self.status_message}\\n\"\n                       f\"当前版本: {self.version}\\n\"\n                       \"项目地址：https://github.com/HenryXiaoYang/XYBotV2\\n\")\n        await bot.send_text_message(message.get(\"FromWxid\"), out_message)\n"
  },
  {
    "path": "plugins/DependencyManager/README.md",
    "content": "# 🔧 依赖包管理器 (DependencyManager)\n\n> 🚀 通过微信命令直接管理 Python 依赖包，无需登录服务器！\n> **本插件是 [XYBotv2](https://github.com/HenryXiaoYang/XYBotv2) 的一个插件。**\n\n <img src=\"https://github.com/user-attachments/assets/a2627960-69d8-400d-903c-309dbeadf125\" width=\"400\" height=\"600\">\n\n## ✨ 功能特点\n\n- 📦 **包管理** - 远程安装、更新、查询和卸载 Python 包\n- 🔒 **安全控制** - 仅限管理员使用，防止未授权操作\n- 🛡️ **包白名单** - 可选启用允许安装的包列表，提高安全性\n- 🔍 **导入检查** - 检查包是否可以成功导入，快速诊断问题\n- 📋 **列表展示** - 清晰显示已安装的所有包及其版本\n- 📊 **详细输出** - 提供完整的安装/卸载过程信息和错误报告\n- ⚙️ **版本控制** - 支持安装特定版本的包，灵活应对兼容性需求\n- 💬 **简单易用** - 通过直观的命令快速管理依赖\n\n## 📋 使用指南\n\n### 安装包\n\n安装最新版本的包：\n\n```\n!pip install 包名\n```\n\n安装特定版本的包：\n\n```\n!pip install 包名==1.2.3\n```\n\n### 查询包信息\n\n查看已安装包的详细信息：\n\n```\n!pip show 包名\n```\n\n### 列出所有已安装的包\n\n```\n!pip list\n```\n\n### 卸载包\n\n```\n!pip uninstall 包名\n```\n\n### 检查包是否可以导入\n\n```\n!import 包名\n```\n\n### 从 GitHub 安装插件\n\n使用 github 唤醒词安装（必需）：\n\n```\ngithub https://github.com/用户名/插件名.git\n```\n\n或使用简化格式：\n\n```\ngithub 用户名/插件名\n```\n\n快捷命令安装 GeminiImage 插件：\n\n```\ngithub gemini\n```\n\n获取 GitHub 安装帮助：\n\n```\ngithub help\n```\n\n### 获取帮助\n\n```\n!pip help\n```\n\n或简单地：\n\n```\n!pip\n```\n\n## 🔄 使用流程示例\n\n1. 检查某个包是否已安装：`!pip show numpy`\n2. 安装新版本的包：`!pip install pandas`\n3. 安装特定版本的包：`!pip install tensorflow==2.9.0`\n4. 检查包是否可以导入：`!import tensorflow`\n5. 如需更新已安装的包：`!pip install --upgrade pillow`\n6. 卸载不再需要的包：`!pip uninstall tensorflow`\n7. 安装 GitHub 上的插件（完整 URL）：`github https://github.com/NanSsye/GeminiImage.git`\n8. 安装 GitHub 上的插件（简化格式）：`github NanSsye/GeminiImage`\n9. 使用快捷命令安装 GeminiImage 插件：`github gemini`\n10. 测试插件是否正常工作：`!test dm`\n\n## ⚙️ 配置说明\n\n在`config.toml`中设置：\n\n```toml\n[basic]\n# 是否启用插件\nenable = true\n\n# 管理员列表，只有这些用户可以使用此插件\n# 这里填写管理员的微信ID\nadmin_list = [\"wxid_lnbsshdobq7y22\", \"xianan96928\"]\n\n# 安全设置\n# 是否检查包是否在允许列表中（true/false）\ncheck_allowed = false\n\n# 允许安装的包列表（如果check_allowed=true）\nallowed_packages = [\n    \"akshare\",\n    \"requests\",\n    \"pillow\",\n    \"matplotlib\",\n    \"numpy\",\n    \"pandas\",\n    \"lxml\",\n    \"beautifulsoup4\",\n    \"aiohttp\"\n]\n\n[commands]\n# 命令前缀配置\ninstall = \"!pip install\"\nshow = \"!pip show\"\nlist = \"!pip list\"\nuninstall = \"!pip uninstall\"\n# GitHub插件安装命令前缀\ngithub_install = \"github\" \n```\n\n### 安全设置说明\n\n为了保护服务器安全，您可以启用包白名单功能：\n\n1. 将`check_allowed`设置为`true`\n2. 在`allowed_packages`列表中添加允许安装的包名\n3. 任何不在列表中的包将被拒绝安装\n\n这可以防止安装潜在有害的包或占用过多系统资源的大型包。\n\n## 🔒 安全性注意事项\n\n- 仅向可信任的管理员提供访问权限\n- 考虑启用包白名单功能，限制可安装的包\n- 定期检查已安装的包，确保系统安全\n- 此插件可以执行系统命令，请谨慎使用\n\n## 📊 适用场景\n\n- 远程服务器维护，无需 SSH 登录\n- 快速安装新依赖以支持其他插件\n- 紧急修复生产环境中的依赖问题\n- 检查和诊断包导入失败的问题\n- 清理不再需要的包以释放空间\n\n## 📝 开发日志\n\n- v1.0.0: 初始版本发布，支持基本的包管理功能\n\n## 👨‍💻 作者\n\n**老夏的金库** ©️ 2024\n\n**开源不易，感谢打赏支持！**\n![image](https://github.com/user-attachments/assets/2dde3b46-85a1-4f22-8a54-3928ef59b85f)\n\n## �� 许可证\n\nMIT License\n"
  },
  {
    "path": "plugins/DependencyManager/__init__.py",
    "content": " "
  },
  {
    "path": "plugins/DependencyManager/config.toml",
    "content": "[basic]\n# 是否启用插件\nenable = true\n\n# 安全设置\n# 是否检查包是否在允许列表中（true/false）\ncheck_allowed = false\n\n# 允许安装的包列表（如果check_allowed=true）\nallowed_packages = [\n    \"akshare\",\n    \"requests\",\n    \"pillow\",\n    \"matplotlib\",\n    \"numpy\",\n    \"pandas\",\n    \"lxml\",\n    \"beautifulsoup4\",\n    \"aiohttp\"\n]\n\n[commands]\n# 命令前缀配置\ninstall = \"!pip install\"\nshow = \"!pip show\"\nlist = \"!pip list\"\nuninstall = \"!pip uninstall\"\n# GitHub插件安装命令前缀\ngithub_install = \"github\" "
  },
  {
    "path": "plugins/DependencyManager/main.py",
    "content": "\"\"\"\r\n依赖包管理插件 - 允许管理员通过微信命令安装Python依赖包和Github插件\r\n\r\n作者: 老夏的金库\r\n版本: 1.2.0\r\n\"\"\"\r\nimport importlib\r\nimport io\r\nimport os\r\nimport re\r\nimport shutil\r\nimport subprocess\r\nimport sys\r\nimport tempfile\r\nimport tomllib\r\nimport zipfile\r\n\r\nimport requests\r\nfrom loguru import logger\r\n\r\nfrom WechatAPI import WechatAPIClient\r\nfrom utils.decorators import *\r\nfrom utils.plugin_base import PluginBase\r\n\r\n\r\nclass DependencyManager(PluginBase):\r\n    \"\"\"依赖包管理插件，允许管理员通过微信发送命令来安装/更新/查询Python依赖包和Github插件\"\"\"\r\n\r\n    description = \"依赖包管理插件\"\r\n    author = \"老夏的金库 HenryXiaoYang\"\r\n    version = \"1.2.0\"\r\n\r\n    # Change Log\r\n    # 1.2.0 HenryXiaoYang 把调试消息和日志都注释掉了，因为刷屏有点严重\r\n\r\n    def __init__(self):\r\n        super().__init__()\r\n\r\n        # 记录插件开始初始化\r\n        # logger.info(\"[DependencyManager] 开始加载插件\")\r\n\r\n        # 获取配置文件路径\r\n        self.plugin_dir = os.path.dirname(os.path.abspath(__file__))\r\n        self.config_path = os.path.join(self.plugin_dir, \"config.toml\")\r\n\r\n        # 获取主项目根目录 - 使用相对路径 - _data/plugins\r\n        self.root_dir = os.path.dirname(self.plugin_dir)  # 指向_data/plugins目录\r\n        # logger.info(f\"[DependencyManager] 根目录设置为: {self.root_dir}\")\r\n\r\n        # 插件目录就是根目录本身\r\n        self.plugins_dir = self.root_dir\r\n        # logger.info(f\"[DependencyManager] 插件目录设置为: {self.plugins_dir}\")\r\n\r\n        # 加载配置\r\n        self.load_config()\r\n\r\n        # logger.info(f\"[DependencyManager] 插件初始化完成, 启用状态: {self.enable}, 优先级: 80\")\r\n\r\n    def load_config(self):\r\n        \"\"\"加载配置文件\"\"\"\r\n        try:\r\n            with open(\"main_config.toml\", \"rb\") as f:\r\n                config = tomllib.load(f)\r\n\r\n            self.admin_list = config.get(\"XYBot\", {}).get(\"admins\", [])\r\n\r\n            # logger.info(f\"[DependencyManager] 尝试从 {self.config_path} 加载配置\")\r\n\r\n            with open(self.config_path, \"rb\") as f:\r\n                config = tomllib.load(f)\r\n\r\n            # 读取基本配置\r\n            basic_config = config.get(\"basic\", {})\r\n            self.enable = basic_config.get(\"enable\", False)\r\n            self.allowed_packages = basic_config.get(\"allowed_packages\", [])\r\n            self.check_allowed = basic_config.get(\"check_allowed\", False)\r\n\r\n            # 读取命令配置\r\n            cmd_config = config.get(\"commands\", {})\r\n            self.install_cmd = cmd_config.get(\"install\", \"!pip install\")\r\n            self.show_cmd = cmd_config.get(\"show\", \"!pip show\")\r\n            self.list_cmd = cmd_config.get(\"list\", \"!pip list\")\r\n            self.uninstall_cmd = cmd_config.get(\"uninstall\", \"!pip uninstall\")\r\n\r\n            # 读取插件安装配置 - 使用唤醒词\r\n            self.github_install_prefix = cmd_config.get(\"github_install\", \"github\")\r\n\r\n            # logger.info(f\"[DependencyManager] 配置加载成功\")\r\n            # logger.info(f\"[DependencyManager] 启用状态: {self.enable}\")\r\n            # logger.info(f\"[DependencyManager] 管理员列表: {self.admin_list}\")\r\n            logger.info(f\"[DependencyManager] GitHub前缀: '{self.github_install_prefix}'\")\r\n\r\n        except Exception as e:\r\n            logger.error(f\"[DependencyManager] 加载配置失败: {str(e)}\")\r\n            self.enable = False\r\n            self.admin_list = []\r\n            self.allowed_packages = []\r\n            self.check_allowed = False\r\n            self.install_cmd = \"!pip install\"\r\n            self.show_cmd = \"!pip show\"\r\n            self.list_cmd = \"!pip list\"\r\n            self.uninstall_cmd = \"!pip uninstall\"\r\n            self.github_install_prefix = \"github\"\r\n\r\n    @on_text_message(priority=80)\r\n    async def handle_text_message(self, bot: WechatAPIClient, message: dict):\r\n        \"\"\"处理文本消息，检查是否为依赖管理命令\"\"\"\r\n        # 在最开始就记录收到消息，即使未启用也记录，便于调试\r\n        # logger.info(f\"[DependencyManager] 收到消息调用: {message.get('Content', '')}\")\r\n\r\n        if not self.enable:\r\n            # logger.debug(\"[DependencyManager] 插件未启用，跳过处理\")\r\n            return True  # 插件未启用，允许其他插件处理\r\n\r\n        # 获取消息内容和发送者 - 修改为使用正确的键名\r\n        content = message.get(\"Content\", \"\").strip()\r\n        from_user = message.get(\"SenderWxid\", \"\")\r\n        conversation_id = message.get(\"FromWxid\", \"\")\r\n\r\n        # 记录所有消息，用于调试\r\n        # logger.info(f\"[DependencyManager] 收到消息: '{content}'\")\r\n\r\n        # 检查是否为管理员\r\n        sender_id = from_user\r\n        # if not sender_id and \"IsGroup\" in message and message[\"IsGroup\"]:\r\n        # 如果是群聊消息，则SenderWxid应该已经包含发送者ID\r\n        # logger.debug(f\"[DependencyManager] 群消息，发送者ID: {sender_id}\")\r\n\r\n        # 记录消息处理信息\r\n        # logger.info(f\"[DependencyManager] 发送者ID: {sender_id}\")\r\n        # logger.info(f\"[DependencyManager] 会话ID: {conversation_id}\")\r\n        # logger.info(f\"[DependencyManager] GitHub安装前缀: {self.github_install_prefix}\")\r\n\r\n        # 检查是否为管理员\r\n        if sender_id not in self.admin_list:\r\n            # logger.info(f\"[DependencyManager] 用户 {sender_id} 不在管理员列表中\")\r\n            # logger.info(f\"[DependencyManager] 当前管理员列表: {self.admin_list}\")\r\n            return True  # 非管理员，允许其他插件处理\r\n\r\n        # logger.info(f\"[DependencyManager] 管理员 {sender_id} 发送命令: {content}\")\r\n\r\n        # ====================== 命令处理部分 ======================\r\n        # 按照优先级排序，先处理特殊命令，再处理标准命令模式\r\n\r\n        # 1. 测试命令 - 用于诊断插件是否正常工作\r\n        if content == \"!test dm\":\r\n            await bot.send_text_message(conversation_id, \"✅ DependencyManager插件工作正常！\")\r\n            logger.info(\"[DependencyManager] 测试命令响应成功\")\r\n            return False\r\n\r\n        # 2. GitHub相关命令处理 - 优先级最高\r\n\r\n        # 2.1 检查是否明确以GitHub前缀开头 - 要求明确的安装意图\r\n        starts_with_prefix = content.lower().startswith(self.github_install_prefix.lower())\r\n        logger.info(\r\n            f\"[DependencyManager] 检查是否以'{self.github_install_prefix}'开头: {starts_with_prefix}, 内容: '{content}'\")\r\n\r\n        # 2.2 GitHub快捷命令 - GeminiImage特殊处理\r\n        if starts_with_prefix and (content.strip().lower() == f\"{self.github_install_prefix} gemini\" or\r\n                                   content.strip().lower() == f\"{self.github_install_prefix} geminiimage\"):\r\n            logger.info(\"[DependencyManager] 检测到GeminiImage快捷安装命令\")\r\n            await bot.send_text_message(conversation_id, \"🔄 正在安装GeminiImage插件...\")\r\n            await self._handle_github_install(bot, conversation_id, \"https://github.com/NanSsye/GeminiImage.git\")\r\n            logger.info(\"[DependencyManager] GeminiImage快捷安装完成，阻止后续插件处理\")\r\n            return False\r\n\r\n        # 2.3 GitHub帮助命令\r\n        if content.strip().lower() == f\"{self.github_install_prefix} help\":\r\n            help_text = f\"\"\"📦 GitHub插件安装帮助:\r\n\r\n1. 安装GitHub上的插件:\r\n   {self.github_install_prefix} https://github.com/用户名/插件名.git\r\n\r\n2. 例如，安装GeminiImage插件:\r\n   {self.github_install_prefix} https://github.com/NanSsye/GeminiImage.git\r\n   \r\n3. 简化格式:\r\n   {self.github_install_prefix} 用户名/插件名\r\n   \r\n4. 快捷命令安装GeminiImage:\r\n   {self.github_install_prefix} gemini\r\n\r\n5. 插件会自动被克隆到插件目录并安装依赖\r\n\r\n注意: 安装后需要重启机器人以加载新插件。\r\n\"\"\"\r\n            await bot.send_text_message(conversation_id, help_text)\r\n            logger.info(\"[DependencyManager] GitHub安装帮助命令响应成功\")\r\n            return False\r\n\r\n        # 2.4 标准GitHub安装命令处理 - 必须以明确的前缀开头\r\n        if starts_with_prefix:\r\n            logger.info(f\"[DependencyManager] 检测到GitHub安装命令: {content}\")\r\n            # 获取前缀后面的内容\r\n            command_content = content[len(self.github_install_prefix):].strip()\r\n            logger.info(f\"[DependencyManager] 提取的命令内容: '{command_content}'\")\r\n\r\n            # 处理快捷命令 - gemini\r\n            if command_content.lower() == \"gemini\" or command_content.lower() == \"geminiimage\":\r\n                logger.info(\"[DependencyManager] 检测到GeminiImage快捷安装命令\")\r\n                await self._handle_github_install(bot, conversation_id, \"https://github.com/NanSsye/GeminiImage.git\")\r\n                logger.info(\"[DependencyManager] GeminiImage安装命令处理完成，返回False阻止后续处理\")\r\n                return False\r\n\r\n            # 处理标准GitHub URL\r\n            elif command_content.startswith(\"https://github.com\") or command_content.startswith(\"github.com\"):\r\n                logger.info(f\"[DependencyManager] 检测到GitHub URL: {command_content}\")\r\n                await self._handle_github_install(bot, conversation_id, command_content)\r\n                logger.info(\"[DependencyManager] GitHub URL安装命令处理完成，返回False阻止后续处理\")\r\n                return False\r\n\r\n            # 处理简化格式 - 用户名/仓库名\r\n            elif \"/\" in command_content and not command_content.startswith(\"!\"):\r\n                # 检查是否符合 用户名/仓库名 格式\r\n                if re.match(r'^[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$', command_content.strip()):\r\n                    repo_path = command_content.strip()\r\n                    logger.info(f\"[DependencyManager] 检测到简化的GitHub路径: {repo_path}\")\r\n                    github_url = f\"https://github.com/{repo_path}\"\r\n                    logger.info(f\"[DependencyManager] 构建GitHub URL: {github_url}\")\r\n                    await self._handle_github_install(bot, conversation_id, github_url)\r\n                    logger.info(\"[DependencyManager] 简化GitHub路径安装命令处理完成，返回False阻止后续处理\")\r\n                    return False\r\n\r\n            # 格式不正确\r\n            else:\r\n                await bot.send_text_message(conversation_id,\r\n                                            f\"⚠️ GitHub安装命令格式不正确。正确格式为: \\n1. {self.github_install_prefix} https://github.com/用户名/插件名.git\\n2. {self.github_install_prefix} 用户名/插件名\")\r\n                logger.info(\"[DependencyManager] GitHub格式不正确，已发送提示，返回False阻止后续处理\")\r\n                return False\r\n\r\n            # 如果是以GitHub前缀开头但没有匹配到任何处理分支，也阻止后续处理\r\n            logger.info(\"[DependencyManager] 命令以github开头但未匹配任何处理逻辑，默认阻止后续处理\")\r\n            return False\r\n\r\n        # 忽略智能识别GitHub URL的逻辑，必须以明确的前缀开始才处理\r\n\r\n        # 3. 依赖管理命令\r\n\r\n        # 3.1 处理安装命令\r\n        if content.startswith(self.install_cmd):\r\n            await self._handle_install(bot, conversation_id, content.replace(self.install_cmd, \"\").strip())\r\n            logger.debug(f\"[DependencyManager] 处理安装命令完成，阻止后续插件\")\r\n            return False  # 命令已处理，不传递给其他插件\r\n\r\n        # 3.2 处理查询命令\r\n        elif content.startswith(self.show_cmd):\r\n            await self._handle_show(bot, conversation_id, content.replace(self.show_cmd, \"\").strip())\r\n            logger.debug(f\"[DependencyManager] 处理查询命令完成，阻止后续插件\")\r\n            return False\r\n\r\n        # 3.3 处理列表命令\r\n        elif content.startswith(self.list_cmd):\r\n            await self._handle_list(bot, conversation_id)\r\n            logger.debug(f\"[DependencyManager] 处理列表命令完成，阻止后续插件\")\r\n            return False\r\n\r\n        # 3.4 处理卸载命令\r\n        elif content.startswith(self.uninstall_cmd):\r\n            await self._handle_uninstall(bot, conversation_id, content.replace(self.uninstall_cmd, \"\").strip())\r\n            logger.debug(f\"[DependencyManager] 处理卸载命令完成，阻止后续插件\")\r\n            return False\r\n\r\n        # 3.5 处理帮助命令\r\n        elif content.strip() == \"!pip help\" or content.strip() == \"!pip\":\r\n            await self._send_help(bot, conversation_id)\r\n            logger.debug(f\"[DependencyManager] 处理帮助命令完成，阻止后续插件\")\r\n            return False\r\n\r\n        # 3.6 处理导入检查命令\r\n        elif content.startswith(\"!import\"):\r\n            package = content.replace(\"!import\", \"\").strip()\r\n            await self._check_import(bot, conversation_id, package)\r\n            logger.debug(f\"[DependencyManager] 处理导入检查命令完成，阻止后续插件\")\r\n            return False\r\n\r\n        # 不是本插件的命令\r\n        logger.debug(f\"[DependencyManager] 非依赖管理相关命令，允许其他插件处理\")\r\n        return True  # 不是命令，允许其他插件处理\r\n\r\n    async def _handle_install(self, bot: WechatAPIClient, chat_id: str, package_spec: str):\r\n        \"\"\"处理安装依赖包命令\"\"\"\r\n        if not package_spec:\r\n            await bot.send_text_message(chat_id, \"请指定要安装的包，例如: !pip install packagename==1.0.0\")\r\n            return\r\n\r\n        # 检查是否在允许安装的包列表中\r\n        base_package = package_spec.split(\"==\")[0].split(\">=\")[0].split(\">\")[0].split(\"<\")[0].strip()\r\n        if self.check_allowed and self.allowed_packages and base_package not in self.allowed_packages:\r\n            await bot.send_text_message(chat_id, f\"⚠️ 安全限制: {base_package} 不在允许安装的包列表中\")\r\n            return\r\n\r\n        await bot.send_text_message(chat_id, f\"📦 正在安装: {package_spec}...\")\r\n\r\n        try:\r\n            # 执行pip安装命令\r\n            process = subprocess.Popen(\r\n                [sys.executable, \"-m\", \"pip\", \"install\", package_spec],\r\n                stdout=subprocess.PIPE,\r\n                stderr=subprocess.PIPE,\r\n                text=True\r\n            )\r\n            stdout, stderr = process.communicate()\r\n\r\n            if process.returncode == 0:\r\n                # 安装成功\r\n                output = f\"✅ 安装成功: {package_spec}\\n\\n{stdout}\"\r\n                # 如果输出太长，只取前后部分\r\n                if len(output) > 1000:\r\n                    output = output[:500] + \"\\n...\\n\" + output[-500:]\r\n                await bot.send_text_message(chat_id, output)\r\n            else:\r\n                # 安装失败\r\n                error = f\"❌ 安装失败: {package_spec}\\n\\n{stderr}\"\r\n                # 如果输出太长，只取前后部分\r\n                if len(error) > 1000:\r\n                    error = error[:500] + \"\\n...\\n\" + error[-500:]\r\n                await bot.send_text_message(chat_id, error)\r\n\r\n        except Exception as e:\r\n            await bot.send_text_message(chat_id, f\"❌ 执行安装命令时出错: {str(e)}\")\r\n\r\n    async def _handle_github_install(self, bot: WechatAPIClient, chat_id: str, github_url: str):\r\n        \"\"\"处理从Github安装插件的命令\"\"\"\r\n        logger.info(f\"[DependencyManager] 开始处理GitHub插件安装，URL: {github_url}\")\r\n\r\n        # 处理各种GitHub URL格式\r\n        if not github_url:\r\n            logger.warning(\"[DependencyManager] GitHub URL为空\")\r\n            await bot.send_text_message(chat_id,\r\n                                        \"请提供有效的GitHub仓库URL，例如: github https://github.com/用户名/插件名.git\")\r\n            return\r\n\r\n        # 标准化GitHub URL\r\n        # 处理不包含https://的情况\r\n        if not github_url.startswith(\"http\"):\r\n            if github_url.startswith(\"github.com\"):\r\n                github_url = \"https://\" + github_url\r\n            elif \"github.com\" in github_url:\r\n                # 尝试提取用户名/仓库名\r\n                match = re.search(r'(?:github\\.com[:/])?([^/\\s]+/[^/\\s]+)(?:\\.git)?', github_url)\r\n                if match:\r\n                    repo_path = match.group(1)\r\n                    github_url = f\"https://github.com/{repo_path}\"\r\n                else:\r\n                    github_url = \"https://github.com/\" + github_url.strip()\r\n\r\n        logger.info(f\"[DependencyManager] 标准化后的URL: {github_url}\")\r\n\r\n        # 验证URL格式\r\n        if not github_url.startswith(\"https://github.com\"):\r\n            logger.warning(f\"[DependencyManager] 无效的GitHub URL: {github_url}\")\r\n            await bot.send_text_message(chat_id,\r\n                                        \"请提供有效的GitHub仓库URL，例如: github https://github.com/用户名/插件名.git\")\r\n            return\r\n\r\n        # 确保URL以.git结尾\r\n        if github_url.endswith(\".git\"):\r\n            github_url = github_url[:-4]  # 移除.git后缀，为了构建zip下载链接\r\n\r\n        # 从URL提取插件名称和仓库信息\r\n        repo_match = re.search(r'https://github\\.com/([^/]+)/([^/]+)$', github_url)\r\n        if not repo_match:\r\n            logger.warning(f\"[DependencyManager] 无法从URL中提取仓库信息: {github_url}\")\r\n            await bot.send_text_message(chat_id, f\"⚠️ 无法从URL中提取仓库信息: {github_url}\")\r\n            return\r\n\r\n        user_name = repo_match.group(1)\r\n        repo_name = repo_match.group(2)\r\n        plugin_name = repo_name\r\n\r\n        # 使用相对路径，直接在plugins_dir下创建插件目录\r\n        plugin_target_dir = os.path.join(self.plugins_dir, plugin_name)\r\n        logger.info(f\"[DependencyManager] 提取到用户名: {user_name}, 仓库名: {repo_name}\")\r\n        logger.info(f\"[DependencyManager] 目标目录: {plugin_target_dir}\")\r\n\r\n        # 检查插件目录是否已存在\r\n        if os.path.exists(plugin_target_dir):\r\n            logger.info(f\"[DependencyManager] 插件目录已存在，尝试更新\")\r\n            await bot.send_text_message(chat_id, f\"⚠️ 插件 {plugin_name} 目录已存在，尝试更新...\")\r\n            try:\r\n                # 尝试使用git更新现有插件\r\n                git_installed = self._check_git_installed()\r\n                if git_installed:\r\n                    os.chdir(plugin_target_dir)\r\n                    logger.info(f\"[DependencyManager] 执行git pull操作于: {plugin_target_dir}\")\r\n                    process = subprocess.Popen(\r\n                        [\"git\", \"pull\", \"origin\", \"main\"],\r\n                        stdout=subprocess.PIPE,\r\n                        stderr=subprocess.PIPE,\r\n                        text=True\r\n                    )\r\n                    stdout, stderr = process.communicate()\r\n                    logger.info(f\"[DependencyManager] Git pull结果：退出码 {process.returncode}\")\r\n                    logger.info(f\"[DependencyManager] Stdout: {stdout}\")\r\n                    logger.info(f\"[DependencyManager] Stderr: {stderr}\")\r\n\r\n                    if process.returncode == 0:\r\n                        await bot.send_text_message(chat_id, f\"✅ 成功更新插件 {plugin_name}!\\n\\n{stdout}\")\r\n                        await self._install_plugin_requirements(bot, chat_id, plugin_target_dir)\r\n                    else:\r\n                        logger.error(f\"[DependencyManager] 更新插件失败: {stderr}\")\r\n                        await bot.send_text_message(chat_id, f\"❌ 更新插件失败: {stderr}\")\r\n                else:\r\n                    # 使用ZIP方式更新\r\n                    await bot.send_text_message(chat_id, f\"⚠️ Git未安装，尝试通过下载ZIP方式更新...\")\r\n                    success = await self._download_github_zip(bot, chat_id, user_name, repo_name, plugin_target_dir,\r\n                                                              is_update=True)\r\n                    if success:\r\n                        await self._install_plugin_requirements(bot, chat_id, plugin_target_dir)\r\n            except Exception as e:\r\n                logger.exception(f\"[DependencyManager] 更新插件时出错\")\r\n                await bot.send_text_message(chat_id, f\"❌ 更新插件时出错: {str(e)}\")\r\n            return\r\n\r\n        # 创建临时目录\r\n        with tempfile.TemporaryDirectory() as temp_dir:\r\n            try:\r\n                logger.info(f\"[DependencyManager] 创建临时目录: {temp_dir}\")\r\n                await bot.send_text_message(chat_id, f\"🔄 正在从GitHub下载插件 {plugin_name}...\")\r\n\r\n                # 检查git是否安装，决定使用哪种下载方式\r\n                git_installed = self._check_git_installed()\r\n                logger.info(f\"[DependencyManager] Git命令安装状态: {git_installed}\")\r\n\r\n                if git_installed:\r\n                    # 使用git克隆仓库\r\n                    logger.info(f\"[DependencyManager] 使用git克隆: {github_url}.git 到 {temp_dir}\")\r\n                    process = subprocess.Popen(\r\n                        [\"git\", \"clone\", f\"{github_url}.git\", temp_dir],\r\n                        stdout=subprocess.PIPE,\r\n                        stderr=subprocess.PIPE,\r\n                        text=True\r\n                    )\r\n                    stdout, stderr = process.communicate()\r\n                    logger.info(f\"[DependencyManager] Git clone结果：退出码 {process.returncode}\")\r\n                    logger.info(f\"[DependencyManager] Stdout: {stdout}\")\r\n                    logger.info(f\"[DependencyManager] Stderr: {stderr}\")\r\n\r\n                    if process.returncode != 0:\r\n                        logger.error(f\"[DependencyManager] Git克隆失败，尝试使用ZIP方式下载\")\r\n                        success = await self._download_github_zip(bot, chat_id, user_name, repo_name, temp_dir)\r\n                        if not success:\r\n                            return\r\n                else:\r\n                    # 使用ZIP方式下载\r\n                    logger.info(f\"[DependencyManager] Git未安装，使用ZIP方式下载\")\r\n                    success = await self._download_github_zip(bot, chat_id, user_name, repo_name, temp_dir)\r\n                    if not success:\r\n                        return\r\n\r\n                # 克隆或下载成功，复制到插件目录\r\n                logger.info(f\"[DependencyManager] 创建插件目录: {plugin_target_dir}\")\r\n                os.makedirs(plugin_target_dir, exist_ok=True)\r\n\r\n                # 复制所有文件\r\n                logger.info(f\"[DependencyManager] 开始从临时目录复制文件到插件目录\")\r\n                for item in os.listdir(temp_dir):\r\n                    s = os.path.join(temp_dir, item)\r\n                    d = os.path.join(plugin_target_dir, item)\r\n                    logger.info(f\"[DependencyManager] 复制: {s} 到 {d}\")\r\n                    if os.path.isdir(s):\r\n                        shutil.copytree(s, d, dirs_exist_ok=True)\r\n                    else:\r\n                        shutil.copy2(s, d)\r\n\r\n                logger.info(f\"[DependencyManager] 文件复制完成\")\r\n                await bot.send_text_message(chat_id, f\"✅ 成功下载插件 {plugin_name}!\")\r\n\r\n                # 安装依赖\r\n                await self._install_plugin_requirements(bot, chat_id, plugin_target_dir)\r\n            except Exception as e:\r\n                logger.exception(f\"[DependencyManager] 安装插件时出错\")\r\n                await bot.send_text_message(chat_id, f\"❌ 安装插件时出错: {str(e)}\")\r\n\r\n    def _check_git_installed(self):\r\n        \"\"\"检查git命令是否可用\"\"\"\r\n        try:\r\n            process = subprocess.Popen(\r\n                [\"git\", \"--version\"],\r\n                stdout=subprocess.PIPE,\r\n                stderr=subprocess.PIPE,\r\n                text=True\r\n            )\r\n            process.communicate()\r\n            return process.returncode == 0\r\n        except Exception:\r\n            return False\r\n\r\n    async def _download_github_zip(self, bot, chat_id, user_name, repo_name, target_dir, is_update=False):\r\n        \"\"\"使用requests下载GitHub仓库的ZIP文件\"\"\"\r\n        try:\r\n            # 构建ZIP下载链接\r\n            zip_url = f\"https://github.com/{user_name}/{repo_name}/archive/refs/heads/main.zip\"\r\n            logger.info(f\"[DependencyManager] 开始下载ZIP: {zip_url}\")\r\n\r\n            # 发送下载状态\r\n            await bot.send_text_message(chat_id, f\"📥 正在从GitHub下载ZIP文件: {zip_url}\")\r\n\r\n            # 下载ZIP文件\r\n            response = requests.get(zip_url, timeout=30)\r\n            if response.status_code != 200:\r\n                # 尝试使用master分支\r\n                zip_url = f\"https://github.com/{user_name}/{repo_name}/archive/refs/heads/master.zip\"\r\n                logger.info(f\"[DependencyManager] 尝试下载master分支: {zip_url}\")\r\n                response = requests.get(zip_url, timeout=30)\r\n\r\n            if response.status_code != 200:\r\n                logger.error(f\"[DependencyManager] 下载ZIP失败，状态码: {response.status_code}\")\r\n                await bot.send_text_message(chat_id, f\"❌ 下载ZIP文件失败，HTTP状态码: {response.status_code}\")\r\n                return False\r\n\r\n            # 解压ZIP文件\r\n            logger.info(f\"[DependencyManager] 下载完成，文件大小: {len(response.content)} 字节\")\r\n            logger.info(f\"[DependencyManager] 解压ZIP文件到: {target_dir}\")\r\n\r\n            z = zipfile.ZipFile(io.BytesIO(response.content))\r\n\r\n            # 检查ZIP文件内容\r\n            zip_contents = z.namelist()\r\n            logger.info(f\"[DependencyManager] ZIP文件内容: {', '.join(zip_contents[:5])}...\")\r\n\r\n            if is_update:\r\n                # 更新时先备份配置文件\r\n                config_files = []\r\n                if os.path.exists(os.path.join(target_dir, \"config.toml\")):\r\n                    with open(os.path.join(target_dir, \"config.toml\"), \"rb\") as f:\r\n                        config_files.append((\"config.toml\", f.read()))\r\n\r\n                # 清空目录（保留.git目录）\r\n                for item in os.listdir(target_dir):\r\n                    if item == \".git\":\r\n                        continue\r\n                    item_path = os.path.join(target_dir, item)\r\n                    if os.path.isdir(item_path):\r\n                        shutil.rmtree(item_path)\r\n                    else:\r\n                        os.remove(item_path)\r\n\r\n            # 解压文件\r\n            extract_dir = tempfile.mkdtemp()\r\n            z.extractall(extract_dir)\r\n\r\n            # ZIP文件解压后通常会有一个包含所有文件的顶级目录\r\n            extracted_dirs = os.listdir(extract_dir)\r\n            if len(extracted_dirs) == 1:\r\n                extract_subdir = os.path.join(extract_dir, extracted_dirs[0])\r\n\r\n                # 将文件从解压的子目录复制到目标目录\r\n                for item in os.listdir(extract_subdir):\r\n                    s = os.path.join(extract_subdir, item)\r\n                    d = os.path.join(target_dir, item)\r\n                    if os.path.isdir(s):\r\n                        shutil.copytree(s, d, dirs_exist_ok=True)\r\n                    else:\r\n                        shutil.copy2(s, d)\r\n            else:\r\n                # 直接解压到目标目录\r\n                for item in os.listdir(extract_dir):\r\n                    s = os.path.join(extract_dir, item)\r\n                    d = os.path.join(target_dir, item)\r\n                    if os.path.isdir(s):\r\n                        shutil.copytree(s, d, dirs_exist_ok=True)\r\n                    else:\r\n                        shutil.copy2(s, d)\r\n\r\n            # 清理临时目录\r\n            shutil.rmtree(extract_dir)\r\n\r\n            # 如果是更新，恢复配置文件\r\n            if is_update and config_files:\r\n                for filename, content in config_files:\r\n                    with open(os.path.join(target_dir, filename), \"wb\") as f:\r\n                        f.write(content)\r\n                logger.info(f\"[DependencyManager] 已恢复配置文件\")\r\n\r\n            await bot.send_text_message(chat_id, f\"✅ ZIP文件下载并解压成功\")\r\n            return True\r\n        except Exception as e:\r\n            logger.exception(f\"[DependencyManager] 下载ZIP文件时出错\")\r\n            await bot.send_text_message(chat_id, f\"❌ 下载ZIP文件时出错: {str(e)}\")\r\n            return False\r\n\r\n    async def _install_plugin_requirements(self, bot: WechatAPIClient, chat_id: str, plugin_dir: str):\r\n        \"\"\"安装插件的依赖项\"\"\"\r\n        requirements_file = os.path.join(plugin_dir, \"requirements.txt\")\r\n\r\n        if not os.path.exists(requirements_file):\r\n            await bot.send_text_message(chat_id, \"📌 未找到requirements.txt文件，跳过依赖安装\")\r\n            return\r\n\r\n        try:\r\n            await bot.send_text_message(chat_id, \"📦 正在安装插件依赖...\")\r\n\r\n            # 读取requirements.txt内容\r\n            with open(requirements_file, \"r\") as f:\r\n                requirements = f.read()\r\n\r\n            # 显示依赖列表\r\n            await bot.send_text_message(chat_id, f\"📋 依赖列表:\\n{requirements}\")\r\n\r\n            # 安装依赖\r\n            process = subprocess.Popen(\r\n                [sys.executable, \"-m\", \"pip\", \"install\", \"-r\", requirements_file],\r\n                stdout=subprocess.PIPE,\r\n                stderr=subprocess.PIPE,\r\n                text=True\r\n            )\r\n            stdout, stderr = process.communicate()\r\n\r\n            if process.returncode == 0:\r\n                output = f\"✅ 依赖安装成功!\\n\\n{stdout}\"\r\n                # 如果输出太长，只取前后部分\r\n                if len(output) > 1000:\r\n                    output = output[:500] + \"\\n...\\n\" + output[-500:]\r\n                await bot.send_text_message(chat_id, output)\r\n\r\n                # 提示重启机器人\r\n                await bot.send_text_message(chat_id, \"🔄 插件安装完成！请重启机器人以加载新插件。\")\r\n            else:\r\n                error = f\"❌ 依赖安装失败:\\n\\n{stderr}\"\r\n                # 如果输出太长，只取前后部分\r\n                if len(error) > 1000:\r\n                    error = error[:500] + \"\\n...\\n\" + error[-500:]\r\n                await bot.send_text_message(chat_id, error)\r\n        except Exception as e:\r\n            await bot.send_text_message(chat_id, f\"❌ 安装依赖时出错: {str(e)}\")\r\n\r\n    async def _handle_show(self, bot: WechatAPIClient, chat_id: str, package: str):\r\n        \"\"\"处理查询包信息命令\"\"\"\r\n        if not package:\r\n            await bot.send_text_message(chat_id, \"请指定要查询的包，例如: !pip show packagename\")\r\n            return\r\n\r\n        await bot.send_text_message(chat_id, f\"🔍 正在查询: {package}...\")\r\n\r\n        try:\r\n            process = subprocess.Popen(\r\n                [sys.executable, \"-m\", \"pip\", \"show\", package],\r\n                stdout=subprocess.PIPE,\r\n                stderr=subprocess.PIPE,\r\n                text=True\r\n            )\r\n            stdout, stderr = process.communicate()\r\n\r\n            if process.returncode == 0:\r\n                # 查询成功\r\n                await bot.send_text_message(chat_id, f\"📋 {package} 信息:\\n\\n{stdout}\")\r\n            else:\r\n                # 查询失败\r\n                await bot.send_text_message(chat_id, f\"❌ 查询失败: {package}\\n\\n{stderr}\")\r\n\r\n        except Exception as e:\r\n            await bot.send_text_message(chat_id, f\"❌ 执行查询命令时出错: {str(e)}\")\r\n\r\n    async def _handle_list(self, bot: WechatAPIClient, chat_id: str):\r\n        \"\"\"处理列出所有包命令\"\"\"\r\n        await bot.send_text_message(chat_id, \"📋 正在获取已安装的包列表...\")\r\n\r\n        try:\r\n            process = subprocess.Popen(\r\n                [sys.executable, \"-m\", \"pip\", \"list\"],\r\n                stdout=subprocess.PIPE,\r\n                stderr=subprocess.PIPE,\r\n                text=True\r\n            )\r\n            stdout, stderr = process.communicate()\r\n\r\n            if process.returncode == 0:\r\n                # 获取成功，但可能很长，分段发送\r\n                if len(stdout) > 1000:\r\n                    chunks = [stdout[i:i + 1000] for i in range(0, len(stdout), 1000)]\r\n                    await bot.send_text_message(chat_id, f\"📦 已安装的包列表 (共{len(chunks)}段):\")\r\n                    for i, chunk in enumerate(chunks):\r\n                        await bot.send_text_message(chat_id, f\"📦 第{i + 1}段:\\n\\n{chunk}\")\r\n                else:\r\n                    await bot.send_text_message(chat_id, f\"📦 已安装的包列表:\\n\\n{stdout}\")\r\n            else:\r\n                # 获取失败\r\n                await bot.send_text_message(chat_id, f\"❌ 获取列表失败\\n\\n{stderr}\")\r\n\r\n        except Exception as e:\r\n            await bot.send_text_message(chat_id, f\"❌ 执行列表命令时出错: {str(e)}\")\r\n\r\n    async def _handle_uninstall(self, bot: WechatAPIClient, chat_id: str, package: str):\r\n        \"\"\"处理卸载包命令\"\"\"\r\n        if not package:\r\n            await bot.send_text_message(chat_id, \"请指定要卸载的包，例如: !pip uninstall packagename\")\r\n            return\r\n\r\n        await bot.send_text_message(chat_id, f\"🗑️ 正在卸载: {package}...\")\r\n\r\n        try:\r\n            # 使用-y参数自动确认卸载\r\n            process = subprocess.Popen(\r\n                [sys.executable, \"-m\", \"pip\", \"uninstall\", \"-y\", package],\r\n                stdout=subprocess.PIPE,\r\n                stderr=subprocess.PIPE,\r\n                text=True\r\n            )\r\n            stdout, stderr = process.communicate()\r\n\r\n            if process.returncode == 0:\r\n                # 卸载成功\r\n                await bot.send_text_message(chat_id, f\"✅ 卸载成功: {package}\\n\\n{stdout}\")\r\n            else:\r\n                # 卸载失败\r\n                await bot.send_text_message(chat_id, f\"❌ 卸载失败: {package}\\n\\n{stderr}\")\r\n\r\n        except Exception as e:\r\n            await bot.send_text_message(chat_id, f\"❌ 执行卸载命令时出错: {str(e)}\")\r\n\r\n    async def _send_help(self, bot: WechatAPIClient, chat_id: str):\r\n        \"\"\"发送帮助信息\"\"\"\r\n        help_text = f\"\"\"📚 依赖包管理插件使用帮助:\r\n\r\n1️⃣ 安装包:\r\n   {self.install_cmd} package_name\r\n   {self.install_cmd} package_name==1.2.3  (指定版本)\r\n\r\n2️⃣ 查询包信息:\r\n   {self.show_cmd} package_name\r\n\r\n3️⃣ 列出所有已安装的包:\r\n   {self.list_cmd}\r\n\r\n4️⃣ 卸载包:\r\n   {self.uninstall_cmd} package_name\r\n\r\n5️⃣ 检查包是否可以导入:\r\n   !import package_name\r\n\r\n6️⃣ 安装GitHub插件:\r\n   {self.github_install_prefix} https://github.com/用户名/插件名.git\r\n\r\nℹ️ 仅允许管理员使用此功能\r\n\"\"\"\r\n        await bot.send_text_message(chat_id, help_text)\r\n\r\n    async def _check_import(self, bot: WechatAPIClient, chat_id: str, package: str):\r\n        \"\"\"检查包是否可以成功导入\"\"\"\r\n        if not package:\r\n            await bot.send_text_message(chat_id, \"请指定要检查的包，例如: !import packagename\")\r\n            return\r\n\r\n        await bot.send_text_message(chat_id, f\"🔍 正在检查是否可以导入: {package}...\")\r\n\r\n        try:\r\n            # 尝试导入包\r\n            importlib.import_module(package)\r\n            await bot.send_text_message(chat_id, f\"✅ {package} 可以成功导入!\")\r\n        except ImportError as e:\r\n            await bot.send_text_message(chat_id, f\"❌ 无法导入 {package}: {str(e)}\")\r\n        except Exception as e:\r\n            await bot.send_text_message(chat_id, f\"❌ 导入 {package} 时发生错误: {str(e)}\")\r\n\r\n    async def on_disable(self):\r\n        \"\"\"插件禁用时的清理工作\"\"\"\r\n        await super().on_disable()\r\n        logger.info(\"[DependencyManager] 插件已禁用\")\r\n"
  },
  {
    "path": "plugins/Dify/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/Dify/config.toml",
    "content": "[Dify]\nenable = true\n\napi-key = \"\" # Dify的API Key\nbase-url = \"https://api.dify.ai/v1\" #Dify API接口base url\n\ncommands = [\"ai\", \"dify\", \"聊天\", \"AI\"]\ncommand-tip = \"\"\"-----XYBot-----\n💬AI聊天指令：\n聊天 请求内容\n\"\"\"\n\nprice = 0 # 用一次扣积分，如果0则不扣\nadmin_ignore = true #admin是否忽略扣除\nwhitelist_ignore = true #白名单是否忽略扣除\n\n# Http代理设置\n# 格式: http://用户名:密码@代理地址:代理端口\n# 例如：http://127.0.0.1:7890\nhttp-proxy = \"\""
  },
  {
    "path": "plugins/Dify/main.py",
    "content": "import json\nimport re\nimport tomllib\nimport traceback\n\nimport aiohttp\nimport filetype\nfrom loguru import logger\n\nfrom WechatAPI import WechatAPIClient\nfrom database.XYBotDB import XYBotDB\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass Dify(PluginBase):\n    description = \"Dify插件\"\n    author = \"HenryXiaoYang\"\n    version = \"1.1.0\"\n\n    # Change Log\n    # 1.1.0 2025-02-20 插件优先级，插件阻塞\n    # 1.2.0 2025-02-22 有插件阻塞了，other-plugin-cmd可删了\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"main_config.toml\", \"rb\") as f:\n            config = tomllib.load(f)\n\n        self.admins = config[\"XYBot\"][\"admins\"]\n\n        with open(\"plugins/Dify/config.toml\", \"rb\") as f:\n            config = tomllib.load(f)\n\n        plugin_config = config[\"Dify\"]\n\n        self.enable = plugin_config[\"enable\"]\n        self.api_key = plugin_config[\"api-key\"]\n        self.base_url = plugin_config[\"base-url\"]\n\n        self.commands = plugin_config[\"commands\"]\n        self.command_tip = plugin_config[\"command-tip\"]\n\n        self.price = plugin_config[\"price\"]\n        self.admin_ignore = plugin_config[\"admin_ignore\"]\n        self.whitelist_ignore = plugin_config[\"whitelist_ignore\"]\n\n        self.http_proxy = plugin_config[\"http-proxy\"]\n\n        self.db = XYBotDB()\n\n    @on_text_message(priority=20)\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        command = str(message[\"Content\"]).strip().split(\" \")\n\n        if (not command or command[0] not in self.commands) and message[\"IsGroup\"]:  # 不是指令，且是群聊\n            return\n        elif len(command) == 1 and command[0] in self.commands:  # 只是指令，但没请求内容\n            await bot.send_at_message(message[\"FromWxid\"], \"\\n\" + self.command_tip, [message[\"SenderWxid\"]])\n            return\n\n        if not self.api_key:\n            await bot.send_at_message(message[\"FromWxid\"], \"\\n你还没配置Dify API密钥！\", [message[\"SenderWxid\"]])\n            return False\n\n        if await self._check_point(bot, message):\n            await self.dify(bot, message, message[\"Content\"])\n        return False\n\n    @on_at_message(priority=20)\n    async def handle_at(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        if not self.api_key:\n            await bot.send_at_message(message[\"FromWxid\"], \"\\n你还没配置Dify API密钥！\", [message[\"SenderWxid\"]])\n            return False\n\n        if await self._check_point(bot, message):\n            await self.dify(bot, message, message[\"Content\"])\n\n        return False\n\n    @on_voice_message(priority=20)\n    async def handle_voice(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        if message[\"IsGroup\"]:\n            return\n\n        if not self.api_key:\n            await bot.send_at_message(message[\"FromWxid\"], \"\\n你还没配置Dify API密钥！\", [message[\"SenderWxid\"]])\n            return False\n\n        if await self._check_point(bot, message):\n            upload_file_id = await self.upload_file(message[\"FromWxid\"], message[\"Content\"])\n\n            files = [\n                {\n                    \"type\": \"audio\",\n                    \"transfer_method\": \"local_file\",\n                    \"upload_file_id\": upload_file_id\n                }\n            ]\n\n            await self.dify(bot, message, \" \\n\", files)\n\n        return False\n\n    @on_image_message(priority=20)\n    async def handle_image(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        if message[\"IsGroup\"]:\n            return\n\n        if not self.api_key:\n            await bot.send_at_message(message[\"FromWxid\"], \"\\n你还没配置Dify API密钥！\", [message[\"SenderWxid\"]])\n            return False\n\n        if await self._check_point(bot, message):\n            upload_file_id = await self.upload_file(message[\"FromWxid\"], bot.base64_to_byte(message[\"Content\"]))\n\n            files = [\n                {\n                    \"type\": \"image\",\n                    \"transfer_method\": \"local_file\",\n                    \"upload_file_id\": upload_file_id\n                }\n            ]\n\n            await self.dify(bot, message, \" \\n\", files)\n\n        return False\n\n    @on_video_message(priority=20)\n    async def handle_video(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        if message[\"IsGroup\"]:\n            return\n\n        if not self.api_key:\n            await bot.send_at_message(message[\"FromWxid\"], \"\\n你还没配置Dify API密钥！\", [message[\"SenderWxid\"]])\n            return False\n\n        if await self._check_point(bot, message):\n            upload_file_id = await self.upload_file(message[\"FromWxid\"], bot.base64_to_byte(message[\"Video\"]))\n\n            files = [\n                {\n                    \"type\": \"video\",\n                    \"transfer_method\": \"local_file\",\n                    \"upload_file_id\": upload_file_id\n                }\n            ]\n\n            await self.dify(bot, message, \" \\n\", files)\n\n        return False\n\n    @on_file_message(priority=20)\n    async def handle_file(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        if message[\"IsGroup\"]:\n            return\n\n        if not self.api_key:\n            await bot.send_at_message(message[\"FromWxid\"], \"\\n你还没配置Dify API密钥！\", [message[\"SenderWxid\"]])\n            return False\n\n        if await self._check_point(bot, message):\n            upload_file_id = await self.upload_file(message[\"FromWxid\"], message[\"Content\"])\n\n            files = [\n                {\n                    \"type\": \"document\",\n                    \"transfer_method\": \"local_file\",\n                    \"upload_file_id\": upload_file_id\n                }\n            ]\n\n            await self.dify(bot, message, \" \\n\", files)\n\n        return False\n\n    async def dify(self, bot: WechatAPIClient, message: dict, query: str, files=None):\n        if files is None:\n            files = []\n        conversation_id = self.db.get_llm_thread_id(message[\"FromWxid\"],\n                                                    namespace=\"dify\")\n        headers = {\"Authorization\": f\"Bearer {self.api_key}\",\n                   \"Content-Type\": \"application/json\"}\n        payload = json.dumps({\n            \"inputs\": {},\n            \"query\": query,\n            \"response_mode\": \"streaming\",\n            \"conversation_id\": conversation_id,\n            \"user\": message[\"FromWxid\"],\n            \"files\": files,\n            \"auto_generate_name\": False,\n        })\n        url = f\"{self.base_url}/chat-messages\"\n\n        ai_resp = \"\"\n        async with aiohttp.ClientSession(proxy=self.http_proxy) as session:\n            async with session.post(url=url, headers=headers, data=payload) as resp:\n                if resp.status == 200:\n                    # 读取响应\n                    async for line in resp.content:  # 流式传输\n                        line = line.decode(\"utf-8\").strip()\n                        if not line or line == \"event: ping\":  # 空行或ping\n                            continue\n                        elif line.startswith(\"data: \"):  # 脑瘫吧，为什么前面要加 \"data: \" ？？？\n                            line = line[6:]\n\n                        try:\n                            resp_json = json.loads(line)\n                        except json.decoder.JSONDecodeError:\n                            logger.error(f\"Dify返回的JSON解析错误，请检查格式: {line}\")\n\n                        event = resp_json.get(\"event\", \"\")\n                        if event == \"message\":  # LLM 返回文本块事件\n                            ai_resp += resp_json.get(\"answer\", \"\")\n                        elif event == \"message_replace\":  # 消息内容替换事件\n                            ai_resp = resp_json(\"answer\", \"\")\n                        elif event == \"message_file\":  # 文件事件 目前dify只输出图片\n                            await self.dify_handle_image(bot, message, resp_json.get(\"url\", \"\"))\n                        elif event == \"tts_message\":  # TTS 音频流结束事件\n                            await self.dify_handle_audio(bot, message, resp_json.get(\"audio\", \"\"))\n                        elif event == \"error\":  # 流式输出过程中出现的异常\n                            await self.dify_handle_error(bot, message,\n                                                         resp_json.get(\"task_id\", \"\"),\n                                                         resp_json.get(\"message_id\", \"\"),\n                                                         resp_json.get(\"status\", \"\"),\n                                                         resp_json.get(\"code\", \"\"),\n                                                         resp_json.get(\"message\", \"\"))\n\n                    new_con_id = resp_json.get(\"conversation_id\", \"\")\n                    if new_con_id and new_con_id != conversation_id:\n                        self.db.save_llm_thread_id(message[\"FromWxid\"], new_con_id, \"dify\")\n\n                elif resp.status == 404:\n                    self.db.save_llm_thread_id(message[\"FromWxid\"], \"\", \"dify\")\n                    return await self.dify(bot, message, query)\n\n                elif resp.status == 400:\n                    return await self.handle_400(bot, message, resp)\n\n                elif resp.status == 500:\n                    return await self.handle_500(bot, message)\n\n                else:\n                    return await self.handle_other_status(bot, message, resp)\n\n        if ai_resp:\n            await self.dify_handle_text(bot, message, ai_resp)\n\n    async def upload_file(self, user: str, file: bytes):\n        headers = {\"Authorization\": f\"Bearer {self.api_key}\"}\n\n        # user multipart/form-data\n        kind = filetype.guess(file)\n        formdata = aiohttp.FormData()\n        formdata.add_field(\"user\", user)\n        formdata.add_field(\"file\", file, filename=kind.extension, content_type=kind.mime)\n\n        url = f\"{self.base_url}/files/upload\"\n\n        async with aiohttp.ClientSession(proxy=self.http_proxy) as session:\n            async with session.post(url, headers=headers, data=formdata) as resp:\n                resp_json = await resp.json()\n\n        return resp_json.get(\"id\", \"\")\n\n    async def dify_handle_text(self, bot: WechatAPIClient, message: dict, text: str):\n        pattern = r\"\\]\\((https?:\\/\\/[^\\s\\)]+)\\)\"\n        links = re.findall(pattern, text)\n        for url in links:\n            file = await self.download_file(url)\n            extension = filetype.guess_extension(file)\n            if extension in ('wav', 'mp3'):\n                await bot.send_voice_message(message[\"FromWxid\"], voice=file, format=filetype.guess_extension(file))\n            elif extension in ('jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg'):\n                await bot.send_image_message(message[\"FromWxid\"], file)\n            elif extension in ('mp4', 'avi', 'mov', 'mkv', 'flv'):\n                await bot.send_video_message(message[\"FromWxid\"], video=file, image=\"None\")\n\n        pattern = r'\\[[^\\]]+\\]\\(https?:\\/\\/[^\\s\\)]+\\)'\n        text = re.sub(pattern, '', text)\n        if text:\n            await bot.send_at_message(message[\"FromWxid\"], \"\\n\" + text, [message[\"SenderWxid\"]])\n\n    async def download_file(self, url: str) -> bytes:\n        async with aiohttp.ClientSession(proxy=self.http_proxy) as session:\n            async with session.get(url) as resp:\n                return await resp.read()\n\n    async def dify_handle_image(self, bot: WechatAPIClient, message: dict, image: Union[str, bytes]):\n        if isinstance(image, str) and image.startswith(\"http\"):\n            async with aiohttp.ClientSession(proxy=self.http_proxy) as session:\n                async with session.get(image) as resp:\n                    image = bot.byte_to_base64(await resp.read())\n        elif isinstance(image, bytes):\n            image = bot.byte_to_base64(image)\n\n        await bot.send_image_message(message[\"FromWxid\"], image)\n\n    @staticmethod\n    async def dify_handle_audio(bot: WechatAPIClient, message: dict, audio: str):\n\n        await bot.send_voice_message(message[\"FromWxid\"], audio)\n\n    @staticmethod\n    async def dify_handle_error(bot: WechatAPIClient, message: dict, task_id: str, message_id: str, status: str,\n                                code: int, err_message: str):\n        output = (\"-----XYBot-----\\n\"\n                  \"🙅对不起，Dify出现错误！\\n\"\n                  f\"任务 ID：{task_id}\\n\"\n                  f\"消息唯一 ID：{message_id}\\n\"\n                  f\"HTTP 状态码：{status}\\n\"\n                  f\"错误码：{code}\\n\"\n                  f\"错误信息：{err_message}\")\n        await bot.send_at_message(message[\"FromWxid\"], \"\\n\" + output, [message[\"SenderWxid\"]])\n\n    @staticmethod\n    async def handle_400(bot: WechatAPIClient, message: dict, resp: aiohttp.ClientResponse):\n        output = (\"-----XYBot-----\\n\"\n                  \"🙅对不起，出现错误！\\n\"\n                  f\"错误信息：{(await resp.content.read()).decode('utf-8')}\")\n        await bot.send_at_message(message[\"FromWxid\"], \"\\n\" + output, [message[\"SenderWxid\"]])\n\n    @staticmethod\n    async def handle_500(bot: WechatAPIClient, message: dict):\n        output = \"-----XYBot-----\\n🙅对不起，Dify服务内部异常，请稍后再试。\"\n        await bot.send_at_message(message[\"FromWxid\"], \"\\n\" + output, [message[\"SenderWxid\"]])\n\n    @staticmethod\n    async def handle_other_status(bot: WechatAPIClient, message: dict, resp: aiohttp.ClientResponse):\n        ai_resp = (\"-----XYBot-----\\n\"\n                   f\"🙅对不起，出现错误！\\n\"\n                   f\"状态码：{resp.status}\\n\"\n                   f\"错误信息：{(await resp.content.read()).decode('utf-8')}\")\n        await bot.send_at_message(message[\"FromWxid\"], \"\\n\" + ai_resp, [message[\"SenderWxid\"]])\n\n    @staticmethod\n    async def hendle_exceptions(bot: WechatAPIClient, message: dict):\n        output = (\"-----XYBot-----\\n\"\n                  \"🙅对不起，出现错误！\\n\"\n                  f\"错误信息：\\n\"\n                  f\"{traceback.format_exc()}\")\n        await bot.send_at_message(message[\"FromWxid\"], \"\\n\" + output, [message[\"SenderWxid\"]])\n\n    async def _check_point(self, bot: WechatAPIClient, message: dict) -> bool:\n        wxid = message[\"SenderWxid\"]\n\n        if wxid in self.admins and self.admin_ignore:\n            return True\n        elif self.db.get_whitelist(wxid) and self.whitelist_ignore:\n            return True\n        else:\n            if self.db.get_points(wxid) < self.price:\n                await bot.send_at_message(message[\"FromWxid\"],\n                                          f\"\\n-----XYBot-----\\n\"\n                                          f\"😭你的积分不够啦！需要 {self.price} 积分\",\n                                          [wxid])\n                return False\n\n            self.db.add_points(wxid, -self.price)\n            return True\n"
  },
  {
    "path": "plugins/DouyinParser/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/DouyinParser/config.toml",
    "content": "[basic]\nenable = true\n\n# Http代理设置（用于获取真实链接发送卡片，如果家里有ipv6，可以设置为空）\n# 格式: http://用户名:密码@代理地址:代理端口\n# 例如：http://127.0.0.1:7890\nhttp_proxy = \"\""
  },
  {
    "path": "plugins/DouyinParser/main.py",
    "content": "import re\nimport tomllib\nimport os\nfrom typing import Dict, Any\nimport traceback\nimport asyncio\n\nimport aiohttp\nfrom loguru import logger\n\nfrom WechatAPI import WechatAPIClient\nfrom utils.decorators import on_text_message\nfrom utils.plugin_base import PluginBase\n\n\nclass DouyinParserError(Exception):\n    \"\"\"抖音解析器自定义异常基类\"\"\"\n    pass\n\n\nclass DouyinParser(PluginBase):\n    description = \"抖音无水印解析插件\"\n    author = \"姜不吃先生\"  # 群友太给力了！\n    version = \"1.0.2\"\n\n    def __init__(self):\n        super().__init__()\n        self.url_pattern = re.compile(r'https?://v\\.douyin\\.com/\\w+/?')\n\n        # 读取代理配置\n        config_path = os.path.join(os.path.dirname(__file__), \"config.toml\")\n        try:\n            with open(config_path, \"rb\") as f:\n                config = tomllib.load(f)\n                \n            # 基础配置\n            basic_config = config.get(\"basic\", {})\n            self.enable = basic_config.get(\"enable\", True)\n            self.http_proxy = basic_config.get(\"http_proxy\", None)\n            \n        except Exception as e:\n            logger.error(f\"加载抖音解析器配置文件失败: {str(e)}\")\n            self.enable = True\n            self.http_proxy = None\n\n        logger.debug(\"[抖音] 插件初始化完成，代理设置: {}\", self.http_proxy)\n\n    def _clean_response_data(self, data: Dict[str, Any]) -> Dict[str, Any]:\n        \"\"\"清理响应数据\"\"\"\n        if not data:\n            return data\n\n        # 使用固定的抖音图标作为封面\n        data[\n            'cover'] = \"https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/7c/49/e1/7c49e1af-ce92-d1c4-9a93-0a316e47ba94/AppIcon_TikTok-0-0-1x_U007epad-0-1-0-0-85-220.png/512x512bb.jpg\"\n\n        return data\n\n    def _clean_url(self, url: str) -> str:\n        \"\"\"清理URL中的特殊字符\"\"\"\n        cleaned_url = url.strip().replace(';', '').replace('\\n', '').replace('\\r', '')\n        logger.debug(\"[抖音] 清理后的URL: {}\", cleaned_url)  # 添加日志\n        return cleaned_url\n\n    async def _get_real_video_url(self, video_url: str) -> str:\n        \"\"\"获取真实视频链接\"\"\"\n        max_retries = 3  # 最大重试次数\n        retry_delay = 2  # 重试延迟秒数\n        \n        for retry in range(max_retries):\n            try:\n                logger.info(\"[抖音] 开始获取真实视频链接: {} (第{}次尝试)\", video_url, retry + 1)\n                \n                # 修正代理格式\n                proxy = f\"http://{self.http_proxy}\" if self.http_proxy and not self.http_proxy.startswith(('http://', 'https://')) else self.http_proxy\n                logger.debug(\"[抖音] 使用代理: {}\", proxy)\n                \n                headers = {\n                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',\n                    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',\n                    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',\n                    'Range': 'bytes=0-'\n                }\n                \n                async with aiohttp.ClientSession() as session:\n                    async with session.get(video_url, \n                                         proxy=proxy, \n                                         headers=headers,\n                                         allow_redirects=True, \n                                         timeout=60) as response:  # 延长超时时间到60秒\n                        if response.status == 200 or response.status == 206:\n                            # 获取所有重定向历史\n                            history = [str(resp.url) for resp in response.history]\n                            real_url = str(response.url)\n                            \n                            # 记录重定向链接历史，用于调试\n                            if history:\n                                logger.debug(\"[抖音] 重定向历史: {}\", history)\n                            \n                            # 检查是否获取到了真实的视频URL\n                            if real_url != video_url and ('v3-' in real_url.lower() or 'douyinvod.com' in real_url.lower()):\n                                logger.info(\"[抖音] 成功获取真实链接: {}\", real_url)\n                                return real_url\n                            else:\n                                logger.warning(\"[抖音] 未能获取到真实视频链接，准备重试\")\n                                if retry < max_retries - 1:  # 如果不是最后一次尝试，则等待后重试\n                                    await asyncio.sleep(retry_delay)\n                                    continue\n                                return video_url\n                        else:\n                            logger.error(\"[抖音] 获取视频真实链接失败, 状态码: {}\", response.status)\n                            logger.debug(\"[抖音] 响应头: {}\", response.headers)\n                            if retry < max_retries - 1:\n                                await asyncio.sleep(retry_delay)\n                                continue\n                            return video_url\n                        \n            except Exception as e:\n                logger.error(\"[抖音] 获取真实链接失败: {} (第{}次尝试)\", str(e), retry + 1)\n                if retry < max_retries - 1:\n                    await asyncio.sleep(retry_delay)\n                    continue\n                return video_url\n        \n        logger.error(\"[抖音] 获取真实链接失败，已达到最大重试次数\")\n        return video_url\n\n    async def _parse_douyin(self, url: str) -> Dict[str, Any]:\n        \"\"\"调用抖音解析API\"\"\"\n        try:\n            api_url = \"https://apih.kfcgw50.me/api/douyin\"\n            clean_url = self._clean_url(url)\n            params = {\n                'url': clean_url,\n                'type': 'json'\n            }\n\n            logger.debug(\"[抖音] 请求API: {}, 参数: {}\", api_url, repr(params))  # 添加日志\n\n            async with aiohttp.ClientSession() as session:\n                # 使用代理\n                proxy = f\"http://{self.http_proxy}\" if self.http_proxy and not self.http_proxy.startswith(('http://', 'https://')) else self.http_proxy\n                async with session.get(api_url, params=params, timeout=30, proxy=proxy) as response:  # 使用代理\n                    if response.status != 200:\n                        raise DouyinParserError(f\"API请求失败，状态码: {response.status}\")\n\n                    data = await response.json()\n                    logger.debug(\"[抖音] API响应数据: {}\", data)  # 添加日志\n\n                    if data.get(\"code\") == 200:\n                        result = data.get(\"data\", {})\n                        if not result:\n                            raise DouyinParserError(\"API返回数据为空\")\n\n                        # 获取真实视频链接\n                        if result.get('video'):\n                            result['video'] = await self._get_real_video_url(result['video'])\n\n                        result = self._clean_response_data(result)\n                        logger.debug(\"[抖音] 清理后的数据: {}\", result)\n                        return result\n                    else:\n                        raise DouyinParserError(data.get(\"message\", \"未知错误\"))\n\n        except (aiohttp.ClientTimeout, aiohttp.ClientError) as e:\n            logger.error(\"[抖音] 解析失败: {}\", str(e))\n            raise DouyinParserError(str(e))\n        except Exception as e:\n            logger.error(\"[抖音] 解析过程发生未知错误: {}\\n{}\", str(e), traceback.format_exc())\n            raise DouyinParserError(f\"未知错误: {str(e)}\")\n\n    async def _send_test_card(self, bot: WechatAPIClient, chat_id: str, sender: str):\n        \"\"\"发送测试卡片消息\"\"\"\n        try:\n            # 测试数据\n            test_data = {\n                'video': 'https://v11-cold.douyinvod.com/c183ceff049f008265680819dbd8ac0a/67b206c0/video/tos/cn/tos-cn-ve-15/ok8JumeiqAI3pJ2nAiQE9rBiTfm1KtADABlBgV/?a=1128&ch=0&cr=0&dr=0&cd=0%7C0%7C0%7C0&cv=1&br=532&bt=532&cs=0&ds=3&ft=H4NIyvvBQx9Uf8ym8Z.6TQjSYE7OYMDtGkd~P4Aq8_45a&mime_type=video_mp4&qs=0&rc=ZzU5NTRnNDw1aGc5aDloZkBpanE4M3Y5cjNkeDMzNGkzM0AuLy1fLWFhXjQxNjFgYzRiYSNmXzZlMmRjcmdgLS1kLTBzcw%3D%3D&btag=80010e000ad000&cquery=100y&dy_q=1739716635&feature_id=aa7df520beeae8e397df15f38df0454c&l=20250216223715047FF68C05B9F67E1F19',\n                'title': '测试视频标题',\n                'name': '测试作者',\n                'cover': 'https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/7c/49/e1/7c49e1af-ce92-d1c4-9a93-0a316e47ba94/AppIcon_TikTok-0-0-1x_U007epad-0-1-0-0-85-220.png/512x512bb.jpg'\n            }\n\n            logger.info(\"开始发送测试卡片\")\n            logger.debug(f\"测试数据: {test_data}\")\n\n            # 发送测试卡片\n            await bot.send_link_message(\n                wxid=chat_id,\n                url=test_data['video'],\n                title=f\"{test_data['title'][:30]} - {test_data['name'][:10]}\",\n                description=\"这是一个测试卡片消息\",\n                thumb_url=test_data['cover']\n            )\n\n            logger.info(\"测试卡片发送成功\")\n\n            # 发送详细信息\n            debug_msg = (\n                \"🔍 测试卡片详情:\\n\"\n                f\"视频链接: {test_data['video']}\\n\"\n                f\"封面链接: {test_data['cover']}\\n\"\n                f\"标题: {test_data['title']} - {test_data['name']}\"\n            )\n            await bot.send_text_message(\n                wxid=chat_id,\n                content=debug_msg,\n                at=[sender]\n            )\n\n        except Exception as e:\n            error_msg = f\"测试卡片发送失败: {str(e)}\"\n            logger.error(error_msg)\n            await bot.send_text_message(\n                wxid=chat_id,\n                content=error_msg,\n                at=[sender]\n            )\n\n    @on_text_message(priority=80)\n    async def handle_douyin_links(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return True\n\n        content = message['Content']\n        sender = message['SenderWxid']\n        chat_id = message['FromWxid']\n\n        # 添加测试命令识别\n        if content.strip() == \"测试卡片\":\n            await self._send_test_card(bot, chat_id, sender)\n            return\n\n        try:\n            # 提取抖音链接并清理\n            match = self.url_pattern.search(content)\n            if not match:\n                return\n\n            original_url = self._clean_url(match.group(0))\n            logger.info(f\"发现抖音链接: {original_url}\")\n            \n            # 添加解析提示\n            msg_args = {\n                'wxid': chat_id,\n                'content': \"检测到抖音分享链接，正在解析无水印视频...\\n\" if message['IsGroup'] else \"检测到抖音分享链接，正在解析无水印视频...\"\n            }\n            if message['IsGroup']:\n                msg_args['at'] = [sender]\n            await bot.send_text_message(**msg_args)\n\n            # 解析视频信息\n            video_info = await self._parse_douyin(original_url)\n\n            if not video_info:\n                raise DouyinParserError(\"无法获取视频信息\")\n\n            # 获取视频信息\n            video_url = video_info.get('video', '')\n            title = video_info.get('title', '无标题')\n            author = video_info.get('name', '未知作者')\n            cover = video_info.get('cover', '')\n\n            if not video_url:\n                raise DouyinParserError(\"无法获取视频地址\")\n\n            # 发送文字版消息\n            text_msg = (\n                f\"🎬 解析成功，微信内可直接观看（需ipv6）,浏览器打开可下载保存。\\n\"\n                f\"链接含有有效期，请尽快保存。\\n\"\n            )\n            if message['IsGroup']:\n                text_msg = text_msg + \"\\n\"\n                await bot.send_text_message(wxid=chat_id, content=text_msg, at=[sender])\n            else:\n                await bot.send_text_message(wxid=chat_id, content=text_msg)\n\n            # 发送卡片版消息\n            await bot.send_link_message(\n                wxid=chat_id,\n                url=video_url,\n                title=f\"{title[:30]} - {author[:10]}\" if author else title[:40],\n                description=\"点击观看无水印视频\",\n                thumb_url=cover\n            )\n\n            logger.info(f\"已发送解析结果: 标题[{title}] 作者[{author}]\")\n\n        except DouyinParserError as e:\n            error_msg = str(e) if str(e) else \"解析失败\"\n            logger.error(f\"抖音解析失败: {error_msg}\")\n            if message['IsGroup']:\n                await bot.send_text_message(wxid=chat_id, content=f\"视频解析失败: {error_msg}\\n\", at=[sender])\n            else:\n                await bot.send_text_message(wxid=chat_id, content=f\"视频解析失败: {error_msg}\")\n        except Exception as e:\n            error_msg = str(e) if str(e) else \"未知错误\"\n            logger.error(f\"抖音解析发生未知错误: {error_msg}\")\n            if message['IsGroup']:\n                await bot.send_text_message(wxid=chat_id, content=f\"视频解析失败: {error_msg}\\n\", at=[sender])\n            else:\n                await bot.send_text_message(wxid=chat_id, content=f\"视频解析失败: {error_msg}\")\n\n    async def async_init(self):\n        \"\"\"异步初始化函数\"\"\"\n        # 可以在这里进行一些异步的初始化操作\n        # 比如测试API可用性等\n        pass\n"
  },
  {
    "path": "plugins/ExamplePlugin/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/ExamplePlugin/config.toml",
    "content": "[basic]\n# 是否启用插件\nenable = true"
  },
  {
    "path": "plugins/ExamplePlugin/main.py",
    "content": "from loguru import logger\nimport tomllib  # 确保导入tomllib以读取配置文件\nimport os  # 确保导入os模块\n\nfrom WechatAPI import WechatAPIClient\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass ExamplePlugin(PluginBase):\n    description = \"示例插件\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    # 同步初始化\n    def __init__(self):\n        super().__init__()\n\n        # 获取配置文件路径\n        config_path = os.path.join(os.path.dirname(__file__), \"config.toml\")\n        \n        try:\n            with open(config_path, \"rb\") as f:\n                config = tomllib.load(f)\n                \n            # 读取基本配置\n            basic_config = config.get(\"basic\", {})\n            self.enable = basic_config.get(\"enable\", False)  # 读取插件开关\n\n        except Exception as e:\n            logger.error(f\"加载ExamplePlugin配置文件失败: {str(e)}\")\n            self.enable = False  # 如果加载失败，禁用插件\n\n    # 异步初始化\n    async def async_init(self):\n        return\n\n    @on_text_message(priority=99)\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return  # 如果插件未启用，直接返回\n        logger.info(\"收到了文本消息。\")\n\n    @on_at_message(priority=50)\n    async def handle_at(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n        logger.info(\"收到了被@消息，中等优先级\")\n\n    @on_voice_message()\n    async def handle_voice(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n        logger.info(\"收到了语音消息，最低优先级\")\n\n    @on_image_message\n    async def handle_image(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n        logger.info(\"收到了图片消息\")\n\n    @on_video_message\n    async def handle_video(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n        logger.info(\"收到了视频消息\")\n\n    @on_file_message\n    async def handle_file(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n        logger.info(\"收到了文件消息\")\n\n    @on_quote_message\n    async def handle_quote(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n        logger.info(\"收到了引用消息\")\n\n    @on_pat_message\n    async def handle_pat(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n        logger.info(\"收到了拍一拍消息\")\n\n    @on_emoji_message\n    async def handle_emoji(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n        logger.info(\"收到了表情消息\")\n\n    @schedule('interval', seconds=5)\n    async def periodic_task(self, bot: WechatAPIClient):\n        if not self.enable:\n            return\n        logger.info(\"我每5秒执行一次\")\n\n    @schedule('cron', hour=8, minute=30, second=30)\n    async def daily_task(self, bot: WechatAPIClient):\n        if not self.enable:\n            return\n        logger.info(\"我每天早上8点30分30秒执行\")\n\n    @schedule('date', run_date='2025-01-29 00:00:00')\n    async def new_year_task(self, bot: WechatAPIClient):\n        if not self.enable:\n            return\n        logger.info(\"我在2025年1月29日执行\")\n"
  },
  {
    "path": "plugins/GetContact/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/GetContact/config.toml",
    "content": "[GetContact]\nenable = true\ncommand = [\"获取联系人\", \"联系人\", \"通讯录\", \"获取通讯录\"]"
  },
  {
    "path": "plugins/GetContact/main.py",
    "content": "import asyncio\nimport tomllib\nfrom datetime import datetime\n\nimport aiohttp\nfrom loguru import logger\nfrom tabulate import tabulate\n\nfrom WechatAPI import WechatAPIClient\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass GetContact(PluginBase):\n    description = \"获取通讯录\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/GetContact/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        with open(\"main_config.toml\", \"rb\") as f:\n            main_config = tomllib.load(f)\n\n        config = plugin_config[\"GetContact\"]\n        main_config = main_config[\"XYBot\"]\n\n        self.enable = config[\"enable\"]\n        self.command = config[\"command\"]\n\n        self.admins = main_config[\"admins\"]\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = content.split(\" \")\n\n        if not len(command) or command[0] not in self.command:\n            return\n\n        sender_wxid = message[\"SenderWxid\"]\n\n        if sender_wxid not in self.admins:\n            await bot.send_text_message(message[\"FromWxid\"], \"-----XYBot-----\\n❌你配用这个指令吗？😡\")\n            return\n\n        a, b, c = await bot.send_text_message(message[\"FromWxid\"], \"-----XYBot-----\\n正在获取通讯录信息，请稍等...\")\n\n        start_time = datetime.now()\n        logger.info(\"开始获取通讯录信息时间：{}\", start_time)\n\n        id_list = []\n        wx_seq, chatroom_seq = 0, 0\n        while True:\n            contact_list = await bot.get_contract_list(wx_seq, chatroom_seq)\n            id_list.extend(contact_list[\"ContactUsernameList\"])\n            wx_seq = contact_list[\"CurrentWxcontactSeq\"]\n            chatroom_seq = contact_list[\"CurrentChatRoomContactSeq\"]\n            if contact_list[\"CountinueFlag\"] != 1:\n                break\n\n        get_list_time = datetime.now()\n        logger.info(\"获取通讯录信息列表耗时：{}\", get_list_time - start_time)\n\n        # 使用协程池处理联系人信息获取\n        info_list = []\n\n        async def fetch_contacts(id_chunk):\n            contact_info = await bot.get_contact(id_chunk)\n            return contact_info\n\n        chunks = [id_list[i:i + 20] for i in range(0, len(id_list), 20)]\n\n        sem = asyncio.Semaphore(20)\n\n        async def worker(chunk):\n            async with sem:\n                return await fetch_contacts(chunk[:-1])  # 去掉最后一个ID，保持与原代码一致\n\n        tasks = [worker(chunk) for chunk in chunks]\n        results = await asyncio.gather(*tasks)\n\n        # 合并结果\n        for result in results:\n            info_list.extend(result)\n\n        done_time = datetime.now()\n        logger.info(\"获取通讯录详细信息耗时：{}\", done_time - get_list_time)\n        logger.info(\"获取通讯录信息总耗时：{}\", done_time - start_time)\n\n        clean_info = []\n        for info in info_list:\n            if info.get(\"UserName\", {}).get(\"string\", \"\"):\n                clean_info.append({\n                    \"Wxid\": info.get(\"UserName\", {}).get(\"string\", \"\"),\n                    \"Nickname\": info.get(\"NickName\", {}).get(\"string\", \"\"),\n                    \"Remark\": info.get(\"Remark\", {}).get(\"string\"),\n                    \"Alias\": info.get(\"Alias\", \"\")})\n\n        table = str(tabulate(clean_info, headers=\"keys\", stralign=\"left\"))\n\n        payload = {\"content\": table}\n\n        conn_ssl = aiohttp.TCPConnector(ssl=False)\n        async with aiohttp.request(\"POST\", url=\"https://easychuan.cn/texts\", connector=conn_ssl, json=payload) as req:\n            resp = await req.json()\n        await conn_ssl.close()\n\n        await bot.send_link_message(message[\"FromWxid\"],\n                                    url=f\"https://easychuan.cn/r/{resp['fetch_code']}?t=t\",\n                                    title=\"XYBot登录账号通讯录\",\n                                    description=f\"过期时间：{resp['date_expire']}、耗时：{done_time - start_time}、点击查看详细通讯录信息\", )\n\n        await bot.revoke_message(message[\"FromWxid\"], a, b, c)\n"
  },
  {
    "path": "plugins/GetWeather/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/GetWeather/config.toml",
    "content": "[GetWeather]\nenable = true\ncommand-format = \"\"\"⚙️获取天气：\n天气 城市名\n天气城市名\n城市名天气\n城市名 天气\"\"\"\napi-key = \"\" # 申请链接： https://dev.qweather.com/  项目订阅选免费订阅即可，把获得到的Key (不是Public ID 而是Private KEY) 填到上面引号中"
  },
  {
    "path": "plugins/GetWeather/main.py",
    "content": "import tomllib\n\nimport aiohttp\nimport jieba\n\nfrom WechatAPI import WechatAPIClient\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass GetWeather(PluginBase):\n    description = \"获取天气\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.1\"\n\n    # Change Log\n    # 1.0.1 2025-02-20 修改天气插件触发条件\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/GetWeather/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        config = plugin_config[\"GetWeather\"]\n\n        self.enable = config[\"enable\"]\n        self.command_format = config[\"command-format\"]\n        self.api_key = config[\"api-key\"]\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        if \"天气\" not in message[\"Content\"]:\n            return\n\n        content = str(message[\"Content\"]).replace(\" \", \"\")\n        command = list(jieba.cut(content))\n\n        if len(command) == 1:\n            await bot.send_at_message(message[\"FromWxid\"], \"\\n\" + self.command_format, [message[\"SenderWxid\"]])\n            return\n        elif len(command) > 3:\n            return\n\n        # 配置密钥检查\n        if not self.api_key:\n            await bot.send_at_message(message[\"FromWxid\"], \"\\n你还没配置天气API密钥！\", [message[\"SenderWxid\"]])\n            return\n\n        command.remove(\"天气\")\n        request_loc = \"\".join(command)\n\n        geo_api_url = f'https://geoapi.qweather.com/v2/city/lookup?key={self.api_key}&number=1&location={request_loc}'\n        conn_ssl = aiohttp.TCPConnector(ssl=False)\n        async with aiohttp.request('GET', url=geo_api_url, connector=conn_ssl) as response:\n            geoapi_json = await response.json()\n            await conn_ssl.close()\n\n        if geoapi_json['code'] == '404':\n            await bot.send_at_message(message[\"FromWxid\"], \"\\n⚠️查无此地！\", [message[\"SenderWxid\"]])\n            return\n\n        elif geoapi_json['code'] != '200':\n            await bot.send_at_message(message[\"FromWxid\"], f\"\\n⚠️请求失败\\n{geoapi_json}\", [message[\"SenderWxid\"]])\n            return\n\n        country = geoapi_json[\"location\"][0][\"country\"]\n        adm1 = geoapi_json[\"location\"][0][\"adm1\"]\n        adm2 = geoapi_json[\"location\"][0][\"adm2\"]\n        city_id = geoapi_json[\"location\"][0][\"id\"]\n\n        # 请求现在天气api\n        conn_ssl = aiohttp.TCPConnector(verify_ssl=False)\n        now_weather_api_url = f'https://devapi.qweather.com/v7/weather/now?key={self.api_key}&location={city_id}'\n        async with aiohttp.request('GET', url=now_weather_api_url, connector=conn_ssl) as response:\n            now_weather_api_json = await response.json()\n            await conn_ssl.close()\n\n        # 请求预报天气api\n        conn_ssl = aiohttp.TCPConnector(verify_ssl=False)\n        weather_forecast_api_url = f'https://devapi.qweather.com/v7/weather/7d?key={self.api_key}&location={city_id}'\n        async with aiohttp.request('GET', url=weather_forecast_api_url, connector=conn_ssl) as response:\n            weather_forecast_api_json = await response.json()\n            await conn_ssl.close()\n\n        out_message = self.compose_weather_message(country, adm1, adm2, now_weather_api_json, weather_forecast_api_json)\n        await bot.send_at_message(message[\"FromWxid\"], \"\\n\" + out_message, [message[\"SenderWxid\"]])\n\n    @staticmethod\n    def compose_weather_message(country, adm1, adm2, now_weather_api_json, weather_forecast_api_json):\n        update_time = now_weather_api_json['updateTime']\n        now_temperature = now_weather_api_json['now']['temp']\n        now_feelslike = now_weather_api_json['now']['feelsLike']\n        now_weather = now_weather_api_json['now']['text']\n        now_wind_direction = now_weather_api_json['now']['windDir']\n        now_wind_scale = now_weather_api_json['now']['windScale']\n        now_humidity = now_weather_api_json['now']['humidity']\n        now_precip = now_weather_api_json['now']['precip']\n        now_visibility = now_weather_api_json['now']['vis']\n        now_uvindex = weather_forecast_api_json['daily'][0]['uvIndex']\n\n        message = (\n            f\"----- XYBot -----\\n\"\n            f\"{country}{adm1}{adm2} 实时天气☁️\\n\"\n            f\"⏰更新时间：{update_time}\\n\\n\"\n            f\"🌡️当前温度：{now_temperature}℃\\n\"\n            f\"🌡️体感温度：{now_feelslike}℃\\n\"\n            f\"☁️天气：{now_weather}\\n\"\n            f\"☀️紫外线指数：{now_uvindex}\\n\"\n            f\"🌬️风向：{now_wind_direction}\\n\"\n            f\"🌬️风力：{now_wind_scale}级\\n\"\n            f\"💦湿度：{now_humidity}%\\n\"\n            f\"🌧️降水量：{now_precip}mm/h\\n\"\n            f\"👀能见度：{now_visibility}km\\n\\n\"\n            f\"☁️未来3天 {adm2} 天气：\\n\"\n        )\n        for day in weather_forecast_api_json['daily'][1:4]:\n            date = '.'.join([i.lstrip('0') for i in day['fxDate'].split('-')[1:]])\n            weather = day['textDay']\n            max_temp = day['tempMax']\n            min_temp = day['tempMin']\n            uv_index = day['uvIndex']\n            message += f'{date} {weather} 最高🌡️{max_temp}℃ 最低🌡️{min_temp}℃ ☀️紫外线:{uv_index}\\n'\n\n        return message\n"
  },
  {
    "path": "plugins/Gomoku/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/Gomoku/config.toml",
    "content": "[Gomoku]\nenable = true\ncommand-format = \"\"\"⚙️五子棋游戏指令:\n\n♟️创建游戏:\n五子棋邀请 @用户\n\n👌接受游戏:\n接受 游戏ID\n\n⬇️下棋:\n五子棋下 坐标\n例如: 下棋 C5\"\"\"\n\ntimeout = 60\ncommand = [\"五子棋\"]\ncreate-game-commands = [\"五子棋创建\", \"五子棋邀请\", \"邀请五子棋\"]\naccept-game-commands = [\"接受\", \"加入\"]\nplay-game-commands = [\"下棋\"]"
  },
  {
    "path": "plugins/Gomoku/main.py",
    "content": "import asyncio\nimport base64\nimport tomllib\nfrom random import sample\n\nfrom PIL import Image, ImageDraw\n\nfrom WechatAPI import WechatAPIClient\nfrom database.XYBotDB import XYBotDB\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass Gomoku(PluginBase):\n    description = \"五子棋游戏\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/Gomoku/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        config = plugin_config[\"Gomoku\"]\n\n        self.enable = config[\"enable\"]\n        self.command_format = config[\"command-format\"]\n        self.timeout = config[\"timeout\"]\n\n        self.command = config[\"command\"]\n        self.create_game_commands = config[\"create-game-commands\"]\n        self.accept_game_commands = config[\"accept-game-commands\"]\n        self.play_game_commands = config[\"play-game-commands\"]\n\n        self.db = XYBotDB()\n\n        # 游戏状态存储\n        self.gomoku_games = {}  # 存储所有进行中的游戏\n        self.gomoku_players = {}  # 存储玩家与游戏的对应关系\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip(\" \")\n        command = content.split(\" \")\n\n        if command[0] in self.create_game_commands:\n            await self.create_game(bot, message)\n        elif command[0] in self.accept_game_commands:\n            await self.accept_game(bot, message)\n        elif command[0] in self.play_game_commands:\n            await self.play_game(bot, message)\n        elif command[0] in self.command:  # 当用户只输入\"五子棋\"时显示帮助\n            await bot.send_text_message(message[\"FromWxid\"], f\"-----XYBot-----\\n{self.command_format}\")\n\n    async def create_game(self, bot: WechatAPIClient, message: dict):\n        \"\"\"创建五子棋游戏\"\"\"\n        error = ''\n        room_id = message[\"FromWxid\"]\n        sender = message[\"SenderWxid\"]\n\n        if not message[\"IsGroup\"]:\n            error = '-----XYBot-----\\n❌请在群聊中游玩五子棋'\n        elif sender in self.gomoku_players:\n            error = '-----XYBot-----\\n❌您已经在一场游戏中了！'\n\n        if error:\n            await bot.send_text_message(message[\"FromWxid\"], error)\n            return\n\n        # 获取被邀请者\n        if len(message[\"Ats\"]) != 1:\n            await bot.send_text_message(room_id, '-----XYBot-----\\n❌请@要邀请的玩家！')\n            return\n\n        invitee_wxid = message[\"Ats\"][0]\n        if invitee_wxid in self.gomoku_players:\n            await bot.send_text_message(room_id, '-----XYBot-----\\n❌对方已经在一场游戏中！')\n            return\n\n        # 创建游戏\n        game_id = self._generate_game_id()\n        self.gomoku_players[sender] = game_id\n        self.gomoku_players[invitee_wxid] = game_id\n\n        inviter_nick = await bot.get_nickname(sender)\n\n        # 发送邀请消息\n        out_message = (f\"\\n-----XYBot-----\\n\"\n                       f\"🎉您收到了来自 {inviter_nick} 的五子棋比赛邀请！\\n\"\n                       f\"\\n\"\n                       f\"⚙️请在{self.timeout}秒内发送:\\n\"\n                       f\"接受 {game_id}\")\n        await bot.send_at_message(room_id, out_message, [invitee_wxid])\n\n        # 创建游戏数据\n        self.gomoku_games[game_id] = {\n            'black': sender,\n            'white': invitee_wxid,\n            'board': None,\n            'turn': None,\n            'status': 'inviting',\n            'chatroom': room_id,\n            'timeout_task': asyncio.create_task(\n                self._handle_invite_timeout(bot, game_id, sender, invitee_wxid, room_id)\n            )\n        }\n\n    async def accept_game(self, bot: WechatAPIClient, message: dict):\n        \"\"\"接受五子棋游戏\"\"\"\n        error = ''\n        room_id = message[\"FromWxid\"]\n        sender = message[\"SenderWxid\"]\n\n        if not message[\"IsGroup\"]:\n            error = '-----XYBot-----\\n❌请在群聊中游玩五子棋'\n\n        command = message[\"Content\"].strip().split()\n        if len(command) != 2:\n            error = f'-----XYBot-----\\n❌指令格式错误\\n\\n{self.command_format}'\n\n        if error:\n            await bot.send_text_message(message[\"FromWxid\"], error)\n            return\n\n        game_id = command[1]\n\n        if game_id not in self.gomoku_games:\n            await bot.send_text_message(room_id, '-----XYBot-----\\n❌该游戏不存在！')\n            return\n\n        game = self.gomoku_games[game_id]\n\n        if game['white'] != sender:\n            await bot.send_text_message(room_id, '-----XYBot-----\\n❌您没有被邀请参加该游戏！')\n            return\n\n        if game['status'] != 'inviting':\n            await bot.send_text_message(room_id, '-----XYBot-----\\n❌该游戏已经开始或结束！')\n            return\n\n        if room_id != game['chatroom']:\n            await bot.send_text_message(room_id, '-----XYBot-----\\n❌请在原群聊中接受邀请！')\n            return\n\n        # 取消超时任务\n        game['timeout_task'].cancel()\n\n        # 初始化游戏\n        game['status'] = 'playing'\n        game['board'] = [[0 for _ in range(17)] for _ in range(17)]\n        game['turn'] = game['black']\n\n        # 发送游戏开始信息\n        black_nick = await bot.get_nickname(game['black'])\n        white_nick = await bot.get_nickname(game['white'])\n\n        start_msg = (\n            f\"-----XYBot-----\\n\"\n            f\"🎉五子棋游戏 {game_id} 开始！\\n\"\n            f\"\\n\"\n            f\"⚫️黑方：{black_nick}\\n\"\n            f\"⚪️白方：{white_nick}\\n\"\n            f\"\\n\"\n            f\"⏰每回合限时：{self.timeout}秒\\n\"\n            f\"\\n\"\n            f\"⚫️黑方先手！\\n\"\n            f\"\\n\"\n            f\"⚙️请发送: 下棋 坐标\\n\"\n            f\"例如: 下棋 C5\"\n        )\n        await bot.send_text_message(room_id, start_msg)\n\n        # 发送棋盘\n        board_base64 = self._draw_board(game_id)\n        await bot.send_image_message(room_id, board_base64)\n\n        # 设置回合超时\n        game['timeout_task'] = asyncio.create_task(\n            self._handle_turn_timeout(bot, game_id, game['black'], room_id)\n        )\n\n    async def play_game(self, bot: WechatAPIClient, message: dict):\n        \"\"\"处理下棋操作\"\"\"\n        error = ''\n        room_id = message[\"FromWxid\"]\n        sender = message[\"SenderWxid\"]\n\n        if not message[\"IsGroup\"]:\n            error = '-----XYBot-----\\n❌请在群聊中游玩五子棋'\n\n        command = message[\"Content\"].strip().split()\n        if len(command) != 2:\n            error = f'-----XYBot-----\\n❌指令格式错误\\n\\n{self.command_format}'\n\n        if error:\n            await bot.send_text_message(message[\"FromWxid\"], error)\n            return\n\n        if sender not in self.gomoku_players:\n            await bot.send_text_message(room_id, '-----XYBot-----\\n❌您不在任何游戏中！')\n            return\n\n        game_id = self.gomoku_players[sender]\n        game = self.gomoku_games[game_id]\n\n        if game['status'] != 'playing':\n            await bot.send_text_message(room_id, '-----XYBot-----\\n❌游戏已经结束！')\n            return\n\n        if sender != game['turn']:\n            await bot.send_text_message(room_id, '-----XYBot-----\\n❌还没到您的回合！')\n            return\n\n        # 解析坐标\n        coord = command[1].upper()\n        if not (len(coord) >= 2 and coord[0] in 'ABCDEFGHIJKLMNOPQ' and coord[1:].isdigit()):\n            await bot.send_text_message(room_id, '-----XYBot-----\\n❌无效的坐标格式！')\n            return\n\n        x = ord(coord[0]) - ord('A')\n        y = 16 - int(coord[1:])\n\n        if not (0 <= x <= 16 and 0 <= y <= 16):\n            await bot.send_text_message(room_id, '-----XYBot-----\\n❌坐标超出范围！')\n            return\n\n        if game['board'][y][x] != 0:\n            await bot.send_text_message(room_id, '-----XYBot-----\\n❌该位置已有棋子！')\n            return\n\n        # 取消超时任务\n        game['timeout_task'].cancel()\n\n        # 落子\n        game['board'][y][x] = 1 if sender == game['black'] else 2\n\n        # 绘制并发送新棋盘\n        board_base64 = self._draw_board(game_id, highlight=(x, y))\n        await bot.send_image_message(room_id, board_base64)\n\n        # 检查是否获胜\n        winner = self._check_winner(game_id)\n        if winner:\n            if winner == 'draw':\n                await bot.send_text_message(room_id, f'-----XYBot-----\\n🎉五子棋游戏 {game_id} 结束！\\n\\n平局！⚖️')\n            else:\n                winner_wxid = game['black'] if winner == 'black' else game['white']\n                winner_nick = await bot.get_nickname(winner_wxid)\n                await bot.send_text_message(\n                    room_id,\n                    f'-----XYBot-----\\n🎉五子棋游戏 {game_id} 结束！\\n\\n'\n                    f'{\"⚫️黑方\" if winner == \"black\" else \"⚪️白方\"}：{winner_nick} 获胜！🏆'\n                )\n\n            # 清理游戏数据\n            self.gomoku_players.pop(game['black'])\n            self.gomoku_players.pop(game['white'])\n            self.gomoku_games.pop(game_id)\n            return\n\n        # 切换回合\n        game['turn'] = game['white'] if sender == game['black'] else game['black']\n\n        # 发送回合信息\n        current_nick = await bot.get_nickname(sender)\n        next_nick = await bot.get_nickname(game['turn'])\n        current_color = '⚫️' if sender == game['black'] else '⚪️'\n        next_color = '⚫️' if game['turn'] == game['black'] else '⚪️'\n\n        turn_msg = (\n            f\"-----XYBot-----\\n\"\n            f\"{current_color}{current_nick} 把棋子落在了 {coord}！\\n\"\n            f\"轮到 {next_color}{next_nick} 下子了！\\n\"\n            f\"\\n\"\n            f\"⏰限时：{self.timeout}秒\\n\"\n            f\"\\n\"\n            f\"⚙️请发送: 下棋 坐标\\n\"\n            f\"例如: 下棋 C5\"\n        )\n        await bot.send_text_message(room_id, turn_msg)\n\n        # 设置新的回合超时\n        game['timeout_task'] = asyncio.create_task(\n            self._handle_turn_timeout(bot, game_id, game['turn'], room_id)\n        )\n\n    def _generate_game_id(self) -> str:\n        \"\"\"生成游戏ID\"\"\"\n        chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'\n        while True:\n            game_id = ''.join(sample(chars, 6))\n            if game_id not in self.gomoku_games:\n                return game_id\n\n    def _draw_board(self, game_id: str, highlight: tuple = None) -> str:\n        \"\"\"绘制棋盘并返回base64编码\"\"\"\n        board_img = Image.open('resource/images/gomoku_board_original.png')\n        draw = ImageDraw.Draw(board_img)\n\n        board = self.gomoku_games[game_id]['board']\n\n        # 绘制棋子\n        for y in range(17):\n            for x in range(17):\n                if board[y][x] != 0:\n                    color = 'black' if board[y][x] == 1 else 'white'\n                    draw.ellipse(\n                        (24 + x * 27 - 8, 24 + y * 27 - 8,\n                         24 + x * 27 + 8, 24 + y * 27 + 8),\n                        fill=color\n                    )\n\n        # 绘制高亮\n        if highlight:\n            x, y = highlight\n            draw.ellipse(\n                (24 + x * 27 - 8, 24 + y * 27 - 8,\n                 24 + x * 27 + 8, 24 + y * 27 + 8),\n                outline='red',\n                width=2\n            )\n\n        # 转换为bytes\n        from io import BytesIO\n        img_byte_arr = BytesIO()\n        board_img.save(img_byte_arr, format='PNG')\n        img_byte_arr = img_byte_arr.getvalue()\n\n        # 转换为base64\n        return base64.b64encode(img_byte_arr).decode()\n\n    def _check_winner(self, game_id: str) -> str:\n        \"\"\"检查是否有获胜者\"\"\"\n        board = self.gomoku_games[game_id]['board']\n\n        # 检查所有方向\n        directions = [(0, 1), (1, 0), (1, 1), (1, -1)]\n\n        for y in range(17):\n            for x in range(17):\n                if board[y][x] == 0:\n                    continue\n\n                for dx, dy in directions:\n                    count = 1\n                    nx, ny = x + dx, y + dy\n\n                    while (0 <= nx < 17 and 0 <= ny < 17 and\n                           board[ny][nx] == board[y][x]):\n                        count += 1\n                        nx += dx\n                        ny += dy\n\n                    if count >= 5:\n                        return 'black' if board[y][x] == 1 else 'white'\n\n        # 检查平局\n        if all(board[y][x] != 0 for y in range(17) for x in range(17)):\n            return 'draw'\n\n        return ''\n\n    async def _handle_invite_timeout(self, bot: WechatAPIClient, game_id: str,\n                                     inviter: str, invitee: str, room_id: str):\n        \"\"\"处理邀请超时\"\"\"\n        await asyncio.sleep(self.timeout)\n\n        if (game_id in self.gomoku_games and\n                self.gomoku_games[game_id]['status'] == 'inviting'):\n            # 清理游戏数据\n            self.gomoku_players.pop(inviter)\n            self.gomoku_players.pop(invitee)\n            self.gomoku_games.pop(game_id)\n\n            await bot.send_at_message(\n                room_id,\n                f'-----XYBot-----\\n❌五子棋游戏 {game_id} 邀请超时！',\n                [inviter]\n            )\n\n    async def _handle_turn_timeout(self, bot: WechatAPIClient, game_id: str,\n                                   player: str, room_id: str):\n        \"\"\"处理回合超时\"\"\"\n        await asyncio.sleep(self.timeout)\n\n        if (game_id in self.gomoku_games and\n                self.gomoku_games[game_id]['status'] == 'playing' and\n                self.gomoku_games[game_id]['turn'] == player):\n            game = self.gomoku_games[game_id]\n            winner = game['white'] if player == game['black'] else game['black']\n\n            # 清理游戏数据\n            self.gomoku_players.pop(game['black'])\n            self.gomoku_players.pop(game['white'])\n            self.gomoku_games.pop(game_id)\n\n            loser_nick = await bot.get_nickname(player)\n            winner_nick = await bot.get_nickname(winner)\n\n            await bot.send_text_message(\n                room_id,\n                f'-----XYBot-----\\n'\n                f'{loser_nick} 落子超时！\\n'\n                f'🏆 {winner_nick} 获胜！'\n            )\n"
  },
  {
    "path": "plugins/GoodMorning/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/GoodMorning/config.toml",
    "content": "[GoodMorning]\nenable = true"
  },
  {
    "path": "plugins/GoodMorning/main.py",
    "content": "import asyncio\nimport tomllib\nfrom datetime import datetime\nfrom random import randint\n\nimport aiohttp\n\nfrom WechatAPI import WechatAPIClient\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass GoodMorning(PluginBase):\n    description = \"早上好插件\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.1\"\n\n    # Change Log\n    # 1.0.1 fix ssl issue, add timeout\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/GoodMorning/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        config = plugin_config[\"GoodMorning\"]\n\n        self.enable = config[\"enable\"]\n\n    @schedule('cron', hour=8, minute=30)\n    async def daily_task(self, bot: WechatAPIClient):\n        if not self.enable:\n            return\n\n        id_list = []\n        wx_seq, chatroom_seq = 0, 0\n        while True:\n            contact_list = await bot.get_contract_list(wx_seq, chatroom_seq)\n            id_list.extend(contact_list[\"ContactUsernameList\"])\n            wx_seq = contact_list[\"CurrentWxcontactSeq\"]\n            chatroom_seq = contact_list[\"CurrentChatRoomContactSeq\"]\n            if contact_list[\"CountinueFlag\"] != 1:\n                break\n\n        chatrooms = []\n        for id in id_list:\n            if id.endswith(\"@chatroom\"):\n                chatrooms.append(id)\n        try:\n            async with aiohttp.request(\"GET\", \"http://zj.v.api.aa1.cn/api/bk/?num=1&type=json\",\n                                       timeout=aiohttp.ClientTimeout(total=20)) as req:\n                resp = await req.json()\n                history_today = \"无\"\n                if resp.get(\"content\"):\n                    history_today = str(resp.get(\"content\")[0])\n        except asyncio.TimeoutError:\n            history_today = \"🛜网络超时😭\"\n\n        weekend = [\"一\", \"二\", \"三\", \"四\", \"五\", \"六\", \"日\"]\n        message = (\"----- XYBot -----\\n\"\n                   f\"[Sun]早上好！今天是 {datetime.now().strftime('%Y年%m月%d号')}，星期{weekend[datetime.now().weekday()]}。\\n\"\n                   \"\\n\"\n                   \"📖历史上的今天：\\n\"\n                   f\"{history_today}\")\n\n        for chatroom in chatrooms:\n            await bot.send_text_message(chatroom, message)\n            await asyncio.sleep(randint(1, 5))\n"
  },
  {
    "path": "plugins/GroupWelcome/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/GroupWelcome/config.toml",
    "content": "[GroupWelcome]\nenable = true\nwelcome-message = \"👆点我查看XYBot文档！\"\nurl = \"https://henryxiaoyang.github.io/XYBotV2\""
  },
  {
    "path": "plugins/GroupWelcome/main.py",
    "content": "import tomllib\nimport xml.etree.ElementTree as ET\nfrom datetime import datetime\n\nfrom loguru import logger\n\nfrom WechatAPI import WechatAPIClient\nfrom utils.decorators import on_system_message\nfrom utils.plugin_base import PluginBase\n\n\nclass GroupWelcome(PluginBase):\n    description = \"进群欢迎\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/GroupWelcome/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        config = plugin_config[\"GroupWelcome\"]\n\n        self.enable = config[\"enable\"]\n        self.welcome_message = config[\"welcome-message\"]\n        self.url = config[\"url\"]\n\n    @on_system_message\n    async def group_welcome(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        if not message[\"IsGroup\"]:\n            return\n\n        xml_content = str(message[\"Content\"]).strip().replace(\"\\n\", \"\").replace(\"\\t\", \"\")\n        root = ET.fromstring(xml_content)\n\n        if root.tag != \"sysmsg\":\n            return\n\n        # 检查是否是进群消息\n        if root.attrib.get(\"type\") == \"sysmsgtemplate\":\n            sys_msg_template = root.find(\"sysmsgtemplate\")\n            if sys_msg_template is None:\n                return\n\n            template = sys_msg_template.find(\"content_template\")\n            if template is None:\n                return\n\n            template_type = template.attrib.get(\"type\")\n            if template_type not in [\"tmpl_type_profile\", \"tmpl_type_profilewithrevoke\"]:\n                return\n\n            template_text = template.find(\"template\").text\n\n            if '\"$names$\"加入了群聊' in template_text:  # 直接加入群聊\n                new_members = self._parse_member_info(root, \"names\")\n            elif '\"$username$\"邀请\"$names$\"加入了群聊' in template_text:  # 通过邀请加入群聊\n                new_members = self._parse_member_info(root, \"names\")\n            elif '你邀请\"$names$\"加入了群聊' in template_text:  # 自己邀请成员加入群聊\n                new_members = self._parse_member_info(root, \"names\")\n            elif '\"$adder$\"通过扫描\"$from$\"分享的二维码加入群聊' in template_text:  # 通过二维码加入群聊\n                new_members = self._parse_member_info(root, \"adder\")\n            elif '\"$adder$\"通过\"$from$\"的邀请二维码加入群聊' in template_text:\n                new_members = self._parse_member_info(root, \"adder\")\n            else:\n                logger.warning(f\"未知的入群方式: {template_text}\")\n                return\n\n            if not new_members:\n                return\n\n            for member in new_members:\n                wxid = member[\"wxid\"]\n                nickname = member[\"nickname\"]\n\n                now = datetime.now().strftime(\"%Y-%m-%d %H:%M:%S\")\n\n                profile = await bot.get_contact(wxid)\n\n                await bot.send_link_message(message[\"FromWxid\"],\n                                            title=f\"👏欢迎 {nickname} 加入群聊！🎉\",\n                                            description=f\"⌚时间：{now}\\n{self.welcome_message}\",\n                                            url=self.url,\n                                            thumb_url=profile.get(\"BigHeadImgUrl\", \"\")\n                                            )\n\n    @staticmethod\n    def _parse_member_info(root: ET.Element, link_name: str = \"names\") -> list[dict]:\n        \"\"\"解析新成员信息\"\"\"\n        new_members = []\n        try:\n            # 查找指定链接中的成员列表\n            names_link = root.find(f\".//link[@name='{link_name}']\")\n            if names_link is None:\n                return new_members\n\n            memberlist = names_link.find(\"memberlist\")\n\n            if memberlist is None:\n                return new_members\n\n            for member in memberlist.findall(\"member\"):\n                username = member.find(\"username\").text\n                nickname = member.find(\"nickname\").text\n                new_members.append({\n                    \"wxid\": username,\n                    \"nickname\": nickname\n                })\n\n        except Exception as e:\n            logger.warning(f\"解析新成员信息失败: {e}\")\n\n        return new_members\n"
  },
  {
    "path": "plugins/Leaderboard/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/Leaderboard/config.toml",
    "content": "[Leaderboard]\nenable = true\ncommand = [\"排行榜\", \"积分榜\", \"积分排行榜\", \"群排行榜\", \"群积分榜\"]\nmax-count = 30"
  },
  {
    "path": "plugins/Leaderboard/main.py",
    "content": "import asyncio\nimport tomllib\nfrom random import choice\n\nfrom WechatAPI import WechatAPIClient\nfrom database.XYBotDB import XYBotDB\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass Leaderboard(PluginBase):\n    description = \"积分榜\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/Leaderboard/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        config = plugin_config[\"Leaderboard\"]\n\n        self.enable = config[\"enable\"]\n        self.command = config[\"command\"]\n        self.max_count = config[\"max-count\"]\n\n        self.db = XYBotDB()\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = content.split(\" \")\n\n        if command[0] not in self.command:\n            return\n\n        if \"群\" in command[0]:\n            chatroom_members = await bot.get_chatroom_member_list(message[\"FromWxid\"])\n            data = []\n            for member in chatroom_members:\n                wxid = member[\"UserName\"]\n                points = self.db.get_points(wxid)\n                if points == 0:\n                    continue\n                data.append((member[\"NickName\"], points))\n\n            data.sort(key=lambda x: x[1], reverse=True)\n            data = data[:self.max_count]\n\n            out_message = \"-----XYBot积分群排行榜-----\"\n            rank_emojis = [\"👑\", \"🥈\", \"🥉\"]\n            for rank, (nickname, points) in enumerate(data, start=1):\n                emoji = rank_emojis[rank - 1] if rank <= 3 else \"\"\n                random_emoji = choice(\n                    [\"😄\", \"😃\", \"😁\", \"😆\", \"😊\", \"😍\", \"😋\", \"😎\", \"🤗\", \"😺\", \"🥳\", \"🤩\", \"🎉\", \"⭐\", \"🎊\", \"🎈\", \"🌟\", \"✨\", \"🎶\",\n                     \"❤️\", \"😛\"])\n                out_message += f\"\\n{emoji}{'' if emoji else str(rank) + '.'} {nickname}   {points}分  {random_emoji}\"\n\n        else:\n            data = self.db.get_leaderboard(self.max_count)\n\n            wxids = [i[0] for i in data]\n            nicknames = []\n\n            async def get_nicknames_chunk(chunk_wxids):\n                return await bot.get_nickname(chunk_wxids)\n\n            # 将wxids分成每组20个\n            chunks = [wxids[i:i + 20] for i in range(0, len(wxids), 20)]\n            # 使用信号量限制并发数为2\n            sem = asyncio.Semaphore(2)\n\n            async def worker(chunk):\n                async with sem:\n                    return await get_nicknames_chunk(chunk)\n\n            # 并发执行所有请求\n            tasks = [worker(chunk) for chunk in chunks]\n            results = await asyncio.gather(*tasks)\n\n            # 将所有结果合并到nicknames列表中\n            for result in results:\n                nicknames.extend(result)\n\n            out_message = \"-----XYBot积分排行榜-----\"\n            rank_emojis = [\"👑\", \"🥈\", \"🥉\"]\n            for rank, (i, nickname) in enumerate(zip(data, nicknames), start=1):\n                wxid, points = i\n                nickname = nickname or wxid\n                emoji = rank_emojis[rank - 1] if rank <= 3 else \"\"\n                random_emoji = choice(\n                    [\"😄\", \"😃\", \"😁\", \"😆\", \"😊\", \"😍\", \"😋\", \"😎\", \"🤗\", \"😺\", \"🥳\", \"🤩\", \"🎉\", \"⭐\", \"🎊\", \"🎈\", \"🌟\", \"✨\", \"🎶\",\n                     \"❤️\", \"😛\"])\n                out_message += f\"\\n{emoji}{'' if emoji else str(rank) + '.'} {nickname}   {points}分  {random_emoji}\"\n\n        await bot.send_text_message(message[\"FromWxid\"], out_message)\n"
  },
  {
    "path": "plugins/LuckyDraw/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/LuckyDraw/config.toml",
    "content": "[LuckyDraw]\nenable = true\ncommand = [\"抽奖\", \"幸运抽奖\", \"抽奖活动\", \"抽奖指令\", \"幸运大抽奖\", \"大抽奖\", \"积分抽奖\"]\ncommand-format = \"\"\"\n-----XYBot-----\n🎉抽奖指令：\n抽奖 奖池名 次数\n次数可选\n\n🎁奖池列表：\n小抽奖指令：抽奖 小\n中抽奖指令：抽奖 中\n大抽奖指令：抽奖 大\n\"\"\"\nmax-draw = 100\ndraw-per-guarantee = 10\nguaranteed-max-probability = 0.5\n\n[LuckyDraw.probabilities.small]\nname = \"小\"\ncost = 20\n[LuckyDraw.probabilities.small.probability]\n\"0.05\" = { name = \"金\", points = 40, symbol = \"🟨\" }\n\"0.10\" = { name = \"紫\", points = 35, symbol = \"🟪\" }\n\"0.20\" = { name = \"蓝\", points = 21, symbol = \"🟦\" }\n\"0.30\" = { name = \"绿\", points = 15, symbol = \"🟩\" }\n\"0.35\" = { name = \"白\", points = 10, symbol = \"⬜️\" }\n\n[LuckyDraw.probabilities.medium]\nname = \"中\"\ncost = 40\n[LuckyDraw.probabilities.medium.probability]\n\"0.05\" = { name = \"金\", points = 70, symbol = \"🟨\" }\n\"0.10\" = { name = \"紫\", points = 55, symbol = \"🟪\" }\n\"0.20\" = { name = \"蓝\", points = 41, symbol = \"🟦\" }\n\"0.30\" = { name = \"绿\", points = 35, symbol = \"🟩\" }\n\"0.35\" = { name = \"白\", points = 25, symbol = \"⬜️\" }\n\n[LuckyDraw.probabilities.large]\nname = \"大\"\ncost = 80\n[LuckyDraw.probabilities.large.probability]\n\"0.01\" = { name = \"红\", points = 170, symbol = \"🟥\" }\n\"0.05\" = { name = \"金\", points = 120, symbol = \"🟨\" }\n\"0.10\" = { name = \"紫\", points = 90, symbol = \"🟪\" }\n\"0.20\" = { name = \"蓝\", points = 81, symbol = \"🟦\" }\n\"0.30\" = { name = \"绿\", points = 75, symbol = \"🟩\" }\n\"0.34\" = { name = \"白\", points = 65, symbol = \"⬜️\" }"
  },
  {
    "path": "plugins/LuckyDraw/main.py",
    "content": "import random\nimport tomllib\n\nfrom loguru import logger\n\nfrom WechatAPI import WechatAPIClient\nfrom database.XYBotDB import XYBotDB\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass LuckyDraw(PluginBase):\n    description = \"幸运抽奖\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/LuckyDraw/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        config = plugin_config[\"LuckyDraw\"]\n\n        self.enable = config[\"enable\"]\n        self.command = config[\"command\"]\n        self.command_format = config[\"command-format\"]\n\n        probabilities = config[\"probabilities\"]\n        self.probabilities = {}\n        for item in probabilities.values():\n            name = item[\"name\"]\n            cost = item[\"cost\"]\n            probability = item[\"probability\"]\n            self.probabilities[name] = {\"cost\": cost, \"probability\": probability}\n\n        self.max_draw = config[\"max-draw\"]\n        self.draw_per_guarantee = config[\"draw-per-guarantee\"]\n        self.guaranteed_max_probability = config[\"guaranteed-max-probability\"]\n\n        self.db = XYBotDB()\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = content.split(\" \")\n\n        if not len(command) or command[0] not in self.command:\n            return\n\n        target_wxid = message[\"SenderWxid\"]\n        target_points = self.db.get_points(target_wxid)\n\n        if len(command) < 2:\n            await bot.send_at_message(message[\"FromWxid\"], self.command_format, [target_wxid])\n            return\n\n        draw_name = command[1]\n        if draw_name not in self.probabilities.keys():\n            await bot.send_text_message(message[\"FromWxid\"], \"-----XYBot-----\\n🤔你指定的奖池无效哦！\")\n            return\n\n        draw_count = 1\n        if len(command) == 3 and command[2].isdigit():\n            draw_count = int(command[2])\n\n        if draw_count > self.max_draw:\n            await bot.send_text_message(message[\"FromWxid\"], f\"-----XYBot-----\\n😔你最多只能抽{self.max_draw}次哦！\")\n            return\n\n        if target_points < self.probabilities[draw_name][\"cost\"] * draw_count:\n            await bot.send_text_message(message[\"FromWxid\"],\n                                        f\"-----XYBot-----\\n😭你积分不足以你抽{draw_count}次{draw_name}抽奖哦！\")\n            return\n\n        draw_probability = self.probabilities[draw_name][\"probability\"]\n        cost = self.probabilities[draw_name][\"cost\"] * draw_count\n\n        self.db.add_points(target_wxid, -cost)\n\n        wins = []\n\n        # 保底抽奖\n        min_guaranteed = draw_count // self.draw_per_guarantee  # 保底抽奖次数\n        for _ in range(min_guaranteed):  # 先把保底抽了\n            random_num = random.uniform(0, self.guaranteed_max_probability)\n            cumulative_probability = 0\n            for p, prize in draw_probability.items():\n                cumulative_probability += float(p)\n                if random_num <= cumulative_probability:\n                    win_name = prize[\"name\"]\n                    win_points = prize[\"points\"]\n                    win_symbol = prize[\"symbol\"]\n\n                    wins.append(\n                        (win_name, win_points, win_symbol)\n                    )  # 把结果加入赢取列表\n                    break\n\n            # 正常抽奖\n        for _ in range(draw_count - min_guaranteed):  # 把剩下的抽了\n            random_num = random.uniform(0, 1)\n            cumulative_probability = 0\n            for p, prize in draw_probability.items():\n                cumulative_probability += float(p)\n                if random_num <= cumulative_probability:\n                    win_name = prize[\"name\"]\n                    win_points = prize[\"points\"]\n                    win_symbol = prize[\"symbol\"]\n\n                    wins.append(\n                        (win_name, win_points, win_symbol)\n                    )  # 把结果加入赢取列表\n                    break\n\n        total_win_points = 0\n        for win_name, win_points, win_symbol in wins:  # 统计赢取的积分\n            total_win_points += win_points\n\n        self.db.add_points(target_wxid, total_win_points)  # 把赢取的积分加入数据库\n        logger.info(f\"用户 {target_wxid} 在 {draw_name} 抽了 {draw_count}次 赢取了{total_win_points}积分\")\n        output = self.make_message(wins, draw_name, draw_count, total_win_points, cost)\n        await bot.send_at_message(message[\"FromWxid\"], output, [target_wxid])\n\n    @staticmethod\n    def make_message(\n            wins, draw_name, draw_count, total_win_points, draw_cost\n    ):  # 组建信息\n        name_max_len = 0\n        for win_name, win_points, win_symbol in wins:\n            if len(win_name) > name_max_len:\n                name_max_len = len(win_name)\n\n        begin_message = f\"\\n----XYBot抽奖----\\n🥳恭喜你在 {draw_count}次 {draw_name}抽奖 中抽到了：\\n\\n\"\n        lines = []\n        for _ in range(name_max_len + 2):\n            lines.append(\"\")\n\n        begin_line = 0\n\n        one_line_length = 0\n\n        for win_name, win_points, win_symbol in wins:\n            if one_line_length >= 10:  # 每行10个结果，以免在微信上格式错误\n                begin_line += name_max_len + 2\n                for _ in range(name_max_len + 2):\n                    lines.append(\"\")  # 占个位\n                one_line_length = 0\n\n            lines[begin_line] += win_symbol\n            for i in range(begin_line + 1, begin_line + name_max_len + 1):\n                if i % (name_max_len + 2) <= len(win_name):\n                    lines[i] += (\n                            \"\\u2004\" + win_name[i % (name_max_len + 2) - 1]\n                    )  # \\u2004 这个空格最好 试过了很多种空格\n                else:\n                    lines[i] += win_symbol\n            lines[begin_line + name_max_len + 1] += win_symbol\n\n            one_line_length += 1\n\n        message = begin_message\n        for line in lines:\n            message += line + \"\\n\"\n\n        message += f\"\\n\\n🎉总计赢取积分: {total_win_points}🎉\\n🎉共计消耗积分：{draw_cost}🎉\\n\\n概率请自行查询菜单⚙️\"\n\n        return message\n"
  },
  {
    "path": "plugins/ManagePlugin/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/ManagePlugin/config.toml",
    "content": "[ManagePlugin]\ncommand = [\"加载插件\", \"加载所有插件\", \"卸载插件\", \"卸载所有插件\", \"重载插件\", \"重载所有插件\", \"插件列表\", \"插件信息\"]\n"
  },
  {
    "path": "plugins/ManagePlugin/main.py",
    "content": "import tomllib\n\nfrom tabulate import tabulate\n\nfrom WechatAPI import WechatAPIClient\nfrom database.XYBotDB import XYBotDB\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\nfrom utils.plugin_manager import PluginManager\n\n\nclass ManagePlugin(PluginBase):\n    description = \"插件管理器\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        self.db = XYBotDB()\n\n        with open(\"plugins/ManagePlugin/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        with open(\"main_config.toml\", \"rb\") as f:\n            main_config = tomllib.load(f)\n\n        plugin_config = plugin_config[\"ManagePlugin\"]\n        main_config = main_config[\"XYBot\"]\n\n        self.command = plugin_config[\"command\"]\n        self.admins = main_config[\"admins\"]\n\n        self.plugin_manager = PluginManager()\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        content = str(message[\"Content\"]).strip()\n        command = content.split(\" \")\n\n        if not len(command) or command[0] not in self.command:\n            return\n\n        if message[\"SenderWxid\"] not in self.admins:\n            await bot.send_text_message(message[\"FromWxid\"], \"你没有权限使用此命令\")\n            return\n\n        plugin_name = command[1] if len(command) > 1 else None\n        if command[0] == \"加载插件\":\n            if plugin_name in self.plugin_manager.plugins.keys():\n                await bot.send_text_message(message[\"FromWxid\"], \"⚠️插件已经加载\")\n                return\n\n            attempt = await self.plugin_manager.load_plugin(plugin_name)\n            if attempt:\n                await bot.send_text_message(message[\"FromWxid\"], f\"✅插件 {plugin_name} 加载成功\")\n            else:\n                await bot.send_text_message(message[\"FromWxid\"], f\"❌插件 {plugin_name} 加载失败，请查看日志错误信息\")\n\n        elif command[0] == \"加载所有插件\":\n            attempt = await self.plugin_manager.load_plugins(load_disabled=True)\n            if isinstance(attempt, list):\n                attempt = '\\n'.join(attempt)\n                await bot.send_text_message(message[\"FromWxid\"], f\"✅插件加载成功：\\n{attempt}\")\n            else:\n                await bot.send_text_message(message[\"FromWxid\"], \"❌插件加载失败，请查看日志错误信息\")\n\n        elif command[0] == \"卸载插件\":\n            if plugin_name == \"ManagePlugin\":\n                await bot.send_text_message(message[\"FromWxid\"], \"⚠️你不能卸载 ManagePlugin 插件！\")\n                return\n            elif plugin_name not in self.plugin_manager.plugins.keys():\n                await bot.send_text_message(message[\"FromWxid\"], \"⚠️插件不存在或未加载\")\n                return\n\n            attempt = await self.plugin_manager.unload_plugin(plugin_name)\n            if attempt:\n                await bot.send_text_message(message[\"FromWxid\"], f\"✅插件 {plugin_name} 卸载成功\")\n            else:\n                await bot.send_text_message(message[\"FromWxid\"], f\"❌插件 {plugin_name} 卸载失败，请查看日志错误信息\")\n\n        elif command[0] == \"卸载所有插件\":\n            unloaded_plugins, failed_unloads = await self.plugin_manager.unload_plugins()\n            unloaded_plugins = '\\n'.join(unloaded_plugins)\n            failed_unloads = '\\n'.join(failed_unloads)\n            await bot.send_text_message(message[\"FromWxid\"],\n                                        f\"✅插件卸载成功：\\n{unloaded_plugins}\\n❌插件卸载失败：\\n{failed_unloads}\")\n\n        elif command[0] == \"重载插件\":\n            if plugin_name == \"ManagePlugin\":\n                await bot.send_text_message(message[\"FromWxid\"], \"⚠️你不能重载 ManagePlugin 插件！\")\n                return\n            elif plugin_name not in self.plugin_manager.plugins.keys():\n                await bot.send_text_message(message[\"FromWxid\"], \"⚠️插件不存在或未加载\")\n                return\n\n            attempt = await self.plugin_manager.reload_plugin(plugin_name)\n            if attempt:\n                await bot.send_text_message(message[\"FromWxid\"], f\"✅插件 {plugin_name} 重载成功\")\n            else:\n                await bot.send_text_message(message[\"FromWxid\"], f\"❌插件 {plugin_name} 重载失败，请查看日志错误信息\")\n\n        elif command[0] == \"重载所有插件\":\n            attempt = await self.plugin_manager.reload_plugins()\n            if attempt:\n                await bot.send_text_message(message[\"FromWxid\"], \"✅所有插件重载成功\")\n            else:\n                await bot.send_text_message(message[\"FromWxid\"], \"❌插件重载失败，请查看日志错误信息\")\n\n        elif command[0] == \"插件列表\":\n            plugin_list = self.plugin_manager.get_plugin_info()\n\n            plugin_stat = [[\"插件名称\", \"是否启用\"]]\n            for plugin in plugin_list:\n                plugin_stat.append([plugin['name'], \"✅\" if plugin['enabled'] else \"🚫\"])\n\n            table = str(tabulate(plugin_stat, headers=\"firstrow\", tablefmt=\"simple\"))\n\n            await bot.send_text_message(message[\"FromWxid\"], table)\n\n        elif command[0] == \"插件信息\":\n            attemt = self.plugin_manager.get_plugin_info(plugin_name)\n            if isinstance(attemt, dict):\n                output = (f\"插件名称: {attemt['name']}\\n\"\n                          f\"插件描述: {attemt['description']}\\n\"\n                          f\"插件作者: {attemt['author']}\\n\"\n                          f\"插件版本: {attemt['version']}\")\n\n                await bot.send_text_message(message[\"FromWxid\"], output)\n            else:\n                await bot.send_text_message(message[\"FromWxid\"], \"⚠️插件不存在或未加载\")\n"
  },
  {
    "path": "plugins/Menu/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/Menu/config.toml",
    "content": "[Menu]\nenable = true\ncommand = [\"菜单\", \"帮助\", \"帮助菜单\", \"功能列表\", \"功能菜单\", \"指令列表\", \"指令菜单\", \"功能\", \"指令\", \"cd\", \"Cd\", \"cd\", \"menu\", \"Menu\"]\nmenu = \"\"\"-======== XYBot ========-\n🤖AI聊天🤖 (可指令可群@可私信语音可私信图片)\n☁️天气☁️          🎵点歌🎵\n📰新闻📰           ♟️五子棋♟️\n📰随机新闻📰    🏞️随机图片🏞️\n🔢随机群员🔢    🎮战雷查询🎮\n\n✅签到✅           💰积分查询💰\n🏆积分榜🏆       🏆群积分榜🏆\n🤝积分交易🤝   🤑积分抽奖🤑\n🧧积分红包🧧\n\n⚙️查看管理员菜单请发送：管理员菜单\"\"\"\n\nadmin-menu = \"\"\"-----XYBot-----\n⚙️管理积分：\n➕积分：\n加积分 积分 wxid/@用户\n➖积分：\n减积分 积分 wxid/@用户\n🔢设置积分：\n设置积分 积分 wxid/@用户\n\n⚙️管理白名单\n➕白名单：\n添加白名单 wxid/@用户\n➖白名单：\n删除白名单 wxid/@用户\n📋白名单列表：\n白名单列表\n\n⚙️重置签到状态：\n重置签到\n\n‼️‼️管理插件‼️‼️\n⚙️加载插件：\n加载插件 插件名\n⚙️加载所有插件：\n加载所有插件\n\n⚙️卸载插件：\n卸载插件 插件名\n⚙️卸载所有插件：\n卸载所有插件\n\n⚙️重载插件：\n重载插件 插件名\n⚙️重载所有插件：\n重载所有插件\n\n⚙️插件列表：\n插件列表\"\"\""
  },
  {
    "path": "plugins/Menu/main.py",
    "content": "import tomllib\n\nfrom WechatAPI import WechatAPIClient\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass Menu(PluginBase):\n    description = \"菜单\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/Menu/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        with open(\"main_config.toml\", \"rb\") as f:\n            main_config = tomllib.load(f)\n\n        config = plugin_config[\"Menu\"]\n        main_config = main_config[\"XYBot\"]\n\n        self.enable = config[\"enable\"]\n        self.command = config[\"command\"]\n        self.menu = config[\"menu\"]\n        self.admin_menu = config[\"admin-menu\"]\n\n        self.version = main_config[\"version\"]\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = content.split(\" \")\n\n        if command[0] in self.command:\n            menu = (f\"\\n\"\n                    f\"{self.menu}\\n\"\n                    f\"\\n\"\n                    f\"来自XYBotV2 {self.version}\\n\"\n                    f\"https://github.com/HenryXiaoYang/XYBotV2\")\n            await bot.send_at_message(message[\"FromWxid\"], menu, [message[\"SenderWxid\"]])\n        elif command[0] == \"管理员菜单\":\n            await bot.send_at_message(message[\"FromWxid\"], self.admin_menu, [message[\"SenderWxid\"]])\n"
  },
  {
    "path": "plugins/Music/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/Music/config.toml",
    "content": "[Music]\nenable = true\ncommand = [\"点歌\", \"音乐\", \"音乐点播\", \"点播音乐\", \"音乐点歌\"]\ncommand-format = \"\"\"\n-----XYBot-----\n🎵点歌指令：\n点歌 歌曲名\n\"\"\""
  },
  {
    "path": "plugins/Music/main.py",
    "content": "import tomllib\n\nimport aiohttp\n\nfrom WechatAPI import WechatAPIClient\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass Music(PluginBase):\n    description = \"点歌\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/Music/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        config = plugin_config[\"Music\"]\n\n        self.enable = config[\"enable\"]\n        self.command = config[\"command\"]\n        self.command_format = config[\"command-format\"]\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = content.split(\" \")\n\n        if command[0] not in self.command:\n            return\n\n        if len(command) == 1:\n            await bot.send_at_message(message[\"FromWxid\"], f\"-----XYBot-----\\n❌命令格式错误！{self.command_format}\",\n                                      [message[\"SenderWxid\"]])\n            return\n\n        song_name = content[len(command[0]):].strip()\n\n        async with aiohttp.ClientSession() as session:\n            async with session.get(\n                    f\"https://www.hhlqilongzhu.cn/api/dg_wyymusic.php?gm={song_name}&n=1&br=2&type=json\") as resp:\n                data = await resp.json()\n\n        if data[\"code\"] != 200:\n            await bot.send_at_message(message[\"FromWxid\"], f\"-----XYBot-----\\n❌点歌失败！\\n{data}\",\n                                      [message[\"SenderWxid\"]])\n            return\n        title = data[\"title\"]\n        singer = data[\"singer\"]\n        url = data[\"link\"]\n        music_url = data[\"music_url\"].split(\"?\")[0]\n        cover_url = data[\"cover\"]\n        lyric = data[\"lrc\"]\n\n        xml = f\"\"\"<appmsg appid=\"wx79f2c4418704b4f8\" sdkver=\"0\"><title>{title}</title><des>{singer}</des><action>view</action><type>3</type><showtype>0</showtype><content/><url>{url}</url><dataurl>{music_url}</dataurl><lowurl>{url}</lowurl><lowdataurl>{music_url}</lowdataurl><recorditem/><thumburl>{cover_url}</thumburl><messageaction/><laninfo/><extinfo/><sourceusername/><sourcedisplayname/><songlyric>{lyric}</songlyric><commenturl/><appattach><totallen>0</totallen><attachid/><emoticonmd5/><fileext/><aeskey/></appattach><webviewshared><publisherId/><publisherReqId>0</publisherReqId></webviewshared><weappinfo><pagepath/><username/><appid/><appservicetype>0</appservicetype></weappinfo><websearch/><songalbumurl>{cover_url}</songalbumurl></appmsg><fromusername>{bot.wxid}</fromusername><scene>0</scene><appinfo><version>1</version><appname/></appinfo><commenturl/>\"\"\"\n        await bot.send_app_message(message[\"FromWxid\"], xml, 3)\n"
  },
  {
    "path": "plugins/News/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/News/config.toml",
    "content": "[News]\nenable = true\nenable-schedule-news = false # 是否每天早上和中午自动推送新闻？\ncommand = [\"新闻\", \"新闻速递\", \"新闻资讯\", \"新闻头条\", \"新闻列表\", \"随机新闻\", \"获取新闻\", \"获取随机新闻\", \"头条\", \"头条新闻\", \"今日头条\", \"今日新闻\"]\n"
  },
  {
    "path": "plugins/News/main.py",
    "content": "import asyncio\nimport tomllib\nfrom random import choice\n\nimport aiohttp\n\nfrom WechatAPI import WechatAPIClient\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass News(PluginBase):\n    description = \"新闻插件\"\n    author = \"HenryXiaoYang\"\n    version = \"1.1.0\"\n\n    # Change Log\n    # 1.1.0 2025/2/22 默认关闭定时新闻\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/News/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        config = plugin_config[\"News\"]\n\n        self.enable = config[\"enable\"]\n        self.enable_schedule_news = config[\"enable-schedule-news\"]\n        self.command = config[\"command\"]\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = content.split(\" \")\n\n        if command[0] not in self.command:\n            return\n\n        if \"随机\" in command[0]:\n            async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) as session:\n                async with session.get(\"https://cn.apihz.cn/api/xinwen/baidu.php?id=88888888&key=88888888\") as resp:\n                    data = await resp.json()\n\n            if data[\"code\"] != 200:\n                await bot.send_text_message(message[\"FromWxid\"], \"-----XYBot-----\\n新闻获取失败！\")\n                return\n\n            result = data.get(\"data\", [])\n\n            if len(result) == 0:\n                await bot.send_text_message(message[\"FromWxid\"], \"-----XYBot-----\\n新闻获取失败！\")\n                return\n\n            new = choice(result[\"data\"])\n            await bot.send_link_message(message[\"FromWxid\"],\n                                        title=new[\"word\"],\n                                        url=new[\"rawUrl\"],\n                                        description=new[\"desc\"],\n                                        thumb_url=new[\"img\"])\n\n        else:\n            async with aiohttp.ClientSession() as session:\n                async with session.get(\"http://zj.v.api.aa1.cn/api/60s-v2/?cc=XYBot\") as resp:\n                    image_byte = await resp.read()\n            await bot.send_image_message(message[\"FromWxid\"], image_byte)\n\n    @schedule('cron', hour=12)\n    async def noon_news(self, bot: WechatAPIClient):\n        if not self.enable_schedule_news:\n            return\n        id_list = []\n        wx_seq, chatroom_seq = 0, 0\n        while True:\n            contact_list = await bot.get_contract_list(wx_seq, chatroom_seq)\n            id_list.extend(contact_list[\"ContactUsernameList\"])\n            wx_seq = contact_list[\"CurrentWxcontactSeq\"]\n            chatroom_seq = contact_list[\"CurrentChatRoomContactSeq\"]\n            if contact_list[\"CountinueFlag\"] != 1:\n                break\n\n        chatrooms = []\n        for id in id_list:\n            if id.endswith(\"@chatroom\"):\n                chatrooms.append(id)\n\n        async with aiohttp.ClientSession() as session:\n            async with session.get(\"http://zj.v.api.aa1.cn/api/60s-v2/?cc=XYBot\") as resp:\n                iamge_byte = await resp.read()\n\n        for id in chatrooms:\n            await bot.send_image_message(id, iamge_byte)\n            await asyncio.sleep(2)\n\n    @schedule('cron', hour=18)\n    async def night_news(self, bot: WechatAPIClient):\n        if not self.enable_schedule_news:\n            return\n        id_list = []\n        wx_seq, chatroom_seq = 0, 0\n        while True:\n            contact_list = await bot.get_contract_list(wx_seq, chatroom_seq)\n            id_list.extend(contact_list[\"ContactUsernameList\"])\n            wx_seq = contact_list[\"CurrentWxcontactSeq\"]\n            chatroom_seq = contact_list[\"CurrentChatRoomContactSeq\"]\n            if contact_list[\"CountinueFlag\"] != 1:\n                break\n\n        chatrooms = []\n        for id in id_list:\n            if id.endswith(\"@chatroom\"):\n                chatrooms.append(id)\n\n        async with aiohttp.ClientSession() as session:\n            async with session.get(\"http://v.api.aa1.cn/api/60s-v3/?cc=XYBot\") as resp:\n                iamge_byte = await resp.read()\n\n        for id in chatrooms:\n            await bot.send_image_message(id, iamge_byte)\n            await asyncio.sleep(2)\n"
  },
  {
    "path": "plugins/PointTrade/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/PointTrade/config.toml",
    "content": "[PointTrade]\nenable = true\ncommand = [\"积分交易\", \"积分转账\", \"转账积分\", \"积分赠送\", \"赠送积分\", \"积分转移\", \"转移积分\", \"送积分\", \"积分送人\", \"送人积分\", \"积分赠予\", \"赠予\"]\ncommand-format = \"\"\"\n-----XYBot-----\n🔄转账积分：\n积分转账 积分数 @用户\n\"\"\""
  },
  {
    "path": "plugins/PointTrade/main.py",
    "content": "import tomllib\nfrom datetime import datetime\n\nfrom WechatAPI import WechatAPIClient\nfrom database.XYBotDB import XYBotDB\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass PointTrade(PluginBase):\n    description = \"积分交易\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/PointTrade/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        config = plugin_config[\"PointTrade\"]\n\n        self.enable = config[\"enable\"]\n        self.command = config[\"command\"]\n        self.command_format = config[\"command-format\"]\n\n        self.db = XYBotDB()\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = content.split(\" \")\n\n        if command[0] not in self.command:\n            return\n\n        if len(command) < 3:\n            await bot.send_at_message(message[\"FromWxid\"], self.command_format, [message[\"SenderWxid\"]])\n            return\n        elif not command[1].isdigit():\n            await bot.send_at_message(message[\"FromWxid\"], \"\\n-----XYBot-----\\n🈚️转账积分无效(必须为正整数!)\",\n                                      [message[\"SenderWxid\"]])\n            return\n        elif len(message[\"Ats\"]) != 1:\n            await bot.send_at_message(message[\"FromWxid\"], \"-----XYBot-----\\n转账失败❌\\n🈚️转账人无效！\",\n                                      [message[\"SenderWxid\"]])\n            return\n\n        points = int(command[1])\n\n        target_wxid = message[\"Ats\"][0]\n        trader_wxid = message[\"SenderWxid\"]\n\n        # check points\n        trader_points = self.db.get_points(trader_wxid)\n\n        if trader_points < points:\n            await bot.send_at_message(message[\"FromWxid\"], \"\\n-----XYBot-----\\n转账失败❌\\n积分不足！😭\",\n                                      [message[\"SenderWxid\"]])\n            return\n\n        self.db.safe_trade_points(trader_wxid, target_wxid, points)\n\n        trader_nick, target_nick = await bot.get_nickname([trader_wxid, target_wxid])\n\n        trader_points = self.db.get_points(trader_wxid)\n        target_points = self.db.get_points(target_wxid)\n\n        output = (\n            f\"\\n-----XYBot-----\\n\"\n            f\"✅积分转账成功！✨\\n\"\n            f\"🤝{trader_nick} 现在有 {trader_points} 点积分➖\\n\"\n            f\"🤝{target_nick} 现在有 {target_points} 点积分➕\\n\"\n            f\"⌚️时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\"\n        )\n        await bot.send_at_message(message[\"FromWxid\"], output, [trader_wxid, target_wxid])\n"
  },
  {
    "path": "plugins/QueryPoint/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/QueryPoint/config.toml",
    "content": "[QueryPoint]\nenable = true\ncommand = [\"查询积分\", \"积分\", \"我的积分\", \"积分查询\"]"
  },
  {
    "path": "plugins/QueryPoint/main.py",
    "content": "import tomllib\n\nfrom WechatAPI import WechatAPIClient\nfrom database.XYBotDB import XYBotDB\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass QueryPoint(PluginBase):\n    description = \"查询积分\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/QueryPoint/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        config = plugin_config[\"QueryPoint\"]\n\n        self.enable = config[\"enable\"]\n        self.command = config[\"command\"]\n\n        self.db = XYBotDB()\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = content.split(\" \")\n\n        if not len(command) or command[0] not in self.command:\n            return\n\n        query_wxid = message[\"SenderWxid\"]\n\n        points = self.db.get_points(query_wxid)\n\n        output = (\"\\n\"\n                  f\"-----XYBot-----\\n\"\n                  f\"你有 {points} 点积分！😄\")\n        await bot.send_at_message(message[\"FromWxid\"], output, [query_wxid])\n"
  },
  {
    "path": "plugins/RandomMember/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/RandomMember/config.toml",
    "content": "[RandomMember]\nenable = true\ncommand = [\"随机成员\", \"随机群员\", \"随机群成员\", \"随机群用户\"]\ncount = 3"
  },
  {
    "path": "plugins/RandomMember/main.py",
    "content": "import random\nimport tomllib\n\nfrom WechatAPI import WechatAPIClient\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass RandomMember(PluginBase):\n    description = \"随机群成员\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/RandomMember/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        config = plugin_config[\"RandomMember\"]\n\n        self.enable = config[\"enable\"]\n        self.command = config[\"command\"]\n        self.count = config[\"count\"]\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = content.split(\" \")\n\n        if command[0] not in self.command:\n            return\n\n        if not message[\"IsGroup\"]:\n            await bot.send_text_message(message[\"FromWxid\"], \"-----XYBot-----\\n😠只能在群里使用！\")\n            return\n\n        memlist = await bot.get_chatroom_member_list(message[\"FromWxid\"])\n        random_members = random.sample(memlist, self.count)\n\n        output = \"\\n-----XYBot-----\\n👋嘿嘿，我随机选到了这几位：\"\n        for member in random_members:\n            output += f\"\\n✨{member['NickName']}\"\n\n        await bot.send_at_message(message[\"FromWxid\"], output, [message[\"SenderWxid\"]])\n"
  },
  {
    "path": "plugins/RandomPicture/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/RandomPicture/config.toml",
    "content": "[RandomPicture]\nenable = true\ncommand = [\"随机图片\", \"随机图图\"]\n"
  },
  {
    "path": "plugins/RandomPicture/main.py",
    "content": "import tomllib\nimport traceback\n\nimport aiohttp\nfrom loguru import logger\n\nfrom WechatAPI import WechatAPIClient\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass RandomPicture(PluginBase):\n    description = \"随机图片\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/RandomPicture/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        config = plugin_config[\"RandomPicture\"]\n\n        self.enable = config[\"enable\"]\n        self.command = config[\"command\"]\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = content.split(\" \")\n\n        if not len(command) or command[0] not in self.command:\n            return\n\n        api_url = \"https://api.52vmy.cn/api/img/tu/man?type=text\"\n\n        try:\n            conn_ssl = aiohttp.TCPConnector(ssl=False)\n            async with aiohttp.request(\"GET\", url=api_url, connector=conn_ssl) as req:\n                pic_url = await req.text()\n\n            async with aiohttp.request(\"GET\", url=pic_url, connector=conn_ssl) as req:\n                content = await req.read()\n\n            await conn_ssl.close()\n\n            await bot.send_image_message(message[\"FromWxid\"], image=content)\n\n        except Exception as error:\n            out_message = f\"-----XYBot-----\\n出现错误❌！\\n{error}\"\n            logger.error(traceback.format_exc())\n\n            await bot.send_text_message(message[\"FromWxid\"], out_message)\n"
  },
  {
    "path": "plugins/RedPacket/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/RedPacket/config.toml",
    "content": "[RedPacket]\nenable = true\ncommand-format = \"\"\"⚙️红包系统：\n发红包 积分 数量\n抢红包 口令\"\"\"\nmax-point = 1000\nmin-point = 1\nmax-packet = 10\nmax-time = 300"
  },
  {
    "path": "plugins/RedPacket/main.py",
    "content": "import base64\nimport random\nimport re\nimport time\nimport tomllib\nfrom io import BytesIO\n\nfrom PIL import Image, ImageDraw, ImageFilter\nfrom captcha.image import ImageCaptcha\nfrom loguru import logger\n\nfrom WechatAPI import WechatAPIClient\nfrom database.XYBotDB import XYBotDB\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass RedPacket(PluginBase):\n    description = \"红包系统\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/RedPacket/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        config = plugin_config[\"RedPacket\"]\n\n        self.enable = config[\"enable\"]\n        self.command_format = config[\"command-format\"]\n        self.max_point = config[\"max-point\"]\n        self.min_point = config[\"min-point\"]\n        self.max_packet = config[\"max-packet\"]\n        self.max_time = config[\"max-time\"]\n\n        self.red_packets = {}\n        self.db = XYBotDB()\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = re.split(r'[\\s\\u2005]+', content)\n\n        if not len(command):\n            return\n\n        if len(command) == 3 and command[0] == \"发红包\":\n            await self.send_red_packet(bot, message, command)\n        elif len(command) == 2 and command[0] == \"抢红包\":\n            await self.grab_red_packet(bot, message, command)\n        elif command[0] in [\"发红包\", \"抢红包\"]:\n            await bot.send_text_message(message[\"FromWxid\"], f\"-----XYBot-----\\n{self.command_format}\")\n\n    async def send_red_packet(self, bot: WechatAPIClient, message: dict, command: list):\n        sender_wxid = message[\"SenderWxid\"]\n        from_wxid = message[\"FromWxid\"]\n\n        error = \"\"\n        if not message[\"IsGroup\"]:\n            error = \"\\n-----XYBot-----\\n红包只能在群里发！😔\"\n        elif not command[1].isdigit() or not command[2].isdigit():\n            error = f\"\\n-----XYBot-----\\n指令格式错误！\\n{self.command_format}\"\n        elif int(command[1]) > self.max_point or int(command[1]) < self.min_point:\n            error = f\"\\n-----XYBot-----\\n⚠️积分无效！最大{self.max_point}，最小{self.min_point}！\"\n        elif int(command[2]) > self.max_packet:\n            error = f\"\\n-----XYBot-----\\n⚠️红包数量无效！最大{self.max_packet}个红包！\"\n        elif int(command[2]) > int(command[1]):\n            error = \"\\n-----XYBot-----\\n🔢红包数量不能大于红包积分！\"\n        elif self.db.get_points(sender_wxid) < int(command[1]):\n            error = \"\\n-----XYBot-----\\n😭你的积分不够！\"\n\n        if error:\n            await bot.send_at_message(from_wxid, error, [sender_wxid])\n            return\n\n        points = int(command[1])\n        amount = int(command[2])\n        sender_nick = await bot.get_nickname(sender_wxid)\n\n        points_list = self._split_integer(points, amount)\n\n        # 生成验证码图片\n        captcha, captcha_image = self._generate_captcha()\n\n        # 加载红包背景图\n        background = Image.open(\"resource/images/redpacket.png\")\n\n        # 调整验证码图片大小\n        captcha_width = 400  # 进一步增加验证码宽度\n        captcha_height = 150  # 进一步增加验证码高度\n        captcha_image = captcha_image.resize((captcha_width, captcha_height))\n\n        # 创建一个带有圆角矩形和模糊边缘效果的遮罩\n        padding = 40  # 增加边缘空间\n        mask = Image.new('L', (captcha_width + padding * 2, captcha_height + padding * 2), 0)\n        draw = ImageDraw.Draw(mask)\n\n        # 绘制圆角矩形\n        radius = 20  # 圆角半径\n        draw.rounded_rectangle(\n            [padding, padding, captcha_width + padding, captcha_height + padding],\n            radius=radius,\n            fill=255\n        )\n\n        # 应用高斯模糊创建柔和边缘\n        mask = mask.filter(ImageFilter.GaussianBlur(radius=20))\n\n        # 创建一个新的白色背景图层用于验证码\n        captcha_layer = Image.new('RGBA', (captcha_width + padding * 2, captcha_height + padding * 2),\n                                  (255, 255, 255, 0))\n        # 将验证码图片粘贴到图层的中心\n        captcha_layer.paste(captcha_image, (padding, padding))\n        # 应用模糊遮罩\n        captcha_layer.putalpha(mask)\n\n        # 计算验证码位置使其在橙色区域居中\n        x = (background.width - (captcha_width + padding * 2)) // 2\n        y = background.height - 320  # 调整位置\n\n        # 将带有模糊边缘的验证码图片粘贴到背景图\n        background.paste(captcha_layer, (x, y), captcha_layer)\n\n        # 转换为base64\n        buffer = BytesIO()\n        background.save(buffer, format='PNG')\n        image_base64 = base64.b64encode(buffer.getvalue()).decode()\n\n        # 保存红包信息\n        self.red_packets[captcha] = {\n            \"points\": points,\n            \"amount\": amount,\n            \"sender\": sender_wxid,\n            \"list\": points_list,\n            \"grabbed\": [],\n            \"time\": time.time(),\n            \"chatroom\": from_wxid,\n            \"sender_nick\": sender_nick\n        }\n\n        self.db.add_points(sender_wxid, -points)\n        logger.info(f\"用户 {sender_wxid} 发了个红包 {captcha}，总计 {points} 点积分\")\n\n        # 发送文字消息和图片\n        text_content = (\n            f\"-----XYBot-----\\n\"\n            f\"✨{sender_nick} 发送了一个红包！🧧\\n\"\n            f\"🥳快输入指令来抢红包！🎉\\n\"\n            f\"🧧指令：抢红包 口令\"\n        )\n\n        await bot.send_text_message(from_wxid, text_content)\n        await bot.send_image_message(from_wxid, image_base64)\n\n    async def grab_red_packet(self, bot: WechatAPIClient, message: dict, command: list):\n        grabber_wxid = message[\"SenderWxid\"]\n        from_wxid = message[\"FromWxid\"]\n        captcha = command[1]\n\n        error = \"\"\n        if captcha not in self.red_packets:\n            error = \"\\n-----XYBot-----\\n❌红包口令错误！\"\n        elif not self.red_packets[captcha][\"list\"]:\n            error = \"\\n-----XYBot-----\\n😭红包已被抢完！\"\n        elif not message[\"IsGroup\"]:\n            error = \"\\n-----XYBot-----\\n红包只能在群里抢！😔\"\n        elif grabber_wxid in self.red_packets[captcha][\"grabbed\"]:\n            error = \"\\n-----XYBot-----\\n你已经抢过这个红包了！😡\"\n        elif self.red_packets[captcha][\"sender\"] == grabber_wxid:\n            error = \"\\n-----XYBot-----\\n😠不能抢自己的红包！\"\n\n        if error:\n            await bot.send_at_message(from_wxid, error, [grabber_wxid])\n            return\n\n        try:\n            grabbed_points = self.red_packets[captcha][\"list\"].pop()\n            self.red_packets[captcha][\"grabbed\"].append(grabber_wxid)\n\n            grabber_nick = await bot.get_nickname(grabber_wxid)\n            self.db.add_points(grabber_wxid, grabbed_points)\n\n            out_message = f\"-----XYBot-----\\n🧧恭喜 {grabber_nick} 抢到了 {grabbed_points} 点积分！👏\"\n            await bot.send_text_message(from_wxid, out_message)\n\n            if not self.red_packets[captcha][\"list\"]:\n                self.red_packets.pop(captcha)\n\n        except IndexError:\n            await bot.send_at_message(from_wxid, \"\\n-----XYBot-----\\n红包已被抢完！😭\", [grabber_wxid])\n\n    @schedule('interval', seconds=300)\n    async def check_expired_packets(self, bot: WechatAPIClient):\n        logger.info(\"[计划任务]检查是否有超时的红包\")\n        for captcha in list(self.red_packets.keys()):\n            packet = self.red_packets[captcha]\n            if time.time() - packet[\"time\"] > self.max_time:\n                points_left = sum(packet[\"list\"])\n                sender_wxid = packet[\"sender\"]\n                chatroom = packet[\"chatroom\"]\n                sender_nick = packet[\"sender_nick\"]\n\n                self.db.add_points(sender_wxid, points_left)\n                self.red_packets.pop(captcha)\n\n                out_message = (\n                    f\"-----XYBot-----\\n\"\n                    f\"🧧发现有红包 {captcha} 超时！已归还剩余 {points_left} 积分给 {sender_nick}\"\n                )\n                await bot.send_text_message(chatroom, out_message)\n\n    @staticmethod\n    def _generate_captcha():\n        chars = \"abdfghkmnpqtwxy23467889\"\n        captcha = ''.join(random.sample(chars, 5))\n\n        image = ImageCaptcha().generate_image(captcha)\n        return captcha, image\n\n    @staticmethod\n    def _split_integer(num: int, count: int) -> list:\n        result = [1] * count\n        remaining = num - count\n\n        while remaining > 0:\n            index = random.randint(0, count - 1)\n            result[index] += 1\n            remaining -= 1\n\n        return result\n"
  },
  {
    "path": "plugins/SignIn/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/SignIn/config.toml",
    "content": "[SignIn]\nenable = true\ncommand = [\"签到\", \"每日签到\", \"qd\", \"Qd\", \"QD\"]\nmin-point = 3\nmax-point = 20\nstreak-cycle = 5 # 每签到?天后，额外积分奖励加1点？\nmax-streak-point = 10 # 额外积分奖励上限"
  },
  {
    "path": "plugins/SignIn/main.py",
    "content": "import tomllib\nfrom datetime import datetime\nfrom random import randint\n\nimport pytz\n\nfrom WechatAPI import WechatAPIClient\nfrom database.XYBotDB import XYBotDB\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass SignIn(PluginBase):\n    description = \"每日签到\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/SignIn/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        with open(\"main_config.toml\", \"rb\") as f:\n            main_config = tomllib.load(f)\n\n        config = plugin_config[\"SignIn\"]\n        main_config = main_config[\"XYBot\"]\n\n        self.enable = config[\"enable\"]\n        self.command = config[\"command\"]\n        self.min_points = config[\"min-point\"]\n        self.max_points = config[\"max-point\"]\n        self.streak_cycle = config[\"streak-cycle\"]\n        self.max_streak_point = config[\"max-streak-point\"]\n\n        self.timezone = main_config[\"timezone\"]\n\n        self.db = XYBotDB()\n\n        # 每日签到排名数据\n        self.today_signin_count = 0\n        self.last_reset_date = datetime.now(tz=pytz.timezone(self.timezone)).date()\n\n    def _check_and_reset_count(self):\n        current_date = datetime.now(tz=pytz.timezone(self.timezone)).date()\n        if current_date != self.last_reset_date:\n            self.today_signin_count = 0\n            self.last_reset_date = current_date\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = content.split(\" \")\n\n        if not len(command) or command[0] not in self.command:\n            return\n\n        # 检查是否需要重置计数\n        self._check_and_reset_count()\n\n        sign_wxid = message[\"SenderWxid\"]\n\n        last_sign = self.db.get_signin_stat(sign_wxid)\n        now = datetime.now(tz=pytz.timezone(self.timezone)).replace(hour=0, minute=0, second=0, microsecond=0)\n\n        # 确保 last_sign 用了时区\n        if last_sign and last_sign.tzinfo is None:\n            last_sign = pytz.timezone(self.timezone).localize(last_sign)\n        last_sign = last_sign.replace(hour=0, minute=0, second=0, microsecond=0)\n\n        if last_sign and (now - last_sign).days < 1:\n            output = \"\\n-----XYBot-----\\n你今天已经签到过了！😠\"\n            await bot.send_at_message(message[\"FromWxid\"], output, [sign_wxid])\n            return\n\n        # 检查是否断开连续签到（超过1天没签到）\n        if last_sign and (now - last_sign).days > 1:\n            old_streak = self.db.get_signin_streak(sign_wxid)\n            streak = 1  # 重置连续签到天数\n            streak_broken = True\n        else:\n            old_streak = self.db.get_signin_streak(sign_wxid)\n            streak = old_streak + 1 if old_streak else 1  # 如果是第一次签到，从1开始\n            streak_broken = False\n\n        self.db.set_signin_stat(sign_wxid, now)\n        self.db.set_signin_streak(sign_wxid, streak)  # 设置连续签到天数\n        streak_points = min(streak // self.streak_cycle, self.max_streak_point)  # 计算连续签到奖励\n\n        signin_points = randint(self.min_points, self.max_points)  # 随机积分\n        self.db.add_points(sign_wxid, signin_points + streak_points)  # 增加积分\n\n        # 增加签到计数并获取排名\n        self.today_signin_count += 1\n        today_rank = self.today_signin_count\n\n        output = (\"\\n\"\n                  f\"-----XYBot-----\\n\"\n                  f\"签到成功！你领到了 {signin_points} 个积分！✅\\n\"\n                  f\"你是今天第 {today_rank} 个签到的！🎉\\n\")\n\n        if streak_broken and old_streak > 0:  # 只有在真的断签且之前有签到记录时才显示\n            output += f\"你断开了 {old_streak} 天的连续签到！[心碎]\"\n        elif streak > 1:\n            output += f\"你连续签到了 {streak} 天！\"\n\n        if streak_points > 0:\n            output += f\" 再奖励 {streak_points} 积分！\"\n\n        if streak > 1 and not streak_broken:\n            output += \"[爱心]\"\n\n        await bot.send_at_message(message[\"FromWxid\"], output, [sign_wxid])\n"
  },
  {
    "path": "plugins/TencentLke/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/TencentLke/config.toml",
    "content": "[TencentLke]\nenable = true\nbot_app_key = \"\" #腾讯大模型知识引擎\n# 其他插件的指令要填进下面，以免冲突\nother-plugin-cmd = [\"status\", \"bot\", \"机器人状态\", \"状态\", \"加载插件\", \"加载所有插件\", \"卸载插件\", \"卸载所有插件\", \"重载插件\", \"重载所有插件\", \"插件列表\", \"插件信息\", \"随机图片\", \"随机图图\", \"查询积分\", \"积分\", \"我的积分\", \"积分查询\", \"签到\", \"每日签到\", \"qd\", \"Qd\", \"QD\", \"重置签到\", \"重置签到状态\", \"五子棋\", \"五子棋创建\", \"五子棋邀请\", \"邀请五子棋\", \"接受\", \"加入\", \"下棋\", \"获取联系人\", \"联系人\", \"通讯录\", \"获取通讯录\", \"排行榜\", \"积分榜\", \"积分排行榜\", \"群排行榜\", \"群积分榜\", \"新闻\", \"新闻速递\", \"新闻资讯\", \"新闻头条\", \"新闻列表\", \"随机新闻\", \"获取新闻\", \"获取随机新闻\", \"头条\", \"头条新闻\", \"今日头条\", \"今日新闻\", \"积分交易\", \"积分转账\", \"转账积分\", \"积分赠送\", \"赠送积分\", \"积分转移\", \"转移积分\", \"送积分\", \"积分送人\", \"送人积分\", \"积分赠予\", \"赠予\", \"战争雷霆\", \"战雷查询\", \"战争雷霆玩家\", \"战雷玩家\", \"点歌\", \"音乐\", \"音乐点播\", \"点播音乐\", \"音乐点歌\", \"抽奖\", \"幸运抽奖\", \"抽奖活动\", \"抽奖指令\", \"幸运大抽奖\", \"大抽奖\", \"积分抽奖\", \"菜单\", \"帮助\", \"帮助菜单\", \"功能列表\", \"功能菜单\", \"指令列表\", \"指令菜单\", \"功能\", \"指令\", \"cd\", \"Cd\", \"cd\", \"menu\", \"Menu\", \"发红包\", \"抢红包\", \"加积分\", \"减积分\", \"设置积分\", \"添加白名单\", \"删除白名单\", \"白名单列表\", \"重置签到\", \"加载插件\", \"加载所有插件\", \"卸载插件\", \"卸载所有插件\", \"重载插件\", \"重载所有插件\", \"插件列表\", \"随机成员\", \"随机群员\", \"随机群成员\", \"随机群用户\"]\n\n# Http代理设置\n# 格式: http://用户名:密码@代理地址:代理端口\n# 例如：http://127.0.0.1:7890\nhttp-proxy = \"\""
  },
  {
    "path": "plugins/TencentLke/main.py",
    "content": "import json\nimport random\nimport time\nimport tomllib\n\nimport aiohttp\n\nfrom WechatAPI import WechatAPIClient\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass TencentLke(PluginBase):\n    description = \"腾讯大模型知识引擎LKE\"\n    author = \"ChenChongWu\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"main_config.toml\", \"rb\") as f:\n            config = tomllib.load(f)\n\n        self.admins = config[\"XYBot\"][\"admins\"]\n\n        with open(\"plugins/TencentLke/config.toml\", \"rb\") as f:\n            config = tomllib.load(f)\n\n        plugin_config = config[\"TencentLke\"]\n        self.enable = plugin_config[\"enable\"]\n        self.bot_app_key = plugin_config[\"bot_app_key\"]\n        self.other_plugin_cmd = plugin_config[\"other-plugin-cmd\"]\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n        command = str(message[\"Content\"]).strip().split(\" \")\n\n        if command and command[0] in self.other_plugin_cmd:  # 指令来自其他插件\n            return\n\n        if message[\"SenderWxid\"] in self.admins:  # 自己发送不进行AI回答\n            return\n\n        content = str(message[\"Content\"]).strip()\n        if (content == \"\"):\n            return\n\n        await self.TencentLke(bot, message, message[\"Content\"])\n\n    async def TencentLke(self, bot: WechatAPIClient, message: dict, query: str, files=None):\n        headers = {\"Content-Type\": \"application/json\"}\n        payload = json.dumps({\n            'content': query,\n            'request_id': str(int(time.time())) + str(random.randint(1, 999)),\n            'bot_app_key': self.bot_app_key,\n            'visitor_biz_id': message[\"FromWxid\"],\n            'session_id': message[\"FromWxid\"]\n        })\n        url = f\"https://wss.lke.cloud.tencent.com/v1/qbot/chat/sse\"\n        async with aiohttp.ClientSession(proxy=\"\") as session:\n            async with session.post(url=url, headers=headers, data=payload, timeout=10) as resp:\n                last_line = None\n                async for line in resp.content:  # 流式传输\n                    line = line.decode(\"utf-8\").strip()\n                    if (line != \"\"):\n                        last_line = line\n\n                last_line = last_line.strip().replace(\"data:\", \"\")\n                resp_json = json.loads(last_line)\n\n                if (resp_json['type'] == \"reply\"):\n                    try:\n                        AIResult = resp_json['payload'][\"content\"]\n                        if (AIResult != \"\"):\n                            await bot.send_text_message(message.get(\"FromWxid\"), AIResult)\n\n                    except json.JSONDecodeError as e:\n                        return\n\n                return\n"
  },
  {
    "path": "plugins/UpdateQR/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/UpdateQR/main.py",
    "content": "import imghdr\nimport io\n\nimport aiohttp\nfrom loguru import logger\n\nfrom WechatAPI import WechatAPIClient\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass UpdateQR(PluginBase):\n    description = \"更新群二维码\"\n    author = \"HenryXiaoYang\"\n    version = \"1.0.0\"\n\n    def __init__(self):\n        super().__init__()\n\n    @on_text_message\n    async def on_text(self, bot: WechatAPIClient, message: dict):\n        if message.get(\"Content\") == \"更新群二维码\":\n            await self.update_qr(bot)\n\n    @schedule('cron', day_of_week='mon', hour=8)\n    async def monday_update_qr(self, bot: WechatAPIClient):\n        await self.update_qr(bot)\n\n    @schedule('cron', day_of_week='thu', hour=8)\n    async def thursday_update_qr(self, bot: WechatAPIClient):\n        await self.update_qr(bot)\n\n    @staticmethod\n    async def update_qr(bot: WechatAPIClient):\n        qr = await bot.get_chatroom_qrcode(\"56994401945@chatroom\")\n        qr = bot.base64_to_byte(qr.get(\"base64\", \"\"))\n\n        img_format = imghdr.what(io.BytesIO(qr))\n\n        async with aiohttp.ClientSession() as session:\n            headers = {\"Authorization\": f\"Bearer Henry_Yang\"}\n            data = aiohttp.FormData()\n            data.add_field('file',\n                           qr,\n                           filename=f'qr_code.{img_format}',\n                           content_type=f'image/{img_format}')\n\n            async with session.post(\"https://qrcode.yangres.com/update_image\", headers=headers, data=data) as response:\n                if response.status != 200:\n                    logger.error(\"上传失败: HTTP {}\", response.status)\n                    logger.error(await response.text())\n                else:\n                    logger.success(\"更新群二维码成功\")\n"
  },
  {
    "path": "plugins/Warthunder/__init__.py",
    "content": ""
  },
  {
    "path": "plugins/Warthunder/config.toml",
    "content": "[Warthunder]\nenable = true\ncommand = [\"战争雷霆\", \"战雷查询\", \"战争雷霆玩家\", \"战雷玩家\"]\ncommand-format = \"\"\"\n-----XYBot-----\n🎮战争雷霆玩家查询：\n战雷查询 玩家名\n\"\"\""
  },
  {
    "path": "plugins/Warthunder/main.py",
    "content": "import asyncio\nimport io\nimport os\nimport tomllib\nfrom io import BytesIO\n\nimport aiohttp\nimport matplotlib.font_manager as fm\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport requests\nfrom PIL import Image, ImageDraw, ImageFont\nfrom matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas\nfrom matplotlib.figure import Figure\n\nfrom WechatAPI import WechatAPIClient\nfrom utils.decorators import *\nfrom utils.plugin_base import PluginBase\n\n\nclass Warthunder(PluginBase):\n    description = \"战争雷霆玩家查询\"\n    author = \"HenryXiaoYang\"\n    version = \"1.1.0\"\n\n    # Change Log\n    # 1.0.0 第一个版本\n    # 1.1.0 适配新的api格式\n\n    def __init__(self):\n        super().__init__()\n\n        with open(\"plugins/Warthunder/config.toml\", \"rb\") as f:\n            plugin_config = tomllib.load(f)\n\n        config = plugin_config[\"Warthunder\"]\n\n        self.enable = config[\"enable\"]\n        self.command = config[\"command\"]\n        self.command_format = config[\"command-format\"]\n\n        self.font_path = \"resource/font/华文细黑.ttf\"\n\n    @on_text_message\n    async def handle_text(self, bot: WechatAPIClient, message: dict):\n        if not self.enable:\n            return\n\n        content = str(message[\"Content\"]).strip()\n        command = content.split(\" \")\n\n        if command[0] not in self.command:\n            return\n\n        if len(command) != 2:\n            await bot.send_text_message(message[\"FromWxid\"], self.command_format)\n            return\n\n        player_name = content[len(command[0]) + 1:]\n\n        output = (f\"\\n-----XYBot-----\\n\"\n                  f\"正在查询玩家 {player_name} 的数据，请稍等...😄\")\n        a, b, c = await bot.send_at_message(message[\"FromWxid\"], output, [message[\"SenderWxid\"]])\n\n        async with aiohttp.ClientSession() as session:\n            async with session.get(f\"https://wtapi.yangres.com/player?nick={player_name}\") as resp:\n                data = await resp.json()\n\n        if data[\"code\"] == 404:\n            await bot.send_at_message(message[\"FromWxid\"],\n                                      f\"-----XYBot-----\\n🈚️玩家不存在！\\n请检查玩家昵称，区分大小写哦！\",\n                                      [message[\"SenderWxid\"]])\n            await bot.revoke_message(message[\"FromWxid\"], a, b, c)\n            return\n        elif data[\"code\"] == 500:\n            await bot.send_at_message(message[\"FromWxid\"],\n                                      f\"-----XYBot-----\\n🙅对不起，API服务出现错误！\\n请稍后再试！\",\n                                      [message[\"SenderWxid\"]])\n            await bot.revoke_message(message[\"FromWxid\"], a, b, c)\n            return\n        elif data[\"code\"] == 400:\n            await bot.send_at_message(message[\"FromWxid\"],\n                                      f\"-----XYBot-----\\n🙅对不起，API客户端出现错误！\\n请稍后再试！\",\n                                      [message[\"SenderWxid\"]])\n            await bot.revoke_message(message[\"FromWxid\"], a, b, c)\n            return\n\n        image = await self.generate_card(data[\"data\"])\n\n        await bot.send_image_message(message[\"FromWxid\"], image)\n        await bot.revoke_message(message[\"FromWxid\"], a, b, c)\n\n    async def generate_card(self, data: dict):\n        loop = asyncio.get_event_loop()\n        return await loop.run_in_executor(None, self._generate_card, data)\n\n    def _generate_card(self, data: dict) -> bytes:\n        width, height = 1800, 2560\n        top_color = np.array([127, 127, 213])\n        bottom_color = np.array([145, 234, 228])\n\n        # 生成坐标网格\n        y, x = np.indices((height, width))\n        # 计算对角线权重（从左上到右下）\n        weight = (x + y) / (width + height)\n\n        # 向量化计算渐变\n        gradient = top_color * (1 - weight[..., np.newaxis]) + bottom_color * weight[..., np.newaxis]\n        gradient = gradient.astype(np.uint8)\n        img = Image.fromarray(gradient).convert('RGBA')\n\n        # 设置矩形参数\n        margin = 50  # 边距\n        radius = 30  # 圆角半径\n\n        # 绘制半透明圆角矩形\n        overlay = Image.new(\"RGBA\", img.size, (0, 0, 0, 0))\n        draw_overlay = ImageDraw.Draw(overlay)\n        draw_overlay.rounded_rectangle(\n            (margin, margin, width - margin, height - margin),\n            radius=radius,\n            fill=(255, 255, 255, 180))\n        img = Image.alpha_composite(img, overlay)\n\n        # 开始画数据\n        fm.fontManager.addfont(self.font_path)\n        plt.rcParams['font.family'] = ['STXihei']\n        plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题\n        plt.rcParams['font.size'] = 23\n        # 使用自定义字体文件\n        title_font = ImageFont.truetype(self.font_path, size=60)\n        normal_font = ImageFont.truetype(self.font_path, size=45)\n\n        draw = ImageDraw.Draw(img)\n\n        # 最上方标题\n        draw.text((80, 60), \"XYBotV2 战争雷霆玩家查询\", fill=\"black\", font=title_font)\n\n        # 头像\n        avatar = self._download_avatar(data[\"avatar\"]).resize((300, 300))\n        img.paste(avatar, (80, 160))\n\n        # 玩家基础信息\n        clan_and_nick = f\"{data['clan_name']}  {data['nickname']}\" if data.get('clan_name') else data['nickname']\n        draw.text((400, 160), clan_and_nick, fill=\"black\", font=normal_font)\n        draw.text((400, 250), f\"等级: {data['player_level']}\", fill=\"black\", font=normal_font)\n        draw.text((400, 340), f\"注册日期: {data['register_date']}\", fill=\"black\", font=normal_font)\n\n        # 载具数据饼图\n        owned_vehicles = []\n        country_labels = []\n        country_translation = {'USA': '美国', 'USSR': '苏联', 'Germany': '德国', 'GreatBritain': '英国',\n                               'Japan': '日本',\n                               'China': '中国', 'Italy': '意大利', 'France': '法国', 'Sweden': '瑞典',\n                               'Israel': '以色列'}\n\n        countries = dict(data[\"vehicles_and_rewards\"]).keys()\n        for c in countries:\n            vehicles = data[\"vehicles_and_rewards\"][c].get(\"owned_vehicles\", 0)\n            if vehicles > 0:\n                owned_vehicles.append(vehicles)\n                country_labels.append(country_translation.get(c, c))\n\n        if owned_vehicles:\n            fig = Figure(figsize=(6, 6), facecolor=(0, 0, 0, 0))\n            ax = fig.add_subplot(111)\n\n            color = plt.cm.Pastel1(np.linspace(0, 1, len(owned_vehicles)))  # 使用柔和的颜色方案\n            ax.pie(owned_vehicles,\n                   labels=country_labels,\n                   autopct=lambda pct: self._show_actual(pct, owned_vehicles),\n                   pctdistance=0.5,\n                   labeldistance=1.1,\n                   colors=color)\n            ax.set_title('载具数据', fontsize=27)\n\n            canvas = FigureCanvas(fig)\n            buf = BytesIO()\n            canvas.print_png(buf)\n            buf.seek(0)\n            pie_img = Image.open(buf)\n\n            pie_img = pie_img.resize((650, 650))\n            img.alpha_composite(pie_img, (1000, 40))\n\n        # KDA数据\n        total_kills = 0\n        total_deaths = 0\n        for mode in ['arcade', 'realistic', 'simulation']:\n            stats = data.get('statistics', {}).get(mode, {})\n            total_kills += stats.get('air_targets_destroyed', 0)\n            total_kills += stats.get('ground_targets_destroyed', 0)\n            total_kills += stats.get('naval_targets_destroyed', 0)\n            total_deaths += stats.get('deaths', 0)\n        kda = round(total_kills / total_deaths if total_deaths > 0 else 0, 2)\n\n        draw.text((80, 480), f\"KDA数据:\", fill=\"black\", font=title_font)\n        draw.text((75, 560), f\"击杀: {total_kills}\", fill=\"black\", font=normal_font)\n        draw.text((350, 560), f\"死亡: {total_deaths}\", fill=\"black\", font=normal_font)\n        draw.text((600, 560), f\"KDA: {kda}\", fill=\"black\", font=normal_font)\n\n        title_font = title_font.font_variant(size=45)\n        normal_font = normal_font.font_variant(size=35)\n\n        # 数据部分\n        titles = {\"victories\": \"获胜数\", \"completed_missions\": \"完成任务\", \"victories_battles_ratio\": \"胜率\",\n                  \"deaths\": \"死亡数\", \"lions_earned\": \"获得银狮\", \"play_time\": \"游玩时间\",\n                  \"air_targets_destroyed\": \"击毁空中目标\", \"ground_targets_destroyed\": \"击毁地面目标\",\n                  \"naval_targets_destroyed\": \"击毁海上目标\"}\n        air_titles = {\"air_battles\": \"空战次数\", \"total_targets_destroyed\": \"共击毁目标\",\n                      \"air_targets_destroyed\": \"击毁空中目标\", \"ground_targets_destroyed\": \"击毁地面目标\",\n                      \"naval_targets_destroyed\": \"击毁海上目标\", \"air_battles_fighters\": \"战斗机次数\",\n                      \"air_battles_bombers\": \"轰炸机次数\", \"air_battles_attackers\": \"攻击机次数\",\n                      \"time_played_air_battles\": \"空战时长\", \"time_played_fighter\": \"战斗机时长\",\n                      \"time_played_bomber\": \"轰炸机时长\", \"time_played_attackers\": \"攻击机时长\"}\n        ground_titles = {\"ground_battles\": \"陆战次数\", \"total_targets_destroyed\": \"共击毁目标\",\n                         \"air_targets_destroyed\": \"击毁空中目标\", \"ground_targets_destroyed\": \"击毁地面目标\",\n                         \"naval_targets_destroyed\": \"击毁海上目标\", \"ground_battles_tanks\": \"坦克次数\",\n                         \"ground_battles_spgs\": \"坦歼次数\", \"ground_battles_heavy_tanks\": \"重坦次数\",\n                         \"ground_battles_spaa\": \"防空车次数\", \"time_played_ground_battles\": \"陆战时长\",\n                         \"tank_battle_time\": \"坦克时长\", \"tank_destroyer_battle_time\": \"坦歼时长\",\n                         \"heavy_tank_battle_time\": \"重坦时长\", \"spaa_battle_time\": \"防空车时长\"}\n        naval_title = {\n            \"naval_battles\": \"海战次数\",\n            \"total_targets_destroyed\": \"共击毁目标\",\n            \"air_targets_destroyed\": \"击毁空中目标\",\n            \"ground_targets_destroyed\": \"击毁地面目标\",\n            \"naval_targets_destroyed\": \"击毁海上目标\",\n            \"ship_battles\": \"战舰次数\",\n            \"motor_torpedo_boat_battles\": \"鱼雷艇次数\",\n            \"motor_gun_boat_battles\": \"炮艇次数\",\n            \"motor_torpedo_gun_boat_battles\": \"鱼雷炮艇次数\",\n            \"sub_chaser_battles\": \"潜艇次数\",\n            \"destroyer_battles\": \"驱逐舰次数\",\n            \"naval_ferry_barge_battles\": \"浮船次数\",\n            \"time_played_naval\": \"海战时长\",\n            \"time_played_on_ship\": \"战舰时长\",\n            \"time_played_on_motor_torpedo_boat\": \"鱼雷艇时长\",\n            \"time_played_on_motor_gun_boat\": \"炮艇时长\",\n            \"time_played_on_motor_torpedo_gun_boat\": \"鱼雷炮艇时长\",\n            \"time_played_on_sub_chaser\": \"潜艇时长\",\n            \"time_played_on_destroyer\": \"驱逐舰时长\",\n            \"time_played_on_naval_ferry_barge\": \"浮船时长\"\n        }\n\n        # 娱乐街机\n        draw.text((80, 650), f\"娱乐街机:\", fill=\"black\", font=title_font)\n        y = 710\n        for key, value in titles.items():\n            draw.text((80, y), f\"{value}: {data['statistics']['arcade'][key]}\", fill=\"black\", font=normal_font)\n            y += 37\n\n        # 娱乐街机 - 空战\n        draw.text((400, 650), f\"街机-空战:\", fill=\"black\", font=title_font)\n        y = 710\n        for key, value in air_titles.items():\n            draw.text((400, y), f\"{value}: {data['statistics']['arcade']['aviation'][key]}\", fill=\"black\",\n                      font=normal_font)\n            y += 37\n\n        # 娱乐街机 - 陆战\n        draw.text((750, 650), f\"街机-陆战:\", fill=\"black\", font=title_font)\n        y = 710\n        for key, value in ground_titles.items():\n            draw.text((750, y), f\"{value}: {data['statistics']['arcade']['ground'][key]}\", fill=\"black\",\n                      font=normal_font)\n            y += 37\n\n        # 娱乐街机 - 海战\n        draw.text((1100, 650), f\"街机-海战:\", fill=\"black\", font=title_font)\n        x, y = 1100, 710\n        for key, value in naval_title.items():\n            draw.text((x, y), f\"{value}: {data['statistics']['arcade']['fleet'][key]}\", fill=\"black\", font=normal_font)\n            y += 37\n            if y > 1063:\n                x = 1400\n                y = 710\n\n        # 历史性能\n        draw.text((80, 1250), f\"历史性能:\", fill=\"black\", font=title_font)\n        y = 1310\n        for key, value in titles.items():\n            draw.text((80, y), f\"{value}: {data['statistics']['realistic'][key]}\", fill=\"black\", font=normal_font)\n            y += 37\n\n        # 历史性能 - 空战\n        draw.text((400, 1250), f\"空历:\", fill=\"black\", font=title_font)\n        y = 1310\n        for key, value in air_titles.items():\n            draw.text((400, y), f\"{value}: {data['statistics']['realistic']['aviation'][key]}\", fill=\"black\",\n                      font=normal_font)\n            y += 37\n\n        # 历史性能 - 陆战\n        draw.text((750, 1250), f\"陆历:\", fill=\"black\", font=title_font)\n        y = 1310\n        for key, value in ground_titles.items():\n            draw.text((750, y), f\"{value}: {data['statistics']['realistic']['ground'][key]}\", fill=\"black\",\n                      font=normal_font)\n            y += 37\n\n        # 历史性能 - 海战\n        draw.text((1100, 1250), f\"历史性能-海战:\", fill=\"black\", font=title_font)\n        x, y = 1100, 1310\n        for key, value in naval_title.items():\n            draw.text((x, y), f\"{value}: {data['statistics']['realistic']['fleet'][key]}\", fill=\"black\",\n                      font=normal_font)\n            y += 37\n            if y > 1663:\n                x = 1400\n                y = 1310\n\n        # 真实模拟\n        draw.text((80, 1850), f\"真实模拟:\", fill=\"black\", font=title_font)\n        y = 1910\n        for key, value in titles.items():\n            draw.text((80, y), f\"{value}: {data['statistics']['simulation'][key]}\", fill=\"black\", font=normal_font)\n            y += 37\n\n        # 真实模拟 - 空战\n        draw.text((400, 1850), f\"真实模拟-空战:\", fill=\"black\", font=title_font)\n        y = 1910\n        for key, value in air_titles.items():\n            draw.text((400, y), f\"{value}: {data['statistics']['realistic']['aviation'][key]}\", fill=\"black\",\n                      font=normal_font)\n            y += 37\n\n        # 真实模拟 - 陆战\n        draw.text((750, 1850), f\"真实模拟-陆战:\", fill=\"black\", font=title_font)\n        y = 1910\n        for key, value in ground_titles.items():\n            draw.text((750, y), f\"{value}: {data['statistics']['realistic']['ground'][key]}\", fill=\"black\",\n                      font=normal_font)\n            y += 37\n\n        # 真实模拟 - 海战\n        draw.text((1100, 1850), f\"真实模拟-海战:\", fill=\"black\", font=title_font)\n        x, y = 1100, 1910\n        for key, value in naval_title.items():\n            draw.text((x, y), f\"{value}: {data['statistics']['realistic']['fleet'][key]}\", fill=\"black\",\n                      font=normal_font)\n            y += 37\n            if y > 2263:\n                x = 1400\n                y = 1910\n\n        byte_array = io.BytesIO()\n        img.save(byte_array, \"PNG\")\n        return byte_array.getvalue()\n\n    @staticmethod\n    def _download_avatar(url: str) -> Image.Image:\n        try:\n            # 创建缓存目录\n            cache_dir = \"resource/images/avatar\"\n            os.makedirs(cache_dir, exist_ok=True)\n\n            # 使用URL的最后部分作为文件名\n            file_path = os.path.join(cache_dir, url.split('/')[-1])\n\n            # 检查缓存\n            if os.path.exists(file_path):\n                return Image.open(file_path)\n            else:\n                resp = requests.get(url)\n                with open(file_path, \"wb\") as f:\n                    f.write(resp.content)\n                return Image.open(file_path)\n        except:\n            return Image.new(\"RGBA\", (150, 150), (255, 255, 255, 255))\n\n    @staticmethod\n    def _show_actual(pct, allvals):\n        absolute = int(np.round(pct / 100. * sum(allvals)))  # 将百分比转换为实际值[3][9]\n        return f\"{absolute}\"\n"
  },
  {
    "path": "redis.conf",
    "content": "dir /var/lib/redis\nsave 60 1000\nappendonly yes\nappendfsync everysec"
  },
  {
    "path": "requirements.txt",
    "content": "loguru~=0.7.3\nAPScheduler~=3.11.0\naiohttp~=3.11.14\nfiletype~=1.2.0\npillow==10.4.0\npytz~=2025.1\ncaptcha~=0.7.1\ntabulate~=0.9.0\njieba~=0.42.1\nSQLAlchemy~=2.0.39\npydub~=0.25.1\nqrcode~=8.0\nmoviepy~=2.1.2\npysilk-mod~=1.6.4\nxywechatpad-binary==1.1.0\npymediainfo~=7.0.1\nmatplotlib~=3.10.1\nnumpy~=1.26.4\nrequests~=2.32.3\npydantic~=2.10.6\naiosqlite~=0.21.0\nFlask~=2.3.3\nFlask-Login~=0.6.3\nFlask-WTF~=1.2.1\nFlask-Session~=0.8.0\nFlask-SocketIO~=5.5.1\nWTForms~=3.2.1\nWerkzeug~=3.1.3\nBootstrap-Flask~=2.3.3\npython-dotenv~=1.0.1\ntomli~=2.2.1\ntomlkit~=0.13.2\neventlet~=0.39.1"
  },
  {
    "path": "utils/decorators.py",
    "content": "from functools import wraps\nfrom typing import Callable, Union\n\nfrom apscheduler.schedulers.asyncio import AsyncIOScheduler\nfrom apscheduler.triggers.cron import CronTrigger\nfrom apscheduler.triggers.interval import IntervalTrigger\n\nscheduler = AsyncIOScheduler()\n\n\ndef schedule(\n        trigger: Union[str, CronTrigger, IntervalTrigger],\n        **trigger_args\n) -> Callable:\n    \"\"\"\n    定时任务装饰器\n    \n    例子:\n\n    - @schedule('interval', seconds=30)\n    - @schedule('cron', hour=8, minute=30, second=30)\n    - @schedule('date', run_date='2024-01-01 00:00:00')\n    \"\"\"\n\n    def decorator(func: Callable):\n        job_id = f\"{func.__module__}.{func.__qualname__}\"\n\n        @wraps(func)\n        async def wrapper(self, *args, **kwargs):\n            return await func(self, *args, **kwargs)\n\n        setattr(wrapper, '_is_scheduled', True)\n        setattr(wrapper, '_schedule_trigger', trigger)\n        setattr(wrapper, '_schedule_args', trigger_args)\n        setattr(wrapper, '_job_id', job_id)\n\n        return wrapper\n\n    return decorator\n\n\ndef add_job_safe(scheduler: AsyncIOScheduler, job_id: str, func: Callable, bot,\n                 trigger: Union[str, CronTrigger, IntervalTrigger], **trigger_args):\n    \"\"\"添加函数到定时任务中，如果存在则先删除现有的任务\"\"\"\n    try:\n        scheduler.remove_job(job_id)\n    except:\n        pass\n    scheduler.add_job(func, trigger, args=[bot], id=job_id, **trigger_args)\n\n\ndef remove_job_safe(scheduler: AsyncIOScheduler, job_id: str):\n    \"\"\"从定时任务中移除任务\"\"\"\n    try:\n        scheduler.remove_job(job_id)\n    except:\n        pass\n\n\ndef on_text_message(priority=50):\n    \"\"\"文本消息装饰器\"\"\"\n\n    def decorator(func):\n        if callable(priority):  # 无参数调用时\n            f = priority\n            setattr(f, '_event_type', 'text_message')\n            setattr(f, '_priority', 50)\n            return f\n        # 有参数调用时\n        setattr(func, '_event_type', 'text_message')\n        setattr(func, '_priority', min(max(priority, 0), 99))\n        return func\n\n    return decorator if not callable(priority) else decorator(priority)\n\n\ndef on_image_message(priority=50):\n    \"\"\"图片消息装饰器\"\"\"\n\n    def decorator(func):\n        if callable(priority):\n            f = priority\n            setattr(f, '_event_type', 'image_message')\n            setattr(f, '_priority', 50)\n            return f\n        setattr(func, '_event_type', 'image_message')\n        setattr(func, '_priority', min(max(priority, 0), 99))\n        return func\n\n    return decorator if not callable(priority) else decorator(priority)\n\n\ndef on_voice_message(priority=50):\n    \"\"\"语音消息装饰器\"\"\"\n\n    def decorator(func):\n        if callable(priority):\n            f = priority\n            setattr(f, '_event_type', 'voice_message')\n            setattr(f, '_priority', 50)\n            return f\n        setattr(func, '_event_type', 'voice_message')\n        setattr(func, '_priority', min(max(priority, 0), 99))\n        return func\n\n    return decorator if not callable(priority) else decorator(priority)\n\n\ndef on_emoji_message(priority=50):\n    \"\"\"表情消息装饰器\"\"\"\n\n    def decorator(func):\n        if callable(priority):\n            f = priority\n            setattr(f, '_event_type', 'emoji_message')\n            setattr(f, '_priority', 50)\n            return f\n        setattr(func, '_event_type', 'emoji_message')\n        setattr(func, '_priority', min(max(priority, 0), 99))\n        return func\n\n    return decorator if not callable(priority) else decorator(priority)\n\n\ndef on_file_message(priority=50):\n    \"\"\"文件消息装饰器\"\"\"\n\n    def decorator(func):\n        if callable(priority):\n            f = priority\n            setattr(f, '_event_type', 'file_message')\n            setattr(f, '_priority', 50)\n            return f\n        setattr(func, '_event_type', 'file_message')\n        setattr(func, '_priority', min(max(priority, 0), 99))\n        return func\n\n    return decorator if not callable(priority) else decorator(priority)\n\n\ndef on_quote_message(priority=50):\n    \"\"\"引用消息装饰器\"\"\"\n\n    def decorator(func):\n        if callable(priority):\n            f = priority\n            setattr(f, '_event_type', 'quote_message')\n            setattr(f, '_priority', 50)\n            return f\n        setattr(func, '_event_type', 'quote_message')\n        setattr(func, '_priority', min(max(priority, 0), 99))\n        return func\n\n    return decorator if not callable(priority) else decorator(priority)\n\n\ndef on_video_message(priority=50):\n    \"\"\"视频消息装饰器\"\"\"\n\n    def decorator(func):\n        if callable(priority):\n            f = priority\n            setattr(f, '_event_type', 'video_message')\n            setattr(f, '_priority', 50)\n            return f\n        setattr(func, '_event_type', 'video_message')\n        setattr(func, '_priority', min(max(priority, 0), 99))\n        return func\n\n    return decorator if not callable(priority) else decorator(priority)\n\n\ndef on_pat_message(priority=50):\n    \"\"\"拍一拍消息装饰器\"\"\"\n\n    def decorator(func):\n        if callable(priority):\n            f = priority\n            setattr(f, '_event_type', 'pat_message')\n            setattr(f, '_priority', 50)\n            return f\n        setattr(func, '_event_type', 'pat_message')\n        setattr(func, '_priority', min(max(priority, 0), 99))\n        return func\n\n    return decorator if not callable(priority) else decorator(priority)\n\n\ndef on_at_message(priority=50):\n    \"\"\"被@消息装饰器\"\"\"\n\n    def decorator(func):\n        if callable(priority):\n            f = priority\n            setattr(f, '_event_type', 'at_message')\n            setattr(f, '_priority', 50)\n            return f\n        setattr(func, '_event_type', 'at_message')\n        setattr(func, '_priority', min(max(priority, 0), 99))\n        return func\n\n    return decorator if not callable(priority) else decorator(priority)\n\n\ndef on_system_message(priority=50):\n    \"\"\"其他消息装饰器\"\"\"\n\n    def decorator(func):\n        if callable(priority):\n            f = priority\n            setattr(f, '_event_type', 'system_message')\n            setattr(f, '_priority', 50)\n            return f\n        setattr(func, '_event_type', 'other_message')\n        setattr(func, '_priority', min(max(priority, 0), 99))\n        return func\n\n    return decorator if not callable(priority) else decorator(priority)\n\n\ndef on_other_message(priority=50):\n    \"\"\"其他消息装饰器\"\"\"\n\n    def decorator(func):\n        if callable(priority):\n            f = priority\n            setattr(f, '_event_type', 'other_message')\n            setattr(f, '_priority', 50)\n            return f\n        setattr(func, '_event_type', 'other_message')\n        setattr(func, '_priority', min(max(priority, 0), 99))\n        return func\n\n    return decorator if not callable(priority) else decorator(priority)\n"
  },
  {
    "path": "utils/event_manager.py",
    "content": "import copy\nfrom typing import Callable, Dict, List\n\n\nclass EventManager:\n    _handlers: Dict[str, List[tuple[Callable, object, int]]] = {}\n\n    @classmethod\n    def bind_instance(cls, instance: object):\n        \"\"\"将实例绑定到对应的事件处理函数\"\"\"\n        for method_name in dir(instance):\n            method = getattr(instance, method_name)\n            if hasattr(method, '_event_type'):\n                event_type = getattr(method, '_event_type')\n                priority = getattr(method, '_priority', 50)\n                \n                if event_type not in cls._handlers:\n                    cls._handlers[event_type] = []\n                cls._handlers[event_type].append((method, instance, priority))\n                # 按优先级排序，优先级高的在前\n                cls._handlers[event_type].sort(key=lambda x: x[2], reverse=True)\n\n    @classmethod\n    async def emit(cls, event_type: str, *args, **kwargs) -> None:\n        \"\"\"触发事件\"\"\"\n        if event_type not in cls._handlers:\n            return\n\n        api_client, message = args\n        for handler, instance, priority in cls._handlers[event_type]:\n            # 只对 message 进行深拷贝，api_client 保持不变\n            handler_args = (api_client, copy.deepcopy(message))\n            new_kwargs = {k: copy.deepcopy(v) for k, v in kwargs.items()}\n\n            result = await handler(*handler_args, **new_kwargs)\n\n            if isinstance(result, bool):\n                # True 继续执行 False 停止执行\n                if not result:\n                    break\n            else:\n                continue  # 我也不知道你返回了个啥玩意，反正继续执行就是了\n\n    @classmethod\n    def unbind_instance(cls, instance: object):\n        \"\"\"解绑实例的所有事件处理函数\"\"\"\n        for event_type in cls._handlers:\n            cls._handlers[event_type] = [\n                (handler, inst, priority)\n                for handler, inst, priority in cls._handlers[event_type]\n                if inst is not instance\n            ]\n"
  },
  {
    "path": "utils/plugin_base.py",
    "content": "from abc import ABC\n\nfrom loguru import logger\n\nfrom .decorators import scheduler, add_job_safe, remove_job_safe\n\n\nclass PluginBase(ABC):\n    \"\"\"插件基类\"\"\"\n\n    # 插件元数据\n    description: str = \"暂无描述\"\n    author: str = \"未知\"\n    version: str = \"1.0.0\"\n\n    def __init__(self):\n        self.enabled = False\n        self._scheduled_jobs = set()\n\n    async def on_enable(self, bot=None):\n        \"\"\"插件启用时调用\"\"\"\n\n        # 定时任务\n        for method_name in dir(self):\n            method = getattr(self, method_name)\n            if hasattr(method, '_is_scheduled'):\n                job_id = getattr(method, '_job_id')\n                trigger = getattr(method, '_schedule_trigger')\n                trigger_args = getattr(method, '_schedule_args')\n\n                add_job_safe(scheduler, job_id, method, bot, trigger, **trigger_args)\n                self._scheduled_jobs.add(job_id)\n        if self._scheduled_jobs:\n            logger.success(\"插件 {} 已加载定时任务: {}\", self.__class__.__name__, self._scheduled_jobs)\n\n    async def on_disable(self):\n        \"\"\"插件禁用时调用\"\"\"\n        \n        # 移除定时任务\n        for job_id in self._scheduled_jobs:\n            remove_job_safe(scheduler, job_id)\n        logger.info(\"已卸载定时任务: {}\", self._scheduled_jobs)\n        self._scheduled_jobs.clear()\n\n    async def async_init(self):\n        \"\"\"插件异步初始化\"\"\"\n        return\n"
  },
  {
    "path": "utils/plugin_manager.py",
    "content": "import importlib\nimport inspect\nimport os\nimport sys\nimport tomllib\nimport traceback\nfrom typing import Dict, Type, List, Union\n\nfrom loguru import logger\n\nfrom WechatAPI import WechatAPIClient\nfrom utils.singleton import Singleton\nfrom .event_manager import EventManager\nfrom .plugin_base import PluginBase\n\n\nclass PluginManager(metaclass=Singleton):\n    def __init__(self):\n        self.plugins: Dict[str, PluginBase] = {}\n        self.plugin_classes: Dict[str, Type[PluginBase]] = {}\n        self.plugin_info: Dict[str, dict] = {}  # 新增：存储所有插件信息\n\n        self.bot = None\n\n        with open(\"main_config.toml\", \"rb\") as f:\n            main_config = tomllib.load(f)\n\n        self.excluded_plugins = main_config[\"XYBot\"][\"disabled-plugins\"]\n\n    def set_bot(self, bot: WechatAPIClient):\n        self.bot = bot\n\n    async def load_plugin(self, plugin: Union[Type[PluginBase], str]) -> bool:\n        if isinstance(plugin, str):\n            return await self._load_plugin_name(plugin)\n        elif isinstance(plugin, type) and issubclass(plugin, PluginBase):\n            return await self._load_plugin_class(plugin)\n\n    async def _load_plugin_class(self, plugin_class: Type[PluginBase],\n                                 is_disabled: bool = False) -> bool:\n        \"\"\"加载单个插件，接受Type[PluginBase]\"\"\"\n        try:\n            plugin_name = plugin_class.__name__\n\n            # 防止重复加载插件\n            if plugin_name in self.plugins:\n                return False\n\n            # 安全获取插件目录名\n            directory = \"unknown\"\n            try:\n                module_name = plugin_class.__module__\n                if module_name.startswith(\"plugins.\"):\n                    directory = module_name.split('.')[1]\n                else:\n                    logger.warning(f\"非常规插件模块路径: {module_name}\")\n            except Exception as e:\n                logger.error(f\"获取插件目录失败: {e}\")\n                directory = \"error\"\n\n            # 记录插件信息，即使插件被禁用也会记录\n            self.plugin_info[plugin_name] = {\n                \"name\": plugin_name,\n                \"description\": plugin_class.description,\n                \"author\": plugin_class.author,\n                \"version\": plugin_class.version,\n                \"directory\": directory,\n                \"enabled\": False,\n                \"class\": plugin_class\n            }\n\n            # 如果插件被禁用则不加载\n            if is_disabled:\n                return False\n\n            plugin = plugin_class()\n            EventManager.bind_instance(plugin)\n            await plugin.on_enable(self.bot)\n            await plugin.async_init()\n            self.plugins[plugin_name] = plugin\n            self.plugin_classes[plugin_name] = plugin_class\n            self.plugin_info[plugin_name][\"enabled\"] = True\n            logger.success(f\"加载插件 {plugin_name} 成功\")\n            return True\n        except:\n            logger.error(f\"加载插件时发生错误: {traceback.format_exc()}\")\n            return False\n\n    async def _load_plugin_name(self, plugin_name: str) -> bool:\n        \"\"\"从plugins目录加载单个插件\n\n        Args:\n            plugin_name: 插件类名称（不是文件名）\n\n        Returns:\n            bool: 是否成功加载插件\n        \"\"\"\n        found = False\n        for dirname in os.listdir(\"plugins\"):\n            try:\n                if os.path.isdir(f\"plugins/{dirname}\") and os.path.exists(f\"plugins/{dirname}/main.py\"):\n                    module = importlib.import_module(f\"plugins.{dirname}.main\")\n                    importlib.reload(module)\n\n                    for name, obj in inspect.getmembers(module):\n                        if (inspect.isclass(obj) and\n                                issubclass(obj, PluginBase) and\n                                obj != PluginBase and\n                                obj.__name__ == plugin_name):\n                            found = True\n                            return await self._load_plugin_class(obj)\n            except:\n                logger.error(f\"检查 {dirname} 时发生错误: {traceback.format_exc()}\")\n                continue\n\n        if not found:\n            logger.warning(f\"未找到插件类 {plugin_name}\")\n\n    async def load_plugins(self, load_disabled: bool = True) -> Union[List[str], bool]:\n        loaded_plugins = []\n\n        for dirname in os.listdir(\"plugins\"):\n            if os.path.isdir(f\"plugins/{dirname}\") and os.path.exists(f\"plugins/{dirname}/main.py\"):\n                try:\n                    module = importlib.import_module(f\"plugins.{dirname}.main\")\n                    for name, obj in inspect.getmembers(module):\n                        if inspect.isclass(obj) and issubclass(obj, PluginBase) and obj != PluginBase:\n                            is_disabled = False\n                            if not load_disabled:\n                                is_disabled = obj.__name__ in self.excluded_plugins or dirname in self.excluded_plugins\n\n                            if await self._load_plugin_class(obj, is_disabled=is_disabled):\n                                loaded_plugins.append(obj.__name__)\n                except:\n                    logger.error(f\"加载 {dirname} 时发生错误: {traceback.format_exc()}\")\n\n        return loaded_plugins\n\n    async def unload_plugin(self, plugin_name: str) -> bool:\n        \"\"\"卸载单个插件\"\"\"\n        if plugin_name not in self.plugins:\n            return False\n\n        # 防止卸载 ManagePlugin\n        if plugin_name == \"ManagePlugin\":\n            logger.warning(\"ManagePlugin 不能被卸载\")\n            return False\n\n        try:\n            plugin = self.plugins[plugin_name]\n            await plugin.on_disable()\n            EventManager.unbind_instance(plugin)\n            del self.plugins[plugin_name]\n            del self.plugin_classes[plugin_name]\n            if plugin_name in self.plugin_info.keys():\n                self.plugin_info[plugin_name][\"enabled\"] = False\n            logger.success(f\"卸载插件 {plugin_name} 成功\")\n            return True\n        except:\n            logger.error(f\"卸载插件 {plugin_name} 时发生错误: {traceback.format_exc()}\")\n            return False\n\n    async def unload_plugins(self) -> tuple[List[str], List[str]]:\n        \"\"\"卸载所有插件\"\"\"\n        unloaded_plugins = []\n        failed_unloads = []\n        for plugin_name in list(self.plugins.keys()):\n            if await self.unload_plugin(plugin_name):\n                unloaded_plugins.append(plugin_name)\n            else:\n                failed_unloads.append(plugin_name)\n        return unloaded_plugins, failed_unloads\n\n    async def reload_plugin(self, plugin_name: str) -> bool:\n        \"\"\"重载单个插件\"\"\"\n        if plugin_name not in self.plugin_classes:\n            return False\n\n        # 防止重载 ManagePlugin\n        if plugin_name == \"ManagePlugin\":\n            logger.warning(\"ManagePlugin 不能被重载\")\n            return False\n\n        try:\n            # 获取插件类所在的模块\n            plugin_class = self.plugin_classes[plugin_name]\n            module_name = plugin_class.__module__\n\n            # 先卸载插件\n            if not await self.unload_plugin(plugin_name):\n                return False\n\n            # 重新导入模块\n            module = importlib.import_module(module_name)\n            importlib.reload(module)\n\n            # 从重新加载的模块中获取插件类\n            for name, obj in inspect.getmembers(module):\n                if (inspect.isclass(obj) and\n                        issubclass(obj, PluginBase) and\n                        obj != PluginBase and\n                        obj.__name__ == plugin_name):\n                    # 使用新的插件类而不是旧的\n                    return await self.load_plugin(obj)\n\n            return False\n        except Exception as e:\n            logger.error(f\"重载插件 {plugin_name} 时发生错误: {e}\")\n            return False\n\n    async def reload_plugins(self) -> List[str]:\n        \"\"\"重载所有插件\n        \n        Returns:\n            List[str]: 成功重载的插件名称列表\n        \"\"\"\n        try:\n            # 记录当前加载的插件名称，排除 ManagePlugin\n            original_plugins = [name for name in self.plugins.keys() if name != \"ManagePlugin\"]\n\n            # 卸载除 ManagePlugin 外的所有插件\n            for plugin_name in original_plugins:\n                await self.unload_plugin(plugin_name)\n\n            # 重新加载所有模块\n            for module_name in list(sys.modules.keys()):\n                if module_name.startswith('plugins.') and not module_name.endswith('ManagePlugin'):\n                    del sys.modules[module_name]\n\n            # 从目录重新加载插件\n            return await self.load_plugins()\n\n        except:\n            logger.error(f\"重载所有插件时发生错误: {traceback.format_exc()}\")\n            return []\n\n    async def refresh_plugins(self):\n        for dirname in os.listdir(\"plugins\"):\n            try:\n                dirpath = f\"plugins/{dirname}\"\n                if os.path.isdir(dirpath) and os.path.exists(f\"{dirpath}/main.py\"):\n                    # 验证目录名合法性\n                    if not dirname.isidentifier():\n                        logger.warning(f\"跳过非法插件目录名: {dirname}\")\n                        continue\n\n                    module = importlib.import_module(f\"plugins.{dirname}.main\")\n                    importlib.reload(module)\n\n                    for name, obj in inspect.getmembers(module):\n                        if inspect.isclass(obj) and issubclass(obj, PluginBase) and obj != PluginBase:\n                            if obj.__name__ not in self.plugin_info.keys():\n                                self.plugin_info[obj.__name__] = {\n                                    \"name\": obj.__name__,\n                                    \"description\": obj.description,\n                                    \"author\": obj.author,\n                                    \"version\": obj.version,\n                                    \"directory\": dirname,\n                                    \"enabled\": False,\n                                    \"class\": obj\n                                }\n            except:\n                logger.error(f\"检查 {dirname} 时发生错误: {traceback.format_exc()}\")\n                continue\n\n    def get_plugin_info(self, plugin_name: str = None) -> Union[dict, List[dict]]:\n        \"\"\"获取插件信息\n        \n        Args:\n            plugin_name: 插件名称，如果为None则返回所有插件信息\n            \n        Returns:\n            如果指定插件名，返回单个插件信息字典；否则返回所有插件信息列表\n        \"\"\"\n        if plugin_name:\n            return self.plugin_info.get(plugin_name)\n        return list(self.plugin_info.values())\n"
  },
  {
    "path": "utils/singleton.py",
    "content": "class Singleton(type):\n    _instances = {}\n\n    def __call__(cls, *args, **kwargs):\n        if cls not in cls._instances:\n            cls._instances[cls] = super().__call__(*args, **kwargs)\n        return cls._instances[cls]\n\n    @classmethod\n    def reset_instance(mcs, cls):\n        \"\"\"重置指定类的单例实例\"\"\"\n        if cls in mcs._instances:\n            del mcs._instances[cls]\n\n    @classmethod\n    def reset_all(mcs):\n        \"\"\"重置所有单例实例\"\"\"\n        mcs._instances.clear()\n"
  },
  {
    "path": "utils/xybot.py",
    "content": "import tomllib\nimport xml.etree.ElementTree as ET\nfrom typing import Dict, Any\n\nfrom loguru import logger\n\nfrom WechatAPI import WechatAPIClient\nfrom WechatAPI.Client.protect import protector\nfrom database.keyvalDB import KeyvalDB\nfrom database.messsagDB import MessageDB\nfrom utils.event_manager import EventManager\n\n\nclass XYBot:\n    def __init__(self, bot_client: WechatAPIClient):\n        self.bot = bot_client\n        self.wxid = None\n        self.nickname = None\n        self.alias = None\n        self.phone = None\n\n        with open(\"main_config.toml\", \"rb\") as f:\n            main_config = tomllib.load(f)\n\n        self.ignore_protection = main_config.get(\"XYBot\", {}).get(\"ignore-protection\", False)\n\n        self.ignore_mode = main_config.get(\"XYBot\", {}).get(\"ignore-mode\", \"\")\n        self.whitelist = main_config.get(\"XYBot\", {}).get(\"whitelist\", [])\n        self.blacklist = main_config.get(\"XYBot\", {}).get(\"blacklist\", [])\n\n        self.msg_db = MessageDB()\n        self.key_db = KeyvalDB()\n\n\n    def update_profile(self, wxid: str, nickname: str, alias: str, phone: str):\n        \"\"\"更新机器人信息\"\"\"\n        self.wxid = wxid\n        self.nickname = nickname\n        self.alias = alias\n        self.phone = phone\n\n    async def process_message(self, message: Dict[str, Any]):\n        \"\"\"处理接收到的消息\"\"\"\n\n        # 数据库消息数+1先\n        msg_count = int(await self.key_db.get(\"messages\") or 0) + 1\n        await self.key_db.set(\"messages\", str(msg_count))\n        \n        # 同时更新WebUI使用的消息计数键\n        await self.key_db.set(\"bot:stats:message_count\", str(msg_count))\n\n        msg_type = message.get(\"MsgType\")\n\n        # 预处理消息\n        message[\"FromWxid\"] = message.get(\"FromUserName\").get(\"string\")\n        message.pop(\"FromUserName\")\n        message[\"ToWxid\"] = message.get(\"ToWxid\").get(\"string\")\n\n        # 处理一下自己发的消息\n        if message[\"FromWxid\"] == self.wxid and message[\"ToWxid\"].endswith(\"@chatroom\"):  # 自己发发到群聊\n            # 由于是自己发送的消息，所以对于自己来说，From和To是反的\n            message[\"FromWxid\"], message[\"ToWxid\"] = message[\"ToWxid\"], message[\"FromWxid\"]\n\n\n        # 根据消息类型触发不同的事件\n        if msg_type == 1:  # 文本消息\n            await self.process_text_message(message)\n\n        elif msg_type == 3:  # 图片消息\n            await self.process_image_message(message)\n\n        elif msg_type == 34:  # 语音消息\n            await self.process_voice_message(message)\n\n        elif msg_type == 43:  # 视频消息\n            await self.process_video_message(message)\n\n        elif msg_type == 49:  # xml消息\n            await self.process_xml_message(message)\n\n        elif msg_type == 10002:  # 系统消息\n            await self.process_system_message(message)\n\n        elif msg_type == 37:  # 好友请求\n            if self.ignore_protection or not protector.check(14400):\n                await EventManager.emit(\"friend_request\", self.bot, message)\n            else:\n                logger.warning(\"风控保护: 新设备登录后4小时内请挂机\")\n\n        elif msg_type == 51:\n            pass\n\n        else:\n            logger.info(\"未知的消息类型: {}\", message)\n\n        # 可以继续添加更多消息类型的处理\n\n    async def process_text_message(self, message: Dict[str, Any]):\n        \"\"\"处理文本消息\"\"\"\n        # 预处理消息\n        message[\"Content\"] = message.get(\"Content\").get(\"string\")\n\n        if message[\"FromWxid\"].endswith(\"@chatroom\"):  # 群聊消息\n            message[\"IsGroup\"] = True\n            split_content = message[\"Content\"].split(\":\\n\", 1)\n            if len(split_content) > 1:\n                message[\"Content\"] = split_content[1]\n                message[\"SenderWxid\"] = split_content[0]\n            else:  # 绝对是自己发的消息! qwq\n                message[\"Content\"] = split_content[0]\n                message[\"SenderWxid\"] = self.wxid\n\n        else:\n            message[\"SenderWxid\"] = message[\"FromWxid\"]\n            if message[\"FromWxid\"] == self.wxid:  # 自己发的消息\n                message[\"FromWxid\"] = message[\"ToWxid\"]\n            message[\"IsGroup\"] = False\n\n        try:\n            root = ET.fromstring(message[\"MsgSource\"])\n            ats = root.find(\"atuserlist\").text if root.find(\"atuserlist\") is not None else \"\"\n        except Exception as e:\n            logger.error(\"解析文本消息失败: {}\", e)\n            return\n\n        if ats:\n            ats = ats.strip(\",\").split(\",\")\n        else:  # 修复\n            ats = []\n        message[\"Ats\"] = ats if ats and ats[0] != \"\" else []\n\n        # 保存消息到数据库\n        await self.msg_db.save_message(\n            msg_id=int(message[\"MsgId\"]),\n            sender_wxid=message[\"SenderWxid\"],\n            from_wxid=message[\"FromWxid\"],\n            msg_type=int(message[\"MsgType\"]),\n            content=message[\"Content\"],\n            is_group=message[\"IsGroup\"]\n        )\n\n        if self.wxid in ats:\n            logger.info(\"收到被@消息: 消息ID:{} 来自:{} 发送人:{} @:{} 内容:{}\",\n                        message[\"MsgId\"],\n                        message[\"FromWxid\"],\n                        message[\"SenderWxid\"],\n                        message[\"Ats\"],\n                        message[\"Content\"])\n\n            if self.ignore_check(message[\"FromWxid\"], message[\"SenderWxid\"]):\n                if self.ignore_protection or not protector.check(14400):\n                    await EventManager.emit(\"at_message\", self.bot, message)\n                else:\n                    logger.warning(\"风控保护: 新设备登录后4小时内请挂机\")\n            return\n\n        logger.info(\"收到文本消息: 消息ID:{} 来自:{} 发送人:{} @:{} 内容:{}\",\n                    message[\"MsgId\"],\n                    message[\"FromWxid\"],\n                    message[\"SenderWxid\"],\n                    message[\"Ats\"],\n                    message[\"Content\"])\n\n        if self.ignore_check(message[\"FromWxid\"], message[\"SenderWxid\"]):\n            if self.ignore_protection or not protector.check(14400):\n                await EventManager.emit(\"text_message\", self.bot, message)\n            else:\n                logger.warning(\"风控保护: 新设备登录后4小时内请挂机\")\n\n    async def process_image_message(self, message: Dict[str, Any]):\n        \"\"\"处理图片消息\"\"\"\n        # 预处理消息\n        message[\"Content\"] = message.get(\"Content\").get(\"string\").replace(\"\\n\", \"\").replace(\"\\t\", \"\")\n\n        if message[\"FromWxid\"].endswith(\"@chatroom\"):  # 群聊消息\n            message[\"IsGroup\"] = True\n            split_content = message[\"Content\"].split(\":\", 1)\n            if len(split_content) > 1:\n                message[\"Content\"] = split_content[1]\n                message[\"SenderWxid\"] = split_content[0]\n            else:  # 绝对是自己发的消息! qwq\n                message[\"Content\"] = split_content[0]\n                message[\"SenderWxid\"] = self.wxid\n        else:\n            message[\"SenderWxid\"] = message[\"FromWxid\"]\n            if message[\"FromWxid\"] == self.wxid:  # 自己发的消息\n                message[\"FromWxid\"] = message[\"ToWxid\"]\n            message[\"IsGroup\"] = False\n\n        logger.info(\"收到图片消息: 消息ID:{} 来自:{} 发送人:{} XML:{}\",\n                    message[\"MsgId\"],\n                    message[\"FromWxid\"],\n                    message[\"SenderWxid\"],\n                    message[\"Content\"])\n\n        await self.msg_db.save_message(\n            msg_id=int(message[\"MsgId\"]),\n            sender_wxid=message[\"SenderWxid\"],\n            from_wxid=message[\"FromWxid\"],\n            msg_type=int(message[\"MsgType\"]),\n            content=message[\"MsgSource\"],\n            is_group=message[\"IsGroup\"]\n        )\n\n        # 解析图片消息\n        aeskey, cdnmidimgurl = None, None\n        try:\n            root = ET.fromstring(message[\"Content\"])\n            img_element = root.find('img')\n            if img_element is not None:\n                aeskey = img_element.get('aeskey')\n                cdnmidimgurl = img_element.get('cdnmidimgurl')\n        except Exception as e:\n            logger.error(\"解析图片消息失败: {}\", e)\n            return\n\n        # 下载图片\n        if aeskey and cdnmidimgurl:\n            message[\"Content\"] = await self.bot.download_image(aeskey, cdnmidimgurl)\n\n        if self.ignore_check(message[\"FromWxid\"], message[\"SenderWxid\"]):\n            if self.ignore_protection or not protector.check(14400):\n                await EventManager.emit(\"image_message\", self.bot, message)\n            else:\n                logger.warning(\"风控保护: 新设备登录后4小时内请挂机\")\n\n    async def process_voice_message(self, message: Dict[str, Any]):\n        \"\"\"处理语音消息\"\"\"\n        # 预处理消息\n        message[\"Content\"] = message.get(\"Content\").get(\"string\").replace(\"\\n\", \"\").replace(\"\\t\", \"\")\n\n        if message[\"FromWxid\"].endswith(\"@chatroom\"):  # 群聊消息\n            message[\"IsGroup\"] = True\n            split_content = message[\"Content\"].split(\":\", 1)\n            if len(split_content) > 1:\n                message[\"Content\"] = split_content[1]\n                message[\"SenderWxid\"] = split_content[0]\n            else:  # 绝对是自己发的消息! qwq\n                message[\"Content\"] = split_content[0]\n                message[\"SenderWxid\"] = self.wxid\n        else:\n            message[\"SenderWxid\"] = message[\"FromWxid\"]\n            if message[\"FromWxid\"] == self.wxid:  # 自己发的消息\n                message[\"FromWxid\"] = message[\"ToWxid\"]\n            message[\"IsGroup\"] = False\n\n        logger.info(\"收到语音消息: 消息ID:{} 来自:{} 发送人:{} XML:{}\",\n                    message[\"MsgId\"],\n                    message[\"FromWxid\"],\n                    message[\"SenderWxid\"],\n                    message[\"Content\"])\n\n        await self.msg_db.save_message(\n            msg_id=int(message[\"MsgId\"]),\n            sender_wxid=message[\"SenderWxid\"],\n            from_wxid=message[\"FromWxid\"],\n            msg_type=int(message[\"MsgType\"]),\n            content=message[\"Content\"],\n            is_group=message[\"IsGroup\"]\n        )\n\n        if message[\"IsGroup\"] or not message.get(\"ImgBuf\", {}).get(\"buffer\", \"\"):\n            # 解析语音消息\n            voiceurl, length = None, None\n            try:\n                root = ET.fromstring(message[\"Content\"])\n                voicemsg_element = root.find('voicemsg')\n                if voicemsg_element is not None:\n                    voiceurl = voicemsg_element.get('voiceurl')\n                    length = int(voicemsg_element.get('length'))\n            except Exception as e:\n                logger.error(\"解析语音消息失败: {}\", e)\n                return\n\n            # 下载语音\n            if voiceurl and length:\n                silk_base64 = await self.bot.download_voice(message[\"MsgId\"], voiceurl, length)\n                message[\"Content\"] = await self.bot.silk_base64_to_wav_byte(silk_base64)\n        else:\n            silk_base64 = message[\"ImgBuf\"][\"buffer\"]\n            message[\"Content\"] = await self.bot.silk_base64_to_wav_byte(silk_base64)\n\n        if self.ignore_check(message[\"FromWxid\"], message[\"SenderWxid\"]):\n            if self.ignore_protection or not protector.check(14400):\n                await EventManager.emit(\"voice_message\", self.bot, message)\n            else:\n                logger.warning(\"风控保护: 新设备登录后4小时内请挂机\")\n\n    async def process_xml_message(self, message: Dict[str, Any]):\n        \"\"\"处理xml消息\"\"\"\n        message[\"Content\"] = message.get(\"Content\").get(\"string\").replace(\"\\n\", \"\").replace(\"\\t\", \"\")\n\n        if message[\"FromWxid\"].endswith(\"@chatroom\"):  # 群聊消息\n            message[\"IsGroup\"] = True\n            split_content = message[\"Content\"].split(\":\", 1)\n            if len(split_content) > 1:\n                message[\"Content\"] = split_content[1]\n                message[\"SenderWxid\"] = split_content[0]\n            else:  # 绝对是自己发的消息! qwq\n                message[\"Content\"] = split_content[0]\n                message[\"SenderWxid\"] = self.wxid\n        else:\n            message[\"SenderWxid\"] = message[\"FromWxid\"]\n            if message[\"FromWxid\"] == self.wxid:  # 自己发的消息\n                message[\"FromWxid\"] = message[\"ToWxid\"]\n            message[\"IsGroup\"] = False\n\n        await self.msg_db.save_message(\n            msg_id=int(message[\"MsgId\"]),\n            sender_wxid=message[\"SenderWxid\"],\n            from_wxid=message[\"FromWxid\"],\n            msg_type=int(message[\"MsgType\"]),\n            content=message[\"Content\"],\n            is_group=message[\"IsGroup\"]\n        )\n\n        try:\n            root = ET.fromstring(message[\"Content\"])\n            type = int(root.find(\"appmsg\").find(\"type\").text)\n        except Exception as e:\n            logger.error(f\"解析xml消息失败: {e}\")\n            return\n\n        if type == 57:\n            await self.process_quote_message(message)\n        elif type == 6:\n            await self.process_file_message(message)\n        elif type == 74:  # 文件消息，但还在上传，不用管\n            pass\n\n        else:\n            logger.info(\"未知的xml消息类型: {}\", message)\n\n    async def process_quote_message(self, message: Dict[str, Any]):\n        \"\"\"处理引用消息\"\"\"\n        quote_messsage = {}\n        try:\n            root = ET.fromstring(message[\"Content\"])\n            appmsg = root.find(\"appmsg\")\n            text = appmsg.find(\"title\").text\n            refermsg = appmsg.find(\"refermsg\")\n\n            quote_messsage[\"MsgType\"] = int(refermsg.find(\"type\").text)\n\n            if quote_messsage[\"MsgType\"] == 1:  # 文本消息\n                quote_messsage[\"NewMsgId\"] = refermsg.find(\"svrid\").text\n                quote_messsage[\"ToWxid\"] = refermsg.find(\"fromusr\").text\n                quote_messsage[\"FromWxid\"] = refermsg.find(\"chatusr\").text\n                quote_messsage[\"Nickname\"] = refermsg.find(\"displayname\").text\n                quote_messsage[\"MsgSource\"] = refermsg.find(\"msgsource\").text\n                quote_messsage[\"Content\"] = refermsg.find(\"content\").text\n                quote_messsage[\"Createtime\"] = refermsg.find(\"createtime\").text\n\n            elif quote_messsage[\"MsgType\"] == 49:  # 引用消息\n                quote_messsage[\"NewMsgId\"] = refermsg.find(\"svrid\").text\n                quote_messsage[\"ToWxid\"] = refermsg.find(\"fromusr\").text\n                quote_messsage[\"FromWxid\"] = refermsg.find(\"chatusr\").text\n                quote_messsage[\"Nickname\"] = refermsg.find(\"displayname\").text\n                quote_messsage[\"MsgSource\"] = refermsg.find(\"msgsource\").text\n                quote_messsage[\"Createtime\"] = refermsg.find(\"createtime\").text\n\n                quote_messsage[\"Content\"] = refermsg.find(\"content\").text\n\n                quote_root = ET.fromstring(quote_messsage[\"Content\"])\n                quote_appmsg = quote_root.find(\"appmsg\")\n\n                quote_messsage[\"Content\"] = quote_appmsg.find(\"title\").text if isinstance(quote_appmsg.find(\"title\"),\n                                                                                          ET.Element) else \"\"\n                quote_messsage[\"destination\"] = quote_appmsg.find(\"des\").text if isinstance(quote_appmsg.find(\"des\"),\n                                                                                            ET.Element) else \"\"\n                quote_messsage[\"action\"] = quote_appmsg.find(\"action\").text if isinstance(quote_appmsg.find(\"action\"),\n                                                                                          ET.Element) else \"\"\n                quote_messsage[\"XmlType\"] = int(quote_appmsg.find(\"type\").text) if isinstance(quote_appmsg.find(\"type\"),\n                                                                                              ET.Element) else 0\n                quote_messsage[\"showtype\"] = int(quote_appmsg.find(\"showtype\").text) if isinstance(\n                    quote_appmsg.find(\"showtype\"), ET.Element) else 0\n                quote_messsage[\"soundtype\"] = int(quote_appmsg.find(\"soundtype\").text) if isinstance(\n                    quote_appmsg.find(\"soundtype\"), ET.Element) else 0\n                quote_messsage[\"url\"] = quote_appmsg.find(\"url\").text if isinstance(quote_appmsg.find(\"url\"),\n                                                                                    ET.Element) else \"\"\n                quote_messsage[\"lowurl\"] = quote_appmsg.find(\"lowurl\").text if isinstance(quote_appmsg.find(\"lowurl\"),\n                                                                                          ET.Element) else \"\"\n                quote_messsage[\"dataurl\"] = quote_appmsg.find(\"dataurl\").text if isinstance(\n                    quote_appmsg.find(\"dataurl\"), ET.Element) else \"\"\n                quote_messsage[\"lowdataurl\"] = quote_appmsg.find(\"lowdataurl\").text if isinstance(\n                    quote_appmsg.find(\"lowdataurl\"), ET.Element) else \"\"\n                quote_messsage[\"songlyric\"] = quote_appmsg.find(\"songlyric\").text if isinstance(\n                    quote_appmsg.find(\"songlyric\"), ET.Element) else \"\"\n                quote_messsage[\"appattach\"] = {}\n                quote_messsage[\"appattach\"][\"totallen\"] = int(\n                    quote_appmsg.find(\"appattach\").find(\"totallen\").text) if isinstance(\n                    quote_appmsg.find(\"appattach\").find(\"totallen\"), ET.Element) else 0\n                quote_messsage[\"appattach\"][\"attachid\"] = quote_appmsg.find(\"appattach\").find(\n                    \"attachid\").text if isinstance(quote_appmsg.find(\"appattach\").find(\"attachid\"), ET.Element) else \"\"\n                quote_messsage[\"appattach\"][\"emoticonmd5\"] = quote_appmsg.find(\"appattach\").find(\n                    \"emoticonmd5\").text if isinstance(quote_appmsg.find(\"appattach\").find(\"emoticonmd5\"),\n                                                      ET.Element) else \"\"\n                quote_messsage[\"appattach\"][\"fileext\"] = quote_appmsg.find(\"appattach\").find(\n                    \"fileext\").text if isinstance(quote_appmsg.find(\"appattach\").find(\"fileext\"), ET.Element) else \"\"\n                quote_messsage[\"appattach\"][\"cdnthumbaeskey\"] = quote_appmsg.find(\"appattach\").find(\n                    \"cdnthumbaeskey\").text if isinstance(quote_appmsg.find(\"appattach\").find(\"cdnthumbaeskey\"),\n                                                         ET.Element) else \"\"\n                quote_messsage[\"appattach\"][\"aeskey\"] = quote_appmsg.find(\"appattach\").find(\n                    \"aeskey\").text if isinstance(quote_appmsg.find(\"appattach\").find(\"aeskey\"), ET.Element) else \"\"\n                quote_messsage[\"extinfo\"] = quote_appmsg.find(\"extinfo\").text if isinstance(\n                    quote_appmsg.find(\"extinfo\"), ET.Element) else \"\"\n                quote_messsage[\"sourceusername\"] = quote_appmsg.find(\"sourceusername\").text if isinstance(\n                    quote_appmsg.find(\"sourceusername\"), ET.Element) else \"\"\n                quote_messsage[\"sourcedisplayname\"] = quote_appmsg.find(\"sourcedisplayname\").text if isinstance(\n                    quote_appmsg.find(\"sourcedisplayname\"), ET.Element) else \"\"\n                quote_messsage[\"thumburl\"] = quote_appmsg.find(\"thumburl\").text if isinstance(\n                    quote_appmsg.find(\"thumburl\"), ET.Element) else \"\"\n                quote_messsage[\"md5\"] = quote_appmsg.find(\"md5\").text if isinstance(quote_appmsg.find(\"md5\"),\n                                                                                    ET.Element) else \"\"\n                quote_messsage[\"statextstr\"] = quote_appmsg.find(\"statextstr\").text if isinstance(\n                    quote_appmsg.find(\"statextstr\"), ET.Element) else \"\"\n                quote_messsage[\"directshare\"] = int(quote_appmsg.find(\"directshare\").text) if isinstance(\n                    quote_appmsg.find(\"directshare\"), ET.Element) else 0\n\n        except Exception as e:\n            logger.error(f\"解析引用消息失败: {e}\")\n            return\n\n        message[\"Content\"] = text\n        message[\"Quote\"] = quote_messsage\n\n        logger.info(\"收到引用消息: 消息ID:{} 来自:{} 发送人:{}  内容:{} 引用:{}\",\n                    message.get(\"Msgid\", \"\"),\n                    message[\"FromWxid\"],\n                    message[\"SenderWxid\"],\n                    message[\"Content\"],\n                    message[\"Quote\"])\n\n        if self.ignore_check(message[\"FromWxid\"], message[\"SenderWxid\"]):\n            if self.ignore_protection or not protector.check(14400):\n                await EventManager.emit(\"quote_message\", self.bot, message)\n            else:\n                logger.warning(\"风控保护: 新设备登录后4小时内请挂机\")\n\n    async def process_video_message(self, message):\n        # 预处理消息\n        message[\"Content\"] = message.get(\"Content\").get(\"string\")\n\n        if message[\"FromWxid\"].endswith(\"@chatroom\"):  # 群聊消息\n            message[\"IsGroup\"] = True\n            split_content = message[\"Content\"].split(\":\", 1)\n            if len(split_content) > 1:\n                message[\"Content\"] = split_content[1]\n                message[\"SenderWxid\"] = split_content[0]\n            else:  # 绝对是自己发的消息! qwq\n                message[\"Content\"] = split_content[0]\n                message[\"SenderWxid\"] = self.wxid\n        else:\n            message[\"SenderWxid\"] = message[\"FromWxid\"]\n            if message[\"FromWxid\"] == self.wxid:  # 自己发的消息\n                message[\"FromWxid\"] = message[\"ToWxid\"]\n            message[\"IsGroup\"] = False\n\n        logger.info(\"收到视频消息: 消息ID:{} 来自:{} 发送人:{} XML:{}\",\n                    message[\"MsgId\"],\n                    message[\"FromWxid\"],\n                    message[\"SenderWxid\"],\n                    str(message[\"Content\"]).replace(\"\\n\", \"\"))\n\n        await self.msg_db.save_message(\n            msg_id=int(message[\"MsgId\"]),\n            sender_wxid=message[\"SenderWxid\"],\n            from_wxid=message[\"FromWxid\"],\n            msg_type=int(message[\"MsgType\"]),\n            content=message[\"Content\"],\n            is_group=message[\"IsGroup\"]\n        )\n\n        message[\"Video\"] = await self.bot.download_video(message[\"MsgId\"])\n\n        if self.ignore_check(message[\"FromWxid\"], message[\"SenderWxid\"]):\n            if self.ignore_protection or not protector.check(14400):\n                await EventManager.emit(\"video_message\", self.bot, message)\n            else:\n                logger.warning(\"风控保护: 新设备登录后4小时内请挂机\")\n\n    async def process_file_message(self, message: Dict[str, Any]):\n        \"\"\"处理文件消息\"\"\"\n        try:\n            root = ET.fromstring(message[\"Content\"])\n            filename = root.find(\"appmsg\").find(\"title\").text\n            attach_id = root.find(\"appmsg\").find(\"appattach\").find(\"attachid\").text\n            file_extend = root.find(\"appmsg\").find(\"appattach\").find(\"fileext\").text\n        except Exception as error:\n            logger.error(f\"解析文件消息失败: {error}\")\n            return\n\n        message[\"Filename\"] = filename\n        message[\"FileExtend\"] = file_extend\n\n        logger.info(\"收到文件消息: 消息ID:{} 来自:{} 发送人:{} XML:{}\",\n                    message[\"MsgId\"],\n                    message[\"FromWxid\"],\n                    message[\"SenderWxid\"],\n                    message[\"Content\"])\n\n        await self.msg_db.save_message(\n            msg_id=int(message[\"MsgId\"]),\n            sender_wxid=message[\"SenderWxid\"],\n            from_wxid=message[\"FromWxid\"],\n            msg_type=int(message[\"MsgType\"]),\n            content=message[\"Content\"],\n            is_group=message[\"IsGroup\"]\n        )\n\n        message[\"File\"] = await self.bot.download_attach(attach_id)\n\n        if self.ignore_check(message[\"FromWxid\"], message[\"SenderWxid\"]):\n            if self.ignore_protection or not protector.check(14400):\n                await EventManager.emit(\"file_message\", self.bot, message)\n            else:\n                logger.warning(\"风控保护: 新设备登录后4小时内请挂机\")\n\n    async def process_system_message(self, message: Dict[str, Any]):\n        \"\"\"处理系统消息\"\"\"\n        # 预处理消息\n        message[\"Content\"] = message.get(\"Content\").get(\"string\")\n\n        if message[\"FromWxid\"].endswith(\"@chatroom\"):  # 群聊消息\n            message[\"IsGroup\"] = True\n            split_content = message[\"Content\"].split(\":\", 1)\n            if len(split_content) > 1:\n                message[\"Content\"] = split_content[1]\n                message[\"SenderWxid\"] = split_content[0]\n            else:  # 绝对是自己发的消息! qwq\n                message[\"Content\"] = split_content[0]\n                message[\"SenderWxid\"] = self.wxid\n        else:\n            message[\"SenderWxid\"] = message[\"FromWxid\"]\n            if message[\"FromWxid\"] == self.wxid:  # 自己发的消息\n                message[\"FromWxid\"] = message[\"ToWxid\"]\n            message[\"IsGroup\"] = False\n\n        try:\n            root = ET.fromstring(message[\"Content\"])\n            msg_type = root.attrib[\"type\"]\n        except Exception as e:\n            logger.error(f\"解析系统消息失败: {e}\")\n            return\n\n        if msg_type == \"pat\":\n            await self.process_pat_message(message)\n        elif msg_type == \"ClientCheckGetExtInfo\":\n            pass\n        else:\n            logger.info(\"收到系统消息: {}\", message)\n            if self.ignore_check(message[\"FromWxid\"], message[\"SenderWxid\"]):\n                if self.ignore_protection or not protector.check(14400):\n                    await EventManager.emit(\"system_message\", self.bot, message)\n                else:\n                    logger.warning(\"风控保护: 新设备登录后4小时内请挂机\")\n\n    async def process_pat_message(self, message: Dict[str, Any]):\n        \"\"\"处理拍一拍请求消息\"\"\"\n        try:\n            root = ET.fromstring(message[\"Content\"])\n            pat = root.find(\"pat\")\n            patter = pat.find(\"fromusername\").text\n            patted = pat.find(\"pattedusername\").text\n            pat_suffix = pat.find(\"patsuffix\").text\n        except Exception as e:\n            logger.error(f\"解析拍一拍消息失败: {e}\")\n            return\n\n        message[\"Patter\"] = patter\n        message[\"Patted\"] = patted\n        message[\"PatSuffix\"] = pat_suffix\n\n        logger.info(\"收到拍一拍消息: 消息ID:{} 来自:{} 发送人:{} 拍者:{} 被拍:{} 后缀:{}\",\n                    message[\"MsgId\"],\n                    message[\"FromWxid\"],\n                    message[\"SenderWxid\"],\n                    message[\"Patter\"],\n                    message[\"Patted\"],\n                    message[\"PatSuffix\"])\n\n        await self.msg_db.save_message(\n            msg_id=int(message[\"MsgId\"]),\n            sender_wxid=message[\"SenderWxid\"],\n            from_wxid=message[\"FromWxid\"],\n            msg_type=int(message[\"MsgType\"]),\n            content=f\"{message['Patter']} 拍了拍 {message['Patted']} {message['PatSuffix']}\",\n            is_group=message[\"IsGroup\"]\n        )\n\n        if self.ignore_check(message[\"FromWxid\"], message[\"SenderWxid\"]):\n            if self.ignore_protection or not protector.check(14400):\n                await EventManager.emit(\"pat_message\", self.bot, message)\n            else:\n                logger.warning(\"风控保护: 新设备登录后4小时内请挂机\")\n\n    def ignore_check(self, FromWxid: str, SenderWxid: str):\n        if self.ignore_mode == \"Whitelist\":\n            return (FromWxid in self.whitelist) or (SenderWxid in self.whitelist)\n        elif self.ignore_mode == \"blacklist\":\n            return (FromWxid not in self.blacklist) and (SenderWxid not in self.blacklist)\n        else:\n            return True\n"
  }
]