[
  {
    "path": ".dockerignore",
    "content": ".github/workflows/build-image.yml\n.git\n.gitignore\n.dockerignore\nREADME.md\nLICENSE\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.yml",
    "content": "name: 🐛 Bug report\ndescription: 创建Bug报告以帮助项目改进。\ntitle: 🐛[BUG] 请输入标题\nlabels: bug\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        📝 **请在上方的`title`中填写一个简洁明了的标题**，格式建议为：🐛[Bug] 简短描述。\n        例如：🐛[Bug] B站某些直播间无法录制。\n  - type: checkboxes\n    attributes:\n      label: ⚠️ 确认是否已存在类似问题\n      description: >\n        🔍 [点击这里搜索历史issue](https://github.com/ihmily/DouyinLiveRecorder/issues?q=is%3Aissue) \n        请确保你的问题没有被报告过。\n      options:\n        - label: 我已经搜索过issues，没有找到类似问题\n          required: true\n  - type: dropdown\n    attributes:\n      label: 🔧 运行方式\n      description: 请选择你是如何运行程序的。\n      options:\n        - 直接运行的exe文件\n        - 使用源代码运行\n        - 使用docker运行\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: 🐍 如果是使用源代码运行，请选择你的Python环境版本\n      description: 请选择你运行程序的Python版本。\n      options:\n        - Python 3.10\n        - Python 3.11\n        - Python 3.12\n        - Python 3.13\n        - Other (请在问题中说明)\n    validations:\n      required: false\n  - type: dropdown\n    attributes:\n      label: 💻 请选择你的系统环境\n      description: 请选择你运行程序的具体系统版本。\n      options:\n        - Windows 10\n        - Windows 11\n        - macOS\n        - Ubuntu\n        - CentOS\n        - Fedora\n        - Debian\n        - Other (请在问题中说明)\n    validations:\n      required: true\n  - type: checkboxes\n    attributes:\n      label: ⚠️ 确认是否已经重试多次\n      description: >\n        有时可能是你的设备或者网络问题导致的。\n      options:\n        - label: 我已经尝试过多次，仍然出现问题\n          required: true\n  - type: textarea\n    attributes:\n      label: 🕹 复现步骤\n      description: |\n        **⚠️ 不能复现将会关闭issue.**\n        请按照以下格式填写：\n        1. 录制的直播间地址是...\n        2. 使用的录制格式是...\n        3. ...\n      placeholder: |\n        1. ...\n        2. ...\n        3. ...\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: 😯 问题描述\n      description: 详细描述出现的问题，或提供有关截图。\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: 📜 错误信息\n      description: 如果有，请贴出相关的日志错误信息或者截图。\n    validations:\n      required: false"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_en.yml",
    "content": "name: 🐛 (English)Bug report\ndescription: Create a bug report to help improve the project.\ntitle: 🐛[BUG] Please enter a title\nlabels: bug\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        📝 **Please fill in a concise and clear title in the `title` above**, the format is suggested as: 🐛[Bug] Short description.\n        For example: 🐛[Bug] Unable to record certain TikTok live rooms.\n  - type: checkboxes\n    attributes:\n      label: ⚠️ Confirm if similar issues exist\n      description: >\n        🔍 [Click here to search historical issues](https://github.com/ihmily/DouyinLiveRecorder/issues?q=is%3Aissue)  \n        Please make sure your issue hasn't been reported before.\n      options:\n        - label: I have searched the issues and found no similar problems\n          required: true\n  - type: dropdown\n    attributes:\n      label: 🔧 How did you run the program?\n      description: Please select how you ran the program.\n      options:\n        - Directly running the exe file\n        - Running with source code\n        - Running with docker\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: 🐍 If running with source code, please select your Python environment version\n      description: Please select the Python version you used to run the program.\n      options:\n        - Python 3.10\n        - Python 3.11\n        - Python 3.12\n        - Python 3.13\n        - Other (please specify in the issue)\n    validations:\n      required: false\n  - type: dropdown\n    attributes:\n      label: 💻 Please select your system environment\n      description: Please select the specific system version you are running the program on.\n      options:\n        - Windows 10\n        - Windows 11\n        - macOS\n        - Ubuntu\n        - CentOS\n        - Fedora\n        - Debian\n        - Other (please specify in the issue)\n    validations:\n      required: true\n  - type: checkboxes\n    attributes:\n      label: ⚠️ Confirm if you have retried multiple times\n      description: >\n        Sometimes it might be due to your device or network issues.\n      options:\n        - label: I have tried multiple times and still encounter the problem\n          required: true\n  - type: textarea\n    attributes:\n      label: 🕹 Reproduction steps\n      description: |\n        **⚠️ Issues that cannot be reproduced will be closed.**\n        Please fill in according to the following format:\n        1. The live room address I tried to record is...\n        2. The recording format I used is...\n        3. ...\n      placeholder: |\n        1. ...\n        2. ...\n        3. ...\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: 😯 Problem description\n      description: Describe the problem in detail or provide relevant screenshots.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: 📜 Error information\n      description: If available, please paste the relevant log error information or screenshots.\n    validations:\n      required: false"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature.yml",
    "content": "name: 🚀 Feature request\ndescription: 提出你对项目的新想法或建议。\ntitle: 🚀[Feature] 请输入标题\nlabels: enhancement\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        📝 **请在上方的`title`中填写一个简洁明了的标题**，格式建议为：🚀[Feature] 简短描述。\n        例如：🚀[Feature] 添加xx直播录制。\n  - type: checkboxes\n    attributes:\n      label: ⚠️ 搜索是否存在类似issue\n      description: >\n        🔍 [点击这里搜索历史issue](https://github.com/ihmily/DouyinLiveRecorder/issues?q=is%3Aissue) 使用关键词搜索，确保没有重复的issue。\n      options:\n        - label: 我已经搜索过issues，没有发现相似issue\n          required: true\n  - type: textarea\n    attributes:\n      label: 📜 功能描述\n      description: 请详细描述你希望添加的功能，包括它的工作方式和预期效果。\n      placeholder: |\n        功能描述：\n  - type: textarea\n    attributes:\n      label: 🌐 举例(可选)\n      description: 如果可能，请提供功能相关的示例、截图或相关网址。\n      placeholder: |\n        直播间示例地址：\n        `https://www.example.com/live/xxxx` \n\n  - type: textarea\n    attributes:\n      label: 💡 动机\n      description: 描述你提出该feature的动机，以及没有这项feature对你的使用造成了怎样的影响。\n      placeholder: |\n        我需要这个功能是因为..."
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_en.yml",
    "content": "name: 🚀 (English)Feature request\ndescription: Propose new ideas or suggestions for the project.\ntitle: 🚀[Feature] Please enter a title\nlabels: enhancement\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        📝 **Please fill in a concise and clear title in the `title` above**, the format is suggested as: 🚀[Feature] Short description.\n        For example: 🚀[Feature] Add xx live recording.\n  - type: checkboxes\n    attributes:\n      label: ⚠️ Search for similar issues\n      description: >\n        🔍 [Click here to search historical issues](https://github.com/ihmily/DouyinLiveRecorder/issues?q=is%3Aissue) using keywords to ensure there are no duplicate issues.\n      options:\n        - label: I have searched the issues and found no similar issues\n          required: true\n  - type: textarea\n    attributes:\n      label: 📜 Feature description\n      description: Please describe in detail the feature you would like to add, including how it should work and what its expected outcomes are.\n      placeholder: |\n        Feature description:\n  - type: textarea\n    attributes:\n      label: 🌐 Example (Optional)\n      description: If possible, provide examples, screenshots, or related URLs related to the feature.\n      placeholder: |\n        Live room example URL:\n        `https://www.example.com/live/xxxx`\n  - type: textarea\n    attributes:\n      label: 💡 Motivation\n      description: Describe the motivation behind your feature request and how not having this feature impacts your use of the project.\n      placeholder: |\n        I need this feature because..."
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.yml",
    "content": "name: ❓ Question\ndescription: 对程序使用有疑问？在这里提出你的问题。\ntitle: ❓[Question] 请输入标题\nlabels: question\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        📝 **请在上方的`title`中填写一个简洁明了的问题标题**。这将帮助其他人快速理解你的问题。\n        例如：❓[Question] 如果设置单个直播间的录制清晰度。\n  - type: checkboxes\n    attributes:\n      label: ⚠️ 搜索是否存在类似问题\n      description: >\n        🔍 [点击这里搜索历史issue](https://github.com/ihmily/DouyinLiveRecorder/issues?q=is%3Aissue) 使用关键词搜索，看看是否已经有人问过类似的问题。\n      options:\n        - label: 我已经搜索过issues，没有找到相似的问题\n          required: true\n  - type: dropdown\n    attributes:\n      label: 🔧 运行方式\n      description: 请选择你是如何运行程序的。\n      options:\n        - 直接运行的exe文件\n        - 使用源代码运行\n        - 使用docker运行\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: 🐍 如果是使用源代码运行，请选择你的Python环境版本\n      description: 请选择你运行程序的Python版本。\n      options:\n        - Python 3.10\n        - Python 3.11\n        - Python 3.12\n        - Python 3.13\n        - Other (请在问题中说明)\n    validations:\n      required: false\n  - type: dropdown\n    attributes:\n      label: 💻 请选择你的系统环境\n      description: 请选择你运行程序的具体系统版本。\n      options:\n        - Windows 10\n        - Windows 11\n        - macOS\n        - Ubuntu\n        - CentOS\n        - Fedora\n        - Debian\n        - Other (请在问题中说明)\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: 🤔 问题详情\n      description: 请提供与你的问题相关的所有详细信息。\n      placeholder: |\n        你的问题具体是关于什么？\n    validations:\n      required: true"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question_en.yml",
    "content": "name: ❓ (English)Question\ndescription: Have questions about using the program? Ask them here.\ntitle: ❓[Question] Please enter a title\nlabels: question\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        📝 **Please fill in a concise and clear question title in the `title` above**. This will help others quickly understand your question.\n        For example: ❓[Question] How to set the recording quality for a single live room.\n  - type: checkboxes\n    attributes:\n      label: ⚠️ Search for similar issues\n      description: >\n        🔍 [Click here to search historical issues](https://github.com/ihmily/DouyinLiveRecorder/issues?q=is%3Aissue) see if your question has already been asked.\n      options:\n        - label: I have searched the issues and found no similar questions\n          required: true\n  - type: dropdown\n    attributes:\n      label: 🔧 How did you run the program?\n      description: Please select how you ran the program.\n      options:\n        - Executable file run directly\n        - Running with source code\n        - Running with docker\n    validations:\n      required: true\n  - type: dropdown\n    attributes:\n      label: 🐍 If running with source code, please select your Python environment version\n      description: Please select the Python version you used to run the program.\n      options:\n        - Python 3.10\n        - Python 3.11\n        - Python 3.12\n        - Python 3.13\n        - Other (please specify in the question)\n    validations:\n      required: false\n  - type: dropdown\n    attributes:\n      label: 💻 Please select your system environment\n      description: Please select the specific system version you are running the program on.\n      options:\n        - Windows 10\n        - Windows 11\n        - macOS\n        - Ubuntu\n        - CentOS\n        - Fedora\n        - Debian\n        - Other (please specify in the question)\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: 🤔 Question details\n      description: Please provide all the details relevant to your question.\n      placeholder: |\n        What is your question about?\n    validations:\n      required: true"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "### 📜 标题（Title）\n\n**请提供这个Pull Request中提议的更改的简洁描述：**  \n<!-- Please provide a succinct description of the changes proposed in this pull request:. -->\n\n- \n\n### 🔍 描述（Description）\n\n**请描述这个PR做了什么/为什么这些更改是必要的：**\n<!-- Please describe what this PR does / why these changes are necessary: -->\n\n- \n\n### 📝 类型（Type of Change）\n\n**这个PR引入了哪种类型的更改？（请勾选所有适用的选项）**\n<!-- What type of change does this PR introduce? (Check all that apply)  -->\n\n- [ ] 修复Bug  <!-- Bugfix -->\n- [ ] 新功能  <!-- Feature -->\n- [ ] 代码风格更新（格式化，局部变量） <!-- Code style update (formatting, local variables) -->\n- [ ] 重构（改进代码结构） <!-- Refactoring (improving code structure) -->\n- [ ] 构建相关更改（依赖项，构建脚本等）  <!-- Build-related changes (dependencies, build scripts, etc.) -->\n- [ ] 其他：_请描述_  <!-- Other: _Please describe_ -->\n\n### 🏗️ 测试（Testing）\n\n**请描述您已经进行的测试：**\n<!-- Please describe the tests you've done: -->\n\n- \n\n**如果适用，请提供测试更改的说明：**\n<!-- If applicable, provide instructions for testing your changes -->\n\n- \n\n### 📋 检查清单（Checklist）\n\n在您创建这个PR之前，请确保以下所有框都被勾选，方法是在每个框中放置一个`x`：\n<!-- Before you create this PR, please ensure the following boxes are checked by placing an `x` in each box: -->\n\n- [ ] 我已经阅读了**贡献指南**文档  <!-- I have read the **CONTRIBUTING** document. -->\n- [ ] 我的更改没有产生新的警告  <!-- My changes generate no new warnings. -->\n- [ ] 我已经添加了覆盖我更改的测试  <!-- I have added tests to cover my changes.. -->\n- [ ] 我已经相应地更新了文档（如果适用） <!-- I have updated the documentation accordingly (if applicable). -->\n- [ ] 我遵循了这个项目的代码风格  <!-- I have followed the code style of this project. -->\n\n**注意：** 这个PR在所有复选框被勾选之前不会被合并。\n<!-- **Note:** This PR will not be merged until all checkboxes are ticked. -->\n\n---\n\n**感谢您的贡献！**\n<!-- Thank you for your contribution! -->"
  },
  {
    "path": ".github/workflows/build-image.yml",
    "content": "name: Build and Push Docker Image\n\non:\n  push:\n    tags:\n      - '*'\n  workflow_dispatch:\n    inputs:\n      tag_name:\n        description: 'Tag name for the Docker image'\n        required: false\n        default: 'latest'\n\njobs:\n  build_and_push:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v2\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v3\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      - name: Cache Docker layers\n        uses: actions/cache@v3\n        with:\n          path: /tmp/.buildx-cache\n          key: ${{ runner.os }}-buildx-${{ github.sha }}\n          restore-keys: |\n            ${{ runner.os }}-buildx-\n\n      - name: Log in to Docker Hub\n        uses: docker/login-action@v2\n        with:\n          username: ${{ secrets.DOCKERHUB_USERNAME }}\n          password: ${{ secrets.DOCKERHUB_PASSWORD }}\n          registry: docker.io\n\n      - name: Build and push Docker image\n        uses: docker/build-push-action@v5\n        with:\n          context: .\n          file: ./Dockerfile\n          push: true\n          tags: |\n            ihmily/douyin-live-recorder:${{ github.event.inputs.tag_name || github.ref_name }} \n            ihmily/douyin-live-recorder:latest\n          platforms: linux/amd64,linux/arm64\n          cache-from: type=local,src=/tmp/.buildx-cache\n          cache-to: type=local,dest=/tmp/.buildx-cache\n"
  },
  {
    "path": ".github/workflows/issue-translator.yml",
    "content": "name: Issue Translator\non:\n  issue_comment:\n    types: [created]\n  issues:\n    types: [opened]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: usthe/issues-translate-action@v2.7\n        with:\n          IS_MODIFY_TITLE: false\n          CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically."
  },
  {
    "path": ".github/workflows/sync.yml",
    "content": "name: 'Upstream Sync'\n\npermissions:\n  contents: write\n\non:\n  schedule:\n    - cron: \"0 0 * * *\" # every day\n\n  workflow_dispatch:  # click the button on Github repo!\n    inputs:\n      sync_test_mode: # Adds a boolean option that appears during manual workflow run for easy test mode config\n        description: 'Fork Sync Test Mode'\n        type: boolean\n        default: false\n\njobs:\n  sync_latest_from_upstream:\n    runs-on: ubuntu-latest\n    name: Sync latest commits from upstream repo\n    if: ${{ github.event.repository.fork }}\n\n    steps:\n    # Step 1: run a standard checkout action, provided by github\n    - name: Checkout target repo\n      uses: actions/checkout@v3\n      with:\n        # optional: set the branch to checkout,\n        # sync action checks out your 'target_sync_branch' anyway\n        ref:  ${{ secrets.MY_TARGET_SYNC_BRANCH }}\n        # REQUIRED if your upstream repo is private (see wiki)\n        persist-credentials: false\n\n    # Step 2: run the sync action\n    - name: Sync upstream changes\n      id: sync\n      uses: aormsby/Fork-Sync-With-Upstream-action@v3.4.1\n      with:\n        target_sync_branch: ${{ secrets.MY_TARGET_SYNC_BRANCH }}  # need to set\n        # REQUIRED 'target_repo_token' exactly like this!\n        target_repo_token: ${{ secrets.GITHUB_TOKEN }}  # automatically generated, no need to set\n        upstream_sync_branch: main\n        upstream_sync_repo: ihmily/DouyinLiveRecorder\n\n        # Set test_mode true during manual dispatch to run tests instead of the true action!!\n        test_mode: ${{ inputs.sync_test_mode }}\n\n    # Step 3: Display a sample message based on the sync output var 'has_new_commits'\n    - name: New commits found\n      if: steps.sync.outputs.has_new_commits == 'true'\n      run: echo \"New commits were found to sync.\"\n\n    - name: No new commits\n      if: steps.sync.outputs.has_new_commits == 'false'\n      run: echo \"There were no new commits.\"\n\n    - name: Show value of 'has_new_commits'\n      run: echo ${{ steps.sync.outputs.has_new_commits }}\n\n    - name: Sync check\n      if: failure()\n      run: |\n        echo \"[Error] 由于上游仓库的 workflow 文件变更，导致 GitHub 自动暂停了本次自动更新，你需要手动 Sync Fork 一次\"\n        echo \"[Error] Due to a change in the workflow file of the upstream repository, GitHub has automatically suspended the scheduled automatic update. You need to manually sync your fork.\"\n        exit 1\n"
  },
  {
    "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*.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# DouyinLiveRecord\nbackup_config/\nlogs/\nnode/\nnode-v*.zip\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# 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\npoetry.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/#use-with-ide\n.pdm.toml\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 settings\n.spyderproject\n.spyproject\n\n# Rope project settings\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\nbackup_config/\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM python:3.11-slim\n\nWORKDIR /app\n\nCOPY . /app\n\nRUN apt-get update && \\\n    apt-get install -y curl gnupg && \\\n    curl -sL https://deb.nodesource.com/setup_20.x  | bash - && \\\n    apt-get install -y nodejs\n\nRUN pip install --no-cache-dir -r requirements.txt\n\nRUN apt-get update && \\\n    apt-get install -y ffmpeg tzdata && \\\n    ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \\\n    dpkg-reconfigure -f noninteractive tzdata\n\nCMD [\"python\", \"main.py\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2025 Hmily\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "![video_spider](https://socialify.git.ci/ihmily/DouyinLiveRecorder/image?font=Inter&forks=1&language=1&owner=1&pattern=Circuit%20Board&stargazers=1&theme=Light)\n\n## 💡简介\n[![Python Version](https://img.shields.io/badge/python-3.11.6-blue.svg)](https://www.python.org/downloads/release/python-3116/)\n[![Supported Platforms](https://img.shields.io/badge/platforms-Windows%20%7C%20Linux-blue.svg)](https://github.com/ihmily/DouyinLiveRecorder)\n[![Docker Pulls](https://img.shields.io/docker/pulls/ihmily/douyin-live-recorder?label=Docker%20Pulls&color=blue&logo=docker)](https://hub.docker.com/r/ihmily/douyin-live-recorder/tags)\n![GitHub issues](https://img.shields.io/github/issues/ihmily/DouyinLiveRecorder.svg)\n[![Latest Release](https://img.shields.io/github/v/release/ihmily/DouyinLiveRecorder)](https://github.com/ihmily/DouyinLiveRecorder/releases/latest)\n[![Downloads](https://img.shields.io/github/downloads/ihmily/DouyinLiveRecorder/total)](https://github.com/ihmily/DouyinLiveRecorder/releases/latest)\n\n一款**简易**的可循环值守的直播录制工具，基于FFmpeg实现多平台直播源录制，支持自定义配置录制以及直播状态推送。\n\n</div>\n\n## 😺已支持平台\n\n- [x] 抖音\n- [x] TikTok\n- [x] 快手\n- [x] 虎牙\n- [x] 斗鱼\n- [x] YY\n- [x] B站\n- [x] 小红书\n- [x] bigo \n- [x] blued\n- [x] SOOP(原AfreecaTV)\n- [x] 网易cc\n- [x] 千度热播\n- [x] PandaTV\n- [x] 猫耳FM\n- [x] Look直播\n- [x] WinkTV\n- [x] TTingLive(原Flextv)\n- [x] PopkonTV\n- [x] TwitCasting\n- [x] 百度直播\n- [x] 微博直播\n- [x] 酷狗直播\n- [x] TwitchTV\n- [x] LiveMe\n- [x] 花椒直播\n- [x] 流星直播\n- [x] ShowRoom\n- [x] Acfun\n- [x] 映客直播\n- [x] 音播直播\n- [x] 知乎直播\n- [x] CHZZK\n- [x] 嗨秀直播\n- [x] vv星球直播\n- [x] 17Live\n- [x] 浪Live\n- [x] 畅聊直播\n- [x] 飘飘直播\n- [x] 六间房直播\n- [x] 乐嗨直播\n- [x] 花猫直播\n- [x] Shopee\n- [x] Youtube\n- [x] 淘宝\n- [x] 京东\n- [x] Faceit\n- [x] 咪咕\n- [x] 连接直播\n- [x] 来秀直播\n- [x] Picarto\n- [ ] 更多平台正在更新中\n\n</div>\n\n## 🎈项目结构\n\n```\n.\n└── DouyinLiveRecorder/\n    ├── /config -> (config record)\n    ├── /logs -> (save runing log file)\n    ├── /backup_config -> (backup file)\n    ├── /douyinliverecorder -> (package)\n        ├── initializer.py-> (check and install nodejs)\n    \t├── spider.py-> (get live data)\n    \t├── stream.py-> (get live stream address)\n    \t├── utils.py -> (contains utility functions)\n    \t├── logger.py -> (logger handdle)\n    \t├── room.py -> (get room info)\n    \t├── ab_sign.py-> (generate dy token)\n    \t├── /javascript -> (some decrypt code)\n    ├── main.py -> (main file)\n    ├── ffmpeg_install.py -> (ffmpeg install script)\n    ├── demo.py -> (call package test demo)\n    ├── msg_push.py -> (send live status update message)\n    ├── ffmpeg.exe -> (record video)\n    ├── index.html -> (play m3u8 and flv video)\n    ├── requirements.txt -> (library dependencies)\n    ├── docker-compose.yaml -> (Container Orchestration File)\n    ├── Dockerfile -> (Application Build Recipe)\n    ├── StopRecording.vbs -> (stop recording script on Windows)\n    ...\n```\n\n</div>\n\n## 🌱使用说明\n\n- 对于只想使用录制软件的小白用户，进入[Releases](https://github.com/ihmily/DouyinLiveRecorder/releases) 中下载最新发布的 zip压缩包即可，里面有打包好的录制软件。（有些电脑可能会报毒，直接忽略即可，如果下载时被浏览器屏蔽，请更换浏览器下载）\n\n- 压缩包解压后，在 `config` 文件夹内的 `URL_config.ini` 中添加录制直播间地址，一行一个直播间地址。如果要自定义配置录制，可以修改`config.ini` 文件，推荐将录制格式修改为`ts`。\n- 以上步骤都做好后，就可以运行`DouyinLiveRecorder.exe` 程序进行录制了。录制的视频文件保存在同目录下的 `downloads` 文件夹内。\n\n- 另外，如果需要录制TikTok、AfreecaTV等海外平台，请在配置文件中设置开启代理并添加proxy_addr链接 如：`127.0.0.1:7890` （这只是示例地址，具体根据实际填写）。\n\n- 假如`URL_config.ini`文件中添加的直播间地址，有个别直播间暂时不想录制又不想移除链接，可以在对应直播间的链接开头加上`#`，那么将停止该直播间的监测以及录制。\n\n- 软件默认录制清晰度为 `原画` ，如果要单独设置某个直播间的录制画质，可以在添加直播间地址时前面加上画质即可，如`超清，https://live.douyin.com/745964462470` 记得中间要有`,` 分隔。\n\n- 如果要长时间挂着软件循环监测直播，最好循环时间设置长一点（咱也不差没录制到的那几分钟），避免因请求频繁导致被官方封禁IP 。\n\n- 要停止直播录制，Windows平台可执行StopRecording.vbs脚本文件，或者在录制界面使用 `Ctrl+C ` 组合键中断录制，若要停止其中某个直播间的录制，可在`URL_config.ini`文件中的地址前加#，会自动停止对应直播间的录制并正常保存已录制的视频。\n- 最后，欢迎右上角给本项目一个star，同时也非常乐意大家提交pr。\n\n&emsp;\n\n直播间链接示例：\n\n```\n抖音:\nhttps://live.douyin.com/745964462470\nhttps://v.douyin.com/iQFeBnt/\nhttps://live.douyin.com/yall1102  （链接+抖音号）\nhttps://v.douyin.com/CeiU5cbX  （主播主页地址）\n\nTikTok:\nhttps://www.tiktok.com/@pearlgaga88/live\n\n快手:\nhttps://live.kuaishou.com/u/yall1102\n\n虎牙:\nhttps://www.huya.com/52333\n\n斗鱼:\nhttps://www.douyu.com/3637778?dyshid=\nhttps://www.douyu.com/topic/wzDBLS6?rid=4921614&dyshid=\n\nYY:\nhttps://www.yy.com/22490906/22490906\n\nB站:\nhttps://live.bilibili.com/320\n\n小红书（直播间分享地址):\nhttp://xhslink.com/xpJpfM\n\nbigo直播:\nhttps://www.bigo.tv/cn/716418802\n\nbuled直播:\nhttps://app.blued.cn/live?id=Mp6G2R\n\nSOOP:\nhttps://play.sooplive.co.kr/sw7love\n\n网易cc:\nhttps://cc.163.com/583946984\n\n千度热播:\nhttps://qiandurebo.com/web/video.php?roomnumber=33333\n\nPandaTV:\nhttps://www.pandalive.co.kr/live/play/bara0109\n\n猫耳FM:\nhttps://fm.missevan.com/live/868895007\n\nLook直播:\nhttps://look.163.com/live?id=65108820&position=3\n\nWinkTV:\nhttps://www.winktv.co.kr/live/play/anjer1004\n\nFlexTV(TTinglive)::\nhttps://www.flextv.co.kr/channels/593127/live\n\nPopkonTV:\nhttps://www.popkontv.com/live/view?castId=wjfal007&partnerCode=P-00117\nhttps://www.popkontv.com/channel/notices?mcid=wjfal007&mcPartnerCode=P-00117\n\nTwitCasting:\nhttps://twitcasting.tv/c:uonq\n\n百度直播:\nhttps://live.baidu.com/m/media/pclive/pchome/live.html?room_id=9175031377&tab_category\n\n微博直播:\nhttps://weibo.com/l/wblive/p/show/1022:2321325026370190442592\n\n酷狗直播:\nhttps://fanxing2.kugou.com/50428671?refer=2177&sourceFrom=\n\nTwitchTV:\nhttps://www.twitch.tv/gamerbee\n\nLiveMe:\nhttps://www.liveme.com/zh/v/17141543493018047815/index.html\n\n花椒直播:\nhttps://www.huajiao.com/l/345096174\n\n流星直播:\nhttps://www.7u66.com/100960\n\nShowRoom:\nhttps://www.showroom-live.com/room/profile?room_id=480206  （主播主页地址）\n\nAcfun:\nhttps://live.acfun.cn/live/179922\n\n映客直播:\nhttps://www.inke.cn/liveroom/index.html?uid=22954469&id=1720860391070904\n\n音播直播:\nhttps://live.ybw1666.com/800002949\n\n知乎直播:\nhttps://www.zhihu.com/people/ac3a467005c5d20381a82230101308e9 (主播主页地址)\n\nCHZZK:\nhttps://chzzk.naver.com/live/458f6ec20b034f49e0fc6d03921646d2\n\n嗨秀直播:\nhttps://www.haixiutv.com/6095106\n\nVV星球直播:\nhttps://h5webcdn-pro.vvxqiu.com//activity/videoShare/videoShare.html?h5Server=https://h5p.vvxqiu.com&roomId=LP115924473&platformId=vvstar\n\n17Live:\nhttps://17.live/en/live/6302408\n\n浪Live:\nhttps://www.lang.live/en-US/room/3349463\n\n畅聊直播:\nhttps://live.tlclw.com/106188\n\n飘飘直播:\nhttps://m.pp.weimipopo.com/live/preview.html?uid=91648673&anchorUid=91625862&app=plpl\n\n六间房直播:\nhttps://v.6.cn/634435\n\n乐嗨直播:\nhttps://www.lehaitv.com/8059096\n\n花猫直播:\nhttps://h.catshow168.com/live/preview.html?uid=19066357&anchorUid=18895331\n\nShopee:\nhttps://sg.shp.ee/GmpXeuf?uid=1006401066&session=802458\n\nYoutube:\nhttps://www.youtube.com/watch?v=cS6zS5hi1w0\n\n淘宝(需cookie):\nhttps://tbzb.taobao.com/live?liveId=532359023188\nhttps://m.tb.cn/h.TWp0HTd\n\n京东:\nhttps://3.cn/28MLBy-E\n\nFaceit:\nhttps://www.faceit.com/zh/players/Compl1/stream\n\n连接直播:\nhttps://show.lailianjie.com/10000258\n\n咪咕直播:\nhttps://www.miguvideo.com/p/live/120000541321\n\n来秀直播:\nhttps://www.imkktv.com/h5/share/video.html?uid=1845195&roomId=1710496\n\nPicarto:\nhttps://www.picarto.tv/cuteavalanche\n```\n\n&emsp;\n\n## 🎃源码运行\n使用源码运行，可参考下面的步骤。\n\n1.首先拉取或手动下载本仓库项目代码\n\n```bash\ngit clone https://github.com/ihmily/DouyinLiveRecorder.git\n```\n\n2.进入项目文件夹，安装依赖\n\n```bash\ncd DouyinLiveRecorder\n```\n\n> [!TIP]\n> - 不论你是否已安装 **Python>=3.10** 环境, 都推荐使用 [**uv**](https://github.com/astral-sh/uv) 运行, 因为它可以自动管理虚拟环境和方便地管理 **Python** 版本, **不过这完全是可选的**<br />\n> 使用以下命令安装\n>    ```bash\n>    # 在 macOS 和 Linux 上安装 uv\n>    curl -LsSf https://astral.sh/uv/install.sh | sh\n>    ```\n>    ```powershell\n>    # 在 Windows 上安装 uv\n>    powershell -ExecutionPolicy ByPass -c \"irm https://astral.sh/uv/install.ps1 | iex\"\n>    ```\n> - 如果安装依赖速度太慢, 你可以考虑使用国内 pip 镜像源:<br />\n> 在 `pip` 命令使用 `-i` 参数指定, 如 `pip3 install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple`<br />\n> 或者在 `uv` 命令 `--index` 选项指定, 如 `uv sync --index https://pypi.tuna.tsinghua.edu.cn/simple`\n\n<details>\n\n  <summary>如果已安装 <b>Python>=3.10</b> 环境</summary>\n\n  - :white_check_mark: 在虚拟环境中安装 (推荐)\n  \n    1. 创建虚拟环境\n\n       - 使用系统已安装的 Python, 不使用 uv\n  \n         ```bash\n         python -m venv .venv\n         ```\n\n       - 使用 uv, 默认使用系统 Python, 你可以添加 `--python` 选项指定 Python 版本而不使用系统 Python [uv官方文档](https://docs.astral.sh/uv/concepts/python-versions/)\n       \n         ```bash\n         uv venv\n         ```\n    \n    2. 在终端激活虚拟环境 (在未安装 uv 或你想要手动激活虚拟环境时执行, 若已安装 uv, 可以跳过这一步, uv 会自动激活并使用虚拟环境)\n   \n       **Bash** 中\n       ```bash\n       source .venv/Scripts/activate\n       ```\n\n       **Powershell** 中\n       ```powershell\n       .venv\\Scripts\\activate.ps1\n       ```\n       \n       **Windows CMD** 中\n       ```bat\n       .venv\\Scripts\\activate.bat\n       ```\n\n    3. 安装依赖\n   \n       ```bash\n       # 使用 pip (若安装太慢或失败, 可使用 `-i` 指定镜像源)\n       pip3 install -U pip && pip3 install -r requirements.txt\n       # 或者使用 uv (可使用 `--index` 指定镜像源)\n       uv sync\n       # 或者\n       uv pip sync requirements.txt\n       ```\n\n  - :x: 在系统 Python 环境中安装 (不推荐)\n  \n    ```bash\n    pip3 install -U pip && pip3 install -r requirements.txt\n    ```\n\n</details>\n\n<details>\n\n  <summary>如果未安装 <b>Python>=3.10</b> 环境</summary>\n\n  你可以使用 [**uv**](https://github.com/astral-sh/uv) 安装依赖\n   \n  ```bash\n  # uv 将使用 3.10 及以上的最新 python 发行版自动创建并使用虚拟环境, 可使用 --python 选项指定 python 版本, 参见 https://docs.astral.sh/uv/reference/cli/#uv-sync--python 和 https://docs.astral.sh/uv/reference/cli/#uv-pip-sync--python\n  uv sync\n  # 或\n  uv pip sync requirements.txt\n  ```\n\n</details>\n\n3.安装[FFmpeg](https://ffmpeg.org/download.html#build-linux)，如果是Windows系统，这一步可跳过。对于Linux系统，执行以下命令安装\n\nCentOS执行\n\n```bash\nyum install epel-release\nyum install ffmpeg\n```\n\nUbuntu则执行\n\n```bash\napt update\napt install ffmpeg\n```\n\nmacOS 执行\n\n**如果已经安装 Homebrew 请跳过这一步**\n\n```bash\n/bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\n```\n\n```bash\nbrew install ffmpeg\n```\n\n4.运行程序\n\n```python\npython main.py\n\n```\n或\n\n```bash\nuv run main.py\n```\n\n其中Linux系统请使用`python3 main.py` 运行。\n\n&emsp;\n## 🐋容器运行\n\n在运行命令之前，请确保您的机器上安装了 [Docker](https://docs.docker.com/get-docker/) 和 [Docker Compose](https://docs.docker.com/compose/install/) \n\n1.快速启动\n\n最简单方法是运行项目中的 [docker-compose.yaml](https://github.com/ihmily/DouyinLiveRecorder/blob/main/docker-compose.yaml) 文件，只需简单执行以下命令：\n\n```bash\ndocker-compose up\n```\n\n可选 `-d` 在后台运行。\n\n\n\n2.构建镜像(可选)\n\n如果你只想简单的运行程序，则不需要做这一步。Docker镜像仓库中代码版本可能不是最新的，如果要运行本仓库主分支最新代码，可以本地自定义构建，通过修改 [docker-compose.yaml](https://github.com/ihmily/DouyinLiveRecorder/blob/main/docker-compose.yaml) 文件，如将镜像名修改为 `douyin-live-recorder:latest`，并取消 `# build: .` 注释，然后再执行\n\n```bash\ndocker build -t douyin-live-recorder:latest .\ndocker-compose up\n```\n\n或者直接使用下面命令进行构建并启动\n\n```bash\ndocker-compose -f docker-compose.yaml up\n```\n\n\n\n3.停止容器实例\n\n```bash\ndocker-compose stop\n```\n\n\n\n4.注意事项\n\n①在docker容器内运行本程序之前，请先在配置文件中添加要录制的直播间地址。\n\n②在容器内时，如果手动中断容器运行停止录制，会导致正在录制的视频文件损坏！\n\n**无论哪种运行方式，为避免手动中断或者异常中断导致录制的视频文件损坏的情况，推荐使用 `ts` 格式保存**。\n\n&emsp;\n\n## 🤖相关项目\n\n- StreamCap: https://github.com/ihmily/StreamCap\n- streamget: https://github.com/ihmily/streamget\n\n&emsp;\n\n## ❤️贡献者\n\n&ensp;&ensp; [![Hmily](https://github.com/ihmily.png?size=50)](https://github.com/ihmily)\n[![iridescentGray](https://github.com/iridescentGray.png?size=50)](https://github.com/iridescentGray)\n[![annidy](https://github.com/annidy.png?size=50)](https://github.com/annidy)\n[![wwkk2580](https://github.com/wwkk2580.png?size=50)](https://github.com/wwkk2580)\n[![missuo](https://github.com/missuo.png?size=50)](https://github.com/missuo)\n<a href=\"https://github.com/xueli12\" target=\"_blank\"><img src=\"https://github.com/xueli12.png?size=50\" alt=\"xueli12\" style=\"width:53px; height:51px;\" /></a>\n<a href=\"https://github.com/kaine1973\" target=\"_blank\"><img src=\"https://github.com/kaine1973.png?size=50\" alt=\"kaine1973\" style=\"width:53px; height:51px;\" /></a>\n<a href=\"https://github.com/yinruiqing\" target=\"_blank\"><img src=\"https://github.com/yinruiqing.png?size=50\" alt=\"yinruiqing\" style=\"width:53px; height:51px;\" /></a>\n<a href=\"https://github.com/Max-Tortoise\" target=\"_blank\"><img src=\"https://github.com/Max-Tortoise.png?size=50\" alt=\"Max-Tortoise\" style=\"width:53px; height:51px;\" /></a>\n[![justdoiting](https://github.com/justdoiting.png?size=50)](https://github.com/justdoiting)\n[![dhbxs](https://github.com/dhbxs.png?size=50)](https://github.com/dhbxs)\n[![wujiyu115](https://github.com/wujiyu115.png?size=50)](https://github.com/wujiyu115)\n[![zhanghao333](https://github.com/zhanghao333.png?size=50)](https://github.com/zhanghao333)\n<a href=\"https://github.com/gyc0123\" target=\"_blank\"><img src=\"https://github.com/gyc0123.png?size=50\" alt=\"gyc0123\" style=\"width:53px; height:51px;\" /></a>\n\n&ensp;&ensp; [![HoratioShaw](https://github.com/HoratioShaw.png?size=50)](https://github.com/HoratioShaw)\n[![nov30th](https://github.com/nov30th.png?size=50)](https://github.com/nov30th)\n[![727155455](https://github.com/727155455.png?size=50)](https://github.com/727155455)\n[![nixingshiguang](https://github.com/nixingshiguang.png?size=50)](https://github.com/nixingshiguang)\n[![1411430556](https://github.com/1411430556.png?size=50)](https://github.com/1411430556)\n[![Ovear](https://github.com/Ovear.png?size=50)](https://github.com/Ovear)\n&emsp;\n\n## ⏳提交日志\n\n- 20251024\n  - 修复抖音风控无法获取数据问题\n  \n  - 新增soop.com录制支持\n  \n  - 修复bigo录制\n  \n- 20250127\n  - 新增淘宝、京东、faceit直播录制\n  - 修复小红书直播流录制以及转码问题\n  - 修复畅聊、VV星球、flexTV直播录制\n  - 修复批量微信直播推送\n  - 新增email发送ssl和port配置\n  - 新增强制转h264配置\n  - 更新ffmpeg版本\n  - 重构包为异步函数！\n\n- 20241130\n  - 新增shopee、youtube直播录制\n  - 新增支持自定义m3u8、flv地址录制\n  - 新增自定义执行脚本，支持python、bat、bash等\n  - 修复YY直播、花椒直播和小红书直播录制\n  - 修复b站标题获取错误\n  - 修复log日志错误\n- 20241030\n  - 新增嗨秀直播、vv星球直播、17Live、浪Live、SOOP、畅聊直播(原时光直播)、飘飘直播、六间房直播、乐嗨直播、花猫直播等10个平台直播录制\n  - 修复小红书直播录制，支持小红书作者主页地址录制直播\n  - 新增支持ntfy消息推送，以及新增支持批量推送多个地址（逗号分隔多个推送地址)\n  - 修复Liveme直播录制、twitch直播录制\n  - 新增Windows平台一键停止录制VB脚本程序\n- 20241005\n  - 新增邮箱和Bark推送\n  - 新增直播注释停止录制\n  - 优化分段录制\n  - 重构部分代码\n- 20240928\n  - 新增知乎直播、CHZZK直播录制\n  - 修复音播直播录制\n- 20240903\n  - 新增抖音双屏录制、音播直播录制\n  - 修复PandaTV、bigo直播录制\n- 20240713\n  - 新增映客直播录制\n- 20240705\n  - 新增时光直播录制\n- 20240701\n  - 修复虎牙直播录制2分钟断流问题\n  - 新增自定义直播推送内容\n- 20240621\n  - 新增Acfun、ShowRoom直播录制\n  - 修复微博录制、新增直播源线路\n  - 修复斗鱼直播60帧录制\n  - 修复酷狗直播录制\n  - 修复TikTok部分无法解析直播源\n  - 修复抖音无法录制连麦直播\n- 20240510\n  - 修复部分虎牙直播间录制错误\n- 20240508\n  - 修复花椒直播录制\n  - 更改文件路径解析方式 [@kaine1973](https://github.com/kaine1973)\n- 20240506\n  - 修复抖音录制画质解析bug\n  - 修复虎牙录制 60帧最高画质问题\n  - 新增流星直播录制\n- 20240427\n  - 新增LiveMe、花椒直播录制\n- 20240425\n  - 新增TwitchTV直播录制\n- 20240424\n  - 新增酷狗直播录制、优化PopkonTV直播录制\n- 20240423\n  - 新增百度直播录制、微博直播录制\n  - 修复斗鱼录制直播回放的问题\n  - 新增直播源地址显示以及输出到日志文件设置\n- 20240311\n  - 修复海外平台录制bug，增加画质选择，增强录制稳定性\n  - 修复虎牙录制bug (虎牙`一起看`频道 有特殊限制，有时无法录制)\n- 20240309\n  - 修复虎牙直播、小红书直播和B站直播录制\n  - 新增5个直播平台录制，包括winktv、flextv、look、popkontv、twitcasting\n  - 新增部分海外平台账号密码配置，实现自动登录并更新配置文件中的cookie\n  - 新增自定义配置需要使用代理录制的平台\n  - 新增只推送开播消息不进行录制设置\n  - 修复了一些bug\n- 20240209\n  - 优化AfreecaTV录制，新增账号密码登录获取cookie以及持久保存\n  - 修复了小红书直播因官方更新直播域名，导致无法录制直播的问题\n  - 修复了更新URL配置文件的bug\n  - 最后，祝大家新年快乐！\n\n<details><summary>点击展开更多提交日志</summary>\n\n- 20240129\n  - 新增猫耳FM直播录制\n- 20240127\n  - 新增千度热播直播录制、新增pandaTV(韩国)直播录制\n  - 新增telegram直播状态消息推送，修复了某些bug\n  - 新增自定义设置不同直播间的录制画质(即每个直播间录制画质可不同)\n  - 修改录制视频保存路径为 `downloads` 文件夹，并且分平台进行保存。\n- 20240114\n  - 新增网易cc直播录制，优化ffmpeg参数，修改AfreecaTV输入直播地址格式\n  - 修改日志记录器 @[iridescentGray](https://github.com/iridescentGray)\n- 20240102\n  - 修复Linux上运行，新增docker配置文件\n- 20231210\n  - 修复录制分段bug，修复bigo录制检测bug\n  - 新增自定义修改录制主播名\n  - 新增AfreecaTV直播录制，修复某些可能会发生的bug\n- 20231207\n  - 新增blued直播录制，修复YY直播录制，新增直播结束消息推送\n- 20231206\n  - 新增bigo直播录制\n- 20231203\n  - 新增小红书直播录制（全网首发），目前小红书官方没有切换清晰度功能，因此直播录制也只有默认画质\n  - 小红书录制暂时无法循环监测，每次主播开启直播，都要重新获取一次链接\n  - 获取链接的方式为 将直播间转发到微信，在微信中打开后，复制页面的链接。\n- 20231030\n  - 本次更新只是进行修复，没时间新增功能。\n  - 欢迎各位大佬提pr 帮忙更新维护\n- 20230930\n  - 新增抖音从接口获取直播流，增强稳定性\n  - 修改快手获取直播流的方式，改用从官方接口获取\n  - 祝大家中秋节快乐！\n- 20230919\n  - 修复了快手版本更新后录制出错的问题，增加了其自动获取cookie(~~稳定性未知~~)\n  - 修复了TikTok显示正在直播但不进行录制的问题\n- 20230907\n  - 修复了因抖音官方更新了版本导致的录制出错以及短链接转换出错\n  - 修复B站无法录制原画视频的bug\n  - 修改了配置文件字段，新增各平台自定义设置Cookie\n- 20230903\n  - 修复了TikTok录制时报644无法录制的问题\n  - 新增直播状态推送到钉钉和微信的功能，如有需要请看 [设置推送教程](https://d04vqdiqwr3.feishu.cn/docx/XFPwdDDvfobbzlxhmMYcvouynDh?from=from_copylink)\n  - 最近比较忙，其他问题有时间再更新\n- 20230816\n  - 修复斗鱼直播（官方更新了字段）和快手直播录制出错的问题\n- 20230814\n  - 新增B站直播录制\n  - 写了一个在线播放M3U8和FLV视频的网页源码，打开即可食用\n- 20230812\n  - 新增YY直播录制\n- 20230808\n  - 修复主播重新开播无法再次录制的问题\n- 20230807\n  - 新增了斗鱼直播录制\n  - 修复显示录制完成之后会重新开始录制的问题\n- 20230805\n  - 新增了虎牙直播录制，其暂时只能用flv视频流进行录制\n  - Web API 新增了快手和虎牙这两个平台的直播流解析（TikTok要代理）\n- 20230804\n  - 新增了快手直播录制，优化了部分代码\n  - 上传了一个自动化获取抖音直播间页面Cookie的代码，可以用于录制\n- 20230803\n  - 通宵更新 \n  - 新增了国际版抖音TikTok的直播录制，去除冗余 简化了部分代码\n- 20230724\t\n  - 新增了一个通过抖音直播间地址获取直播视频流链接的API接口，上传即可用\n  </details>\n  &emsp;\n\n## 有问题可以提issue, 我会在这里持续添加更多直播平台的录制 欢迎Star\n#### \n"
  },
  {
    "path": "StopRecording.vbs",
    "content": "'********************************************************************************************/\n'* File Name       : StopRecording.vbs\n'* Created Date  : 2024-10-15 01:50:30\n'* Author            : Hmily\n'* GitHub            : http://github.com/ihmily\n'* Description     : This script is designed to terminate the process of live recording\n'********************************************************************************************/\n\nDim objWMIService, colProcesses, objProcess\nDim intResponse\nstrComputer = \".\"\nOn Error Resume Next\nintResponse = MsgBox(\"ȷҪкֱ̨¼ƽ\", vbYesNo + vbQuestion, \"ȷϽ\")\n\nIf intResponse = vbYes Then\n    Set objWMIService = GetObject(\"winmgmts:\\\\\" & strComputer & \"\\root\\cimv2\")\n    If Err.Number <> 0 Then\n        Err.Clear\n    End If\n\n    Set colProcesses = objWMIService.ExecQuery(\"Select * from Win32_Process Where Name = 'ffmpeg.exe'\")\n    Set colProcesses2 = objWMIService.ExecQuery(\"Select * from Win32_Process Where Name = 'pythonw.exe'\")\n    Set colProcesses3 = objWMIService.ExecQuery(\"Select * from Win32_Process Where Name = 'DouyinLiveRecorder.exe'\")\n    If Err.Number <> 0 Then\n        Err.Clear\n    End If\n\n    If Not objWMIService Is Nothing And Not colProcesses Is Nothing  And Not colProcesses2 Is Nothing Then\n        If colProcesses2.Count = 0 And colProcesses3.Count = 0 Then\n            MsgBox \"ûҵ¼ƳĽ\", vbExclamation, \"ʾϢ\"\n            WScript.Quit(1)\n        Else\n            For Each objProcess in colProcesses\n                objProcess.Terminate()\n                If Err.Number <> 0 Then\n                    objShell.Run \"taskkill /f /im \" & objProcess.Name, 0, True\n                    Err.Clear\n                End If                \n            Next\n        End If\n    Else\n        objShell.Run \"taskkill /f /im \" & objProcess.Name, 0, True\n    End If\n    MsgBox \"ѳɹ¼ֱḶ̌\" & vbCrLf & \"رմ˴30Զֹͣ¼Ƴ\", vbInformation, \"ʾϢ\"\n\n    WScript.Sleep 10000\n    If colProcesses3.Count <> 0 Then\n        Set colProcesses_ = colProcesses3\n    Else\n        Set colProcesses_ = colProcesses2\n    End If\n    For Each objProcess in colProcesses_\n        objProcess.Terminate()\n        If Err.Number <> 0 Then\n            objShell.Run \"taskkill /f /im \" & objProcess.Name, 0, True\n            Err.Clear\n        End If         \n    Next\nElse\n    MsgBox \"ȡ¼Ʋ\", vbExclamation, \"ʾϢ\"\nEnd If\n\nOn Error GoTo 0\nSet objWMIService = Nothing\nSet colProcesses = Nothing\nSet colProcesses2 = Nothing\nSet colProcesses3 = Nothing\nSet objProcess = Nothing\nSet objShell = Nothing"
  },
  {
    "path": "config/URL_config.ini",
    "content": "﻿"
  },
  {
    "path": "config/config.ini",
    "content": "﻿[录制设置]\nlanguage(zh_cn/en) = zh_cn\n是否跳过代理检测(是/否) = 否\n直播保存路径(不填则默认) = \n保存文件夹是否以作者区分 = 是\n保存文件夹是否以时间区分 = 否\n保存文件夹是否以标题区分 = 否\n保存文件名是否包含标题 = 否\n是否去除名称中的表情符号 = 是\n视频保存格式ts|mkv|flv|mp4|mp3音频|m4a音频 = ts\n原画|超清|高清|标清|流畅 = 原画\n是否使用代理ip(是/否) = 是\n代理地址 = \n同一时间访问网络的线程数 = 3\n循环时间(秒) = 300\n排队读取网址时间(秒) = 0\n是否显示循环秒数 = 否\n是否显示直播源地址 = 否\n分段录制是否开启 = 是\n是否强制启用https录制 = 否\n录制空间剩余阈值(gb) = 1.0\n视频分段时间(秒) = 1800\n录制完成后自动转为mp4格式 = 是\nmp4格式重新编码为h264 = 否\n追加格式后删除原文件 = 是\n生成时间字幕文件 = 否\n是否录制完成后执行自定义脚本 = 否\n自定义脚本执行命令 = \n使用代理录制的平台(逗号分隔) = tiktok, sooplive, pandalive, winktv, flextv, popkontv, twitch, liveme, showroom, chzzk, shopee, shp, youtu\n额外使用代理录制的平台(逗号分隔) = \n\n[推送配置]\n# 可选微信|钉钉|tg|邮箱|bark|ntfy|pushplus 可填多个\n直播状态推送渠道 =\n钉钉推送接口链接 =\n微信推送接口链接 =\nbark推送接口链接 =\nbark推送中断级别 = active\nbark推送铃声 =\n钉钉通知@对象(填手机号) =\n钉钉通知@全体(是/否) = 否\ntgapi令牌 =\ntg聊天id(个人或者群组id) =\nsmtp邮件服务器 =\n是否使用SMTP服务SSL加密(是/否) =\nSMTP邮件服务器端口 =\n邮箱登录账号 =\n发件人密码(授权码) =\n发件人邮箱 =\n发件人显示昵称 =\n收件人邮箱 =\nntfy推送地址 = https://ntfy.sh/xxxx\nntfy推送标签 = tada\nntfy推送邮箱 =\npushplus推送token =\n自定义推送标题 =\n自定义开播推送内容 =\n自定义关播推送内容 =\n只推送通知不录制(是/否) = 否\n直播推送检测频率(秒) = 1800\n开播推送开启(是/否) = 是\n关播推送开启(是/否)= 否\n\n[Cookie]\n# 录制抖音必填\n抖音cookie = ttwid=1%7CB1qls3GdnZhUov9o2NxOMxxYS2ff6OSvEWbv0ytbES4%7C1680522049%7C280d802d6d478e3e78d0c807f7c487e7ffec0ae4e5fdd6a0fe74c3c6af149511; my_rd=1; passport_csrf_token=3ab34460fa656183fccfb904b16ff742; passport_csrf_token_default=3ab34460fa656183fccfb904b16ff742; d_ticket=9f562383ac0547d0b561904513229d76c9c21; n_mh=hvnJEQ4Q5eiH74-84kTFUyv4VK8xtSrpRZG1AhCeFNI; store-region=cn-fj; store-region-src=uid; LOGIN_STATUS=1; __security_server_data_status=1; FORCE_LOGIN=%7B%22videoConsumedRemainSeconds%22%3A180%7D; pwa2=%223%7C0%7C3%7C0%22; download_guide=%223%2F20230729%2F0%22; volume_info=%7B%22isUserMute%22%3Afalse%2C%22isMute%22%3Afalse%2C%22volume%22%3A0.6%7D; strategyABtestKey=%221690824679.923%22; stream_recommend_feed_params=%22%7B%5C%22cookie_enabled%5C%22%3Atrue%2C%5C%22screen_width%5C%22%3A1536%2C%5C%22screen_height%5C%22%3A864%2C%5C%22browser_online%5C%22%3Atrue%2C%5C%22cpu_core_num%5C%22%3A8%2C%5C%22device_memory%5C%22%3A8%2C%5C%22downlink%5C%22%3A10%2C%5C%22effective_type%5C%22%3A%5C%224g%5C%22%2C%5C%22round_trip_time%5C%22%3A150%7D%22; VIDEO_FILTER_MEMO_SELECT=%7B%22expireTime%22%3A1691443863751%2C%22type%22%3Anull%7D; home_can_add_dy_2_desktop=%221%22; __live_version__=%221.1.1.2169%22; device_web_cpu_core=8; device_web_memory_size=8; xgplayer_user_id=346045893336; csrf_session_id=2e00356b5cd8544d17a0e66484946f28; odin_tt=724eb4dd23bc6ffaed9a1571ac4c757ef597768a70c75fef695b95845b7ffcd8b1524278c2ac31c2587996d058e03414595f0a4e856c53bd0d5e5f56dc6d82e24004dc77773e6b83ced6f80f1bb70627; __ac_nonce=064caded4009deafd8b89; __ac_signature=_02B4Z6wo00f01HLUuwwAAIDBh6tRkVLvBQBy9L-AAHiHf7; ttcid=2e9619ebbb8449eaa3d5a42d8ce88ec835; webcast_leading_last_show_time=1691016922379; webcast_leading_total_show_times=1; webcast_local_quality=sd; live_can_add_dy_2_desktop=%221%22; msToken=1JDHnVPw_9yTvzIrwb7cQj8dCMNOoesXbA_IooV8cezcOdpe4pzusZE7NB7tZn9TBXPr0ylxmv-KMs5rqbNUBHP4P7VBFUu0ZAht_BEylqrLpzgt3y5ne_38hXDOX8o=; msToken=jV_yeN1IQKUd9PlNtpL7k5vthGKcHo0dEh_QPUQhr8G3cuYv-Jbb4NnIxGDmhVOkZOCSihNpA2kvYtHiTW25XNNX_yrsv5FN8O6zm3qmCIXcEe0LywLn7oBO2gITEeg=; tt_scid=mYfqpfbDjqXrIGJuQ7q-DlQJfUSG51qG.KUdzztuGP83OjuVLXnQHjsz-BRHRJu4e986\n快手cookie =\ntiktok_cookie =\n虎牙cookie =\n斗鱼cookie =\nyy_cookie =\nb站cookie =\n小红书cookie =\nbigo_cookie =\nblued_cookie =\nsooplive_cookie =\nnetease_cookie =\n千度热播_cookie =\npandatv_cookie =\n猫耳fm_cookie =\nwinktv_cookie =\nflextv_cookie =\nlook_cookie =\ntwitcasting_cookie =\nbaidu_cookie =\nweibo_cookie =\nkugou_cookie =\ntwitch_cookie =\nliveme_cookie =\nhuajiao_cookie =\nliuxing_cookie =\nshowroom_cookie =\nacfun_cookie =\nchangliao_cookie =\nyinbo_cookie =\nyingke_cookie =\nzhihu_cookie =\nchzzk_cookie =\nhaixiu_cookie =\nvvxqiu_cookie =\n17live_cookie =\nlanglive_cookie =\npplive_cookie =\n6room_cookie =\nlehaitv_cookie =\nhuamao_cookie =\nshopee_cookie =\nyoutube_cookie =\ntaobao_cookie =\njd_cookie =\nfaceit_cookie =\nmigu_cookie =\nlianjie_cookie =\nlaixiu_cookie =\npicarto_cookie =\n\n\n[Authorization]\npopkontv_token =\n\n[账号密码]\nsooplive账号 =\nsooplive密码 =\nflextv账号 =\nflextv密码 =\npopkontv账号 =\npartner_code = P-00001\npopkontv密码 =\ntwitcasting账号类型 = normal\ntwitcasting账号 =\ntwitcasting密码 ="
  },
  {
    "path": "demo.py",
    "content": "# -*- coding: utf-8 -*-\nimport asyncio\nfrom src.logger import logger\nfrom src import spider\n\n# 以下示例直播间链接不保证时效性，请自行查看链接是否能正常访问\n# Please note that the following example live room links may not be up-to-date\nLIVE_STREAM_CONFIG = {\n    \"douyin\": {\n        \"url\": \"https://live.douyin.com/745964462470\",\n        \"func\": spider.get_douyin_app_stream_data,\n    },\n    \"tiktok\": {\n        \"url\": \"https://www.tiktok.com/@pearlgaga88/live\",\n        \"func\": spider.get_tiktok_stream_data,\n    },\n    \"kuaishou\": {\n        \"url\": \"https://live.kuaishou.com/u/yall1102\",\n        \"func\": spider.get_kuaishou_stream_data,\n    },\n    \"huya\": {\n        \"url\": \"https://www.huya.com/116\",\n        \"func\": spider.get_huya_app_stream_url,\n    },\n    \"douyu\": {\n        \"url\": \"https://www.douyu.com/topic/wzDBLS6?rid=4921614&dyshid=\",\n        \"func\": spider.get_douyu_info_data,\n    },\n    \"yy\": {\n        \"url\": \"https://www.yy.com/22490906/22490906\",\n        \"func\": spider.get_yy_stream_data,\n    },\n    \"bilibili\": {\n        \"url\": \"https://live.bilibili.com/21593109\",\n        \"func\": spider.get_bilibili_stream_data,\n    },\n    \"xhs\": {\n        \"url\": \"https://www.xiaohongshu.com/user/profile/6330049c000000002303c7ed?appuid=5f3f478a00000000010005b3\",\n        \"func\": spider.get_xhs_stream_url,\n    },\n    \"bigo\": {\n        \"url\": \"https://www.bigo.tv/cn/716418802\",\n        \"func\": spider.get_bigo_stream_url,\n    },\n    \"blued\": {\n        \"url\": \"https://app.blued.cn/live?id=Mp6G2R\",\n        \"func\": spider.get_blued_stream_url,\n    },\n    \"sooplive\": {\n        \"url\": \"https://play.sooplive.co.kr/sw7love\",\n        \"func\": spider.get_sooplive_stream_data,\n    },\n    \"netease\": {\n        \"url\": \"https://cc.163.com/583946984\",\n        \"func\": spider.get_netease_stream_data,\n    },\n    \"qiandurebo\": {\n        \"url\": \"https://qiandurebo.com/web/video.php?roomnumber=33333\",\n        \"func\": spider.get_qiandurebo_stream_data,\n    },\n    \"pandatv\": {\n        \"url\": \"https://www.pandalive.co.kr/live/play/bara0109\",\n        \"func\": spider.get_pandatv_stream_data,\n    },\n    \"maoerfm\": {\n        \"url\": \"https://fm.missevan.com/live/868895007\",\n        \"func\": spider.get_maoerfm_stream_url,\n    },\n    \"winktv\": {\n        \"url\": \"https://www.winktv.co.kr/live/play/anjer1004\",\n        \"func\": spider.get_winktv_stream_data,\n    },\n    \"flextv\": {\n        \"url\": \"https://www.ttinglive.com/channels/685479/live\",\n        \"func\": spider.get_flextv_stream_data,\n    },\n    \"looklive\": {\n        \"url\": \"https://look.163.com/live?id=65108820&position=3\",\n        \"func\": spider.get_looklive_stream_url,\n    },\n    \"popkontv\": {\n        \"url\": \"https://www.popkontv.com/live/view?castId=wjfal007&partnerCode=P-00117\",\n        \"func\": spider.get_popkontv_stream_url,\n    },\n    \"twitcasting\": {\n        \"url\": \"https://twitcasting.tv/c:uonq\",\n        \"func\": spider.get_twitcasting_stream_url,\n    },\n    \"baidu\": {\n        \"url\": \"https://live.baidu.com/m/media/pclive/pchome/live.html?room_id=9175031377&tab_category\",\n        \"func\": spider.get_baidu_stream_data,\n    },\n    \"weibo\": {\n        \"url\": \"https://weibo.com/u/7849520225\",\n        \"func\": spider.get_weibo_stream_data,\n    },\n    \"kugou\": {\n        \"url\": \"https://fanxing2.kugou.com/50428671?refer=2177&sourceFrom=\",\n        \"func\": spider.get_kugou_stream_url,\n    },\n    \"twitchtv\": {\n        \"url\": \"https://www.twitch.tv/gamerbee\",\n        \"func\": spider.get_twitchtv_stream_data,\n    },\n    \"liveme\": {\n        \"url\": \"https://www.liveme.com/zh/v/17141937295821012854/index.html\",\n        \"func\": spider.get_liveme_stream_url,\n    },\n    \"huajiao\": {\n        \"url\": \"https://www.huajiao.com/user/207446325\",\n        \"func\": spider.get_huajiao_stream_url,\n    },\n    \"showroom\": {\n        \"url\": \"https://www.showroom-live.com/room/profile?room_id=511033\",\n        \"func\": spider.get_showroom_stream_data,\n    },\n    \"acfun\": {\n        \"url\": \"https://live.acfun.cn/live/17912421\",\n        \"func\": spider.get_acfun_stream_data,\n    },\n    \"changliao\": {\n        \"url\": \"https://www.tlclw.com/801044397\",\n        \"func\": spider.get_changliao_stream_url,\n    },\n    \"yingke\": {\n        \"url\": \"https://www.inke.cn/liveroom/index.html?uid=710032101&id=1720857535354099\",\n        \"func\": spider.get_yingke_stream_url,\n    },\n    \"yinbo\": {\n        \"url\": \"https://live.ybw1666.com/800008687\",\n        \"func\": spider.get_yinbo_stream_url,\n    },\n    \"zhihu\": {\n        \"url\": \"https://www.zhihu.com/people/ac3a467005c5d20381a82230101308e9\",\n        \"func\": spider.get_zhihu_stream_url,\n    },\n    \"chzzk\": {\n        \"url\": \"https://chzzk.naver.com/live/458f6ec20b034f49e0fc6d03921646d2\",\n        \"func\": spider.get_chzzk_stream_data,\n    },\n    \"haixiu\": {\n        \"url\": \"https://www.haixiutv.com/6095106\",\n        \"func\": spider.get_haixiu_stream_url,\n    },\n    \"vvxqiu\": {\n        \"url\": \"https://h5webcdnp.vvxqiu.com//activity/videoShare/videoShare.html?h5Server=https://h5p.vvxqiu.com&\"\n               \"roomId=LP115664695&platformId=vvstar\",\n        \"func\": spider.get_vvxqiu_stream_url,\n    },\n    \"17live\": {\n        \"url\": \"https://17.live/en/live/6302408\",\n        \"func\": spider.get_17live_stream_url,\n    },\n    \"langlive\": {\n        \"url\": \"https://www.lang.live/en-US/room/3349463\",\n        \"func\": spider.get_langlive_stream_url,\n    },\n    \"pplive\": {\n        \"url\": \"https://m.pp.weimipopo.com/live/preview.html?uid=91648673&anchorUid=91625862&app=plpl\",\n        \"func\": spider.get_pplive_stream_url,\n    },\n    \"6room\": {\n        \"url\": \"https://v.6.cn/634435\",\n        \"func\": spider.get_6room_stream_url,\n    },\n    \"lehai\": {\n        \"url\": \"https://www.lehaitv.com/8059096\",\n        \"func\": spider.get_haixiu_stream_url,\n    },\n    \"huamao\": {\n        \"url\": \"https://h.catshow168.com/live/preview.html?uid=19066357&anchorUid=18895331\",\n        \"func\": spider.get_pplive_stream_url,\n    },\n    \"shopee\": {\n        \"url\": \"https://sg.shp.ee/GmpXeuf?uid=1006401066&session=802458\",\n        \"func\": spider.get_shopee_stream_url,\n    },\n    \"youtube\": {\n        \"url\": \"https://www.youtube.com/watch?v=cS6zS5hi1w0\",\n        \"func\": spider.get_youtube_stream_url,\n    },\n    \"taobao\": {\n        \"url\": \"https://m.tb.cn/h.TWp0HTd\",\n        \"func\": spider.get_taobao_stream_url,\n    },\n    \"jd\": {\n        \"url\": \"https://3.cn/28MLBy-E\",\n        \"func\": spider.get_jd_stream_url,\n    },\n    \"faceit\": {\n        \"url\": \"https://www.faceit.com/zh/players/Compl1/stream\",\n        \"func\": spider.get_faceit_stream_data,\n    },\n    \"lianjie\": {\n        \"url\": \"https://show.lailianjie.com/10000258\",\n        \"func\": spider.get_lianjie_stream_url,\n    },\n    \"migu\": {\n        \"url\": \"https://www.miguvideo.com/p/live/120000541321\",\n        \"func\": spider.get_migu_stream_url,\n    },\n    \"laixiu\": {\n        \"url\": \"https://www.imkktv.com/h5/share/video.html?uid=1845195&roomId=1710496\",\n        \"func\": spider.get_laixiu_stream_url,\n    },\n    \"picarto\": {\n        \"url\": \"https://www.picarto.tv/cuteavalanche\",\n        \"func\": spider.get_picarto_stream_url,\n    }\n}\n\n\ndef test_live_stream(platform_name: str, proxy_addr=None, cookies=None) -> None:\n    if platform_name in LIVE_STREAM_CONFIG:\n        config = LIVE_STREAM_CONFIG[platform_name]\n        try:\n            stream_data = asyncio.run(config['func'](config['url'], proxy_addr=proxy_addr, cookies=cookies))\n            logger.debug(f\"Stream data for {platform_name}: {stream_data}\")\n        except Exception as e:\n            logger.error(f\"Error fetching stream data for {platform_name}: {e}\")\n    else:\n        logger.warning(f\"No configuration found for platform: {platform_name}\")\n\n\nif __name__ == \"__main__\":\n    platform = \"douyin\"\n    test_live_stream(platform)\n"
  },
  {
    "path": "docker-compose.yaml",
    "content": "version: '3.8'\n\nservices:\n  app:\n    image: ihmily/douyin-live-recorder:latest\n    environment:\n      - TERM=xterm-256color\n    tty: true\n    stdin_open: true\n    #build: .\n    volumes:\n      - ./config:/app/config\n      - ./logs:/app/logs\n      - ./backup_config:/app/backup_config\n      - ./downloads:/app/downloads\n    restart: always"
  },
  {
    "path": "ffmpeg_install.py",
    "content": "# -*- coding: utf-8 -*-\n\n\"\"\"\nAuthor: Hmily\nGitHub: https://github.com/ihmily\nCopyright (c) 2024 by Hmily, All Rights Reserved.\n\"\"\"\n\nimport os\nimport re\nimport subprocess\nimport sys\nimport platform\nimport zipfile\nfrom pathlib import Path\nimport requests\nfrom tqdm import tqdm\nfrom src.logger import logger\n\ncurrent_platform = platform.system()\nexecute_dir = os.path.split(os.path.realpath(sys.argv[0]))[0]\ncurrent_env_path = os.environ.get('PATH')\nffmpeg_path = os.path.join(execute_dir, 'ffmpeg')\n\n\ndef unzip_file(zip_path: str | Path, extract_to: str | Path, delete: bool = True) -> None:\n    if not os.path.exists(extract_to):\n        os.makedirs(extract_to)\n\n    with zipfile.ZipFile(zip_path, 'r') as zip_ref:\n        zip_ref.extractall(extract_to)\n\n    if delete and os.path.exists(zip_path):\n        os.remove(zip_path)\n\n\ndef get_lanzou_download_link(url: str, password: str | None = None) -> str | None:\n    try:\n        headers = {\n            'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n            'Origin': 'https://wweb.lanzouv.com',\n            'Referer': 'https://wweb.lanzouv.com/iXncv0dly6mh',\n            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\n                          'Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0',\n        }\n        response = requests.get(url, headers=headers)\n        sign = re.search(\"var skdklds = '(.*?)';\", response.text).group(1)\n        data = {\n            'action': 'downprocess',\n            'sign': sign,\n            'p': password,\n            'kd': '1',\n        }\n        response = requests.post('https://wweb.lanzouv.com/ajaxm.php', headers=headers, data=data)\n        json_data = response.json()\n        download_url = json_data['dom'] + \"/file/\" + json_data['url']\n        response = requests.get(download_url, headers=headers)\n        return response.url\n    except Exception as e:\n        logger.error(f\"Failed to obtain ffmpeg download address. {e}\")\n\n\ndef install_ffmpeg_windows():\n    try:\n        logger.warning(\"ffmpeg is not installed.\")\n        logger.debug(\"Installing the latest version of ffmpeg for Windows...\")\n        ffmpeg_url = get_lanzou_download_link('https://wweb.lanzouv.com/iHAc22ly3r3g', 'eots')\n        if ffmpeg_url:\n            full_file_name = 'ffmpeg_latest_build_20250124.zip'\n            version = 'v20250124'\n            zip_file_path = Path(execute_dir) / full_file_name\n            if Path(zip_file_path).exists():\n                logger.debug(\"ffmpeg installation file already exists, start install...\")\n            else:\n                response = requests.get(ffmpeg_url, stream=True)\n                total_size = int(response.headers.get('Content-Length', 0))\n                block_size = 1024\n\n                with tqdm(total=total_size, unit=\"B\", unit_scale=True,\n                          ncols=100, desc=f'Downloading ffmpeg ({version})') as t:\n                    with open(zip_file_path, 'wb') as f:\n                        for data in response.iter_content(block_size):\n                            t.update(len(data))\n                            f.write(data)\n\n            unzip_file(zip_file_path, execute_dir)\n            os.environ['PATH'] = ffmpeg_path + os.pathsep + current_env_path\n            result = subprocess.run([\"ffmpeg\", \"-version\"], capture_output=True)\n            if result.returncode == 0:\n                logger.debug('ffmpeg installation was successful')\n            else:\n                logger.error('ffmpeg installation failed. Please manually install ffmpeg by yourself')\n            return True\n        else:\n            logger.error(\"Please manually install ffmpeg by yourself\")\n    except Exception as e:\n        logger.error(f\"type: {type(e).__name__}, ffmpeg installation failed {e}\")\n\n\ndef install_ffmpeg_mac():\n    logger.warning(\"ffmpeg is not installed.\")\n    logger.debug(\"Installing the stable version of ffmpeg for macOS...\")\n    try:\n        result = subprocess.run([\"brew\", \"install\", \"ffmpeg\"], capture_output=True)\n        if result.returncode == 0:\n            logger.debug('ffmpeg installation was successful. Restart for changes to take effect.')\n            return True\n        else:\n            logger.error(\"ffmpeg installation failed\")\n    except subprocess.CalledProcessError as e:\n        logger.error(f\"Failed to install ffmpeg using Homebrew. {e}\")\n        logger.error(\"Please install ffmpeg manually or check your Homebrew installation.\")\n    except Exception as e:\n        logger.error(f\"An unexpected error occurred: {e}\")\n\n\ndef install_ffmpeg_linux():\n    is_RHS = True\n\n    try:\n        logger.warning(\"ffmpeg is not installed.\")\n        logger.debug(\"Trying to install the stable version of ffmpeg\")\n        result = subprocess.run(['yum', '-y', 'update'], capture_output=True)\n        if result.returncode != 0:\n            logger.error(\"Failed to update package lists using yum.\")\n            return False\n\n        result = subprocess.run(['yum', 'install', '-y', 'ffmpeg'], capture_output=True)\n        if result.returncode == 0:\n            logger.debug(\"ffmpeg installation was successful using yum. Restart for changes to take effect.\")\n            return True\n        logger.error(result.stderr.decode('utf-8').strip())\n    except FileNotFoundError:\n        logger.debug(\"yum command not found, trying to install using apt...\")\n        is_RHS = False\n    except Exception as e:\n        logger.error(f\"An error occurred while trying to install ffmpeg using yum: {e}\")\n\n    if not is_RHS:\n        try:\n            logger.debug(\"Trying to install the stable version of ffmpeg for Linux using apt...\")\n            result = subprocess.run(['apt', 'update'], capture_output=True)\n            if result.returncode != 0:\n                logger.error(\"Failed to update package lists using apt\")\n                return False\n\n            result = subprocess.run(['apt', 'install', '-y', 'ffmpeg'], capture_output=True)\n            if result.returncode == 0:\n                logger.debug(\"ffmpeg installation was successful using apt. Restart for changes to take effect.\")\n                return True\n            else:\n                logger.error(result.stderr.decode('utf-8').strip())\n        except FileNotFoundError:\n            logger.error(\"apt command not found, unable to install ffmpeg. Please manually install ffmpeg by yourself\")\n        except Exception as e:\n            logger.error(f\"An error occurred while trying to install ffmpeg using apt: {e}\")\n    logger.error(\"Manual installation of ffmpeg is required. Please manually install ffmpeg by yourself.\")\n    return False\n\n\ndef install_ffmpeg() -> bool:\n    if current_platform == \"Windows\":\n        return install_ffmpeg_windows()\n    elif current_platform == \"Linux\":\n        return install_ffmpeg_linux()\n    elif current_platform == \"Darwin\":\n        return install_ffmpeg_mac()\n    else:\n        logger.debug(f\"ffmpeg auto installation is not supported on this platform: {current_platform}. \"\n                     f\"Please install ffmpeg manually.\")\n    return False\n\n\ndef ensure_ffmpeg_installed(func):\n    def wrapper(*args, **kwargs):\n        try:\n            result = subprocess.run(['ffmpeg', '-version'], capture_output=True)\n            version = result.stdout.strip()\n            if result.returncode == 0 and version:\n                return func(*args, **kwargs)\n        except FileNotFoundError:\n            pass\n        return False\n\n    def wrapped_func(*args, **kwargs):\n        if sys.version_info >= (3, 7):\n            res = wrapper(*args, **kwargs)\n        else:\n            res = wrapper(*args, **kwargs)\n        if not res:\n            install_ffmpeg()\n            res = wrapper(*args, **kwargs)\n\n        if not res:\n            raise RuntimeError(\"ffmpeg is not installed.\")\n\n        return func(*args, **kwargs)\n\n    return wrapped_func\n\n\ndef check_ffmpeg_installed() -> bool:\n    try:\n        result = subprocess.run(['ffmpeg', '-version'], capture_output=True)\n        version = result.stdout.strip()\n        if result.returncode == 0 and version:\n            return True\n    except FileNotFoundError:\n        pass\n    except OSError as e:\n        print(f\"OSError occurred: {e}. ffmpeg may not be installed correctly or is not available in the system PATH.\")\n        print(\"Please delete the ffmpeg and try to download and install again.\")\n    except Exception as e:\n        print(f\"An unexpected error occurred: {e}\")\n    return False\n\n\ndef check_ffmpeg() -> bool:\n    if not check_ffmpeg_installed():\n        return install_ffmpeg()\n    return True\n"
  },
  {
    "path": "i18n/en/LC_MESSAGES/.gitkeep",
    "content": ""
  },
  {
    "path": "i18n/zh_CN/LC_MESSAGES/zh_CN.po",
    "content": "# DouyinLiveRecorder.\n# Copyright (C) 2024 Hmily\n# This file is distributed under the same license as the DouyinLiveRecorder package.\n#\n#, fuzzy\n\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: 4.0.1\\n\"\n\"POT-Creation-Date: 2024-10-20 00:00+0800\\n\"\n\"PO-Revision-Date: 2024-11-09 03:05+0800\\n\"\n\"Last-Translator: Hmily <EMAIL@ADDRESS>\\n\"\n\"Language-Team: Chinese\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\"Language: zh_CN\\n\"\n\"Plural-Forms: nplurals=1; plural=0;\\n\"\n\n#: douyinliverecorder/spider.py\nmsgid \"IP banned. Please change device or network.\"\nmsgstr \"IP被禁止 请更换设备或网络\"\n\nmsgid \"The anchor did not start broadcasting.\"\nmsgstr \"主播并未开播\"\n\nmsgid \"sooplive platform login successful! Starting to fetch live streaming data...\"\nmsgstr \"sooplive平台登录成功！开始获取直播数据...\"\n\nmsgid \"sooplive live stream failed to retrieve, the live stream just ended.\"\nmsgstr \"sooplive直播获取失败,该直播间刚结束直播\"\n\nmsgid \"sooplive live stream retrieval failed, the live needs 19+, you are not logged in.\"\nmsgstr \"soop直播获取失败，该直播间需要年龄19+观看，您尚未登录\"\n\nmsgid \"Attempting to log in to the sooplive live streaming platform with your account and password, please ensure it is configured.\"\nmsgstr \"正在尝试使用您的账号和密码登录soop直播平台，请确保已在config配置文件中配置\"\n\nmsgid \"error message：Please check if the input sooplive live room address is correct.\"\nmsgstr \"错误信息：请检查输入的sooplive直播间地址是否正确\"\n\nmsgid \"Please check if the FlexTV account and password in the configuration file are correct.\"\nmsgstr \"请检查配置文件中的FlexTV账号和密码是否正确\"\n\nmsgid \"FlexTV live stream retrieval failed [not logged in]: 19+ live streams are only available for logged-in adults.\"\nmsgstr \"FlexTV直播获取失败[未登录]: 19+直播需要登录后是成人才可观看\"\n\nmsgid \"Attempting to log in to the FlexTV live streaming platform, please ensure your account and password are correctly filled in the configuration file.\"\nmsgstr \"正在尝试登录FlexTV直播平台，请确保已在配置文件中填写好您的账号和密码\"\n\nmsgid \"Logging into FlexTV platform...\"\nmsgstr \"FlexTV平台登录中...\"\n\nmsgid \"Logged into FlexTV platform successfully! Starting to fetch live streaming data...\"\nmsgstr \"FlexTV平台登录成功！开始获取直播数据...\"\n\nmsgid \"Look live currently only supports audio live streaming, not video live streaming!\"\nmsgstr \"Look直播暂时只支持音频直播，不支持Look视频直播!\"\n\nmsgid \"Failed to retrieve popkontv live stream [token does not exist or has expired]: Please log in to watch.\"\nmsgstr \"popkontv直播获取失败[token不存在或者已过期]: 请登录后观看\"\n\nmsgid \"Attempting to log in to the popkontv live streaming platform, please ensure your account and password are correctly filled in the configuration file.\"\nmsgstr \"正在尝试登录popkontv直播平台，请确保已在配置文件中填写好您的账号和密码\"\n\nmsgid \"Logging into popkontv platform...\"\nmsgstr \"popkontv平台登录中...\"\n\nmsgid \"Logged into popkontv platform successfully! Starting to fetch live streaming data...\"\nmsgstr \"popkontv平台登录成功！开始获取直播数据...\"\n\nmsgid \"Attempting to log in to TwitCasting...\"\nmsgstr \"TwitCasting正在尝试登录...\"\n\nmsgid \"TwitCasting login successful! Starting to fetch data...\"\nmsgstr \"TwitCasting 登录成功！开始获取数据...\"\n\nmsgid \"Failed to retrieve TwitCasting data, attempting to log in...\"\nmsgstr \"获取TwitCasting数据失败，正在尝试登录...\"\n\nmsgid \"Failed to retrieve live room data, the Huajiao live room address is not fixed, please manually change the address for recording.\"\nmsgstr \"获取直播间数据失败，花椒直播间地址是非固定的，请手动更换地址进行录制\"\n\nmsgid \"Fetch shopee live data failed, please update the address of the live broadcast room and try again.\"\nmsgstr \"获取shopee直播间数据失败，请手动更换直播录制地址后重试\"\n\n"
  },
  {
    "path": "i18n.py",
    "content": "import os\nimport sys\nimport gettext\nimport inspect\nimport builtins\nfrom pathlib import Path\n\n\ndef init_gettext(locale_dir, locale_name):\n    gettext.bindtextdomain('zh_CN', locale_dir)\n    gettext.textdomain('zh_CN')\n    os.environ['LANG'] = f'{locale_name}.utf8'\n    return gettext.gettext\n\n\nexecute_dir = os.path.split(os.path.realpath(sys.argv[0]))[0]\nif os.path.exists(Path(execute_dir) / '_internal/i18n'):\n    locale_path = Path(execute_dir) / '_internal/i18n'\nelse:\n    locale_path = Path(execute_dir) / 'i18n'\n_tr = init_gettext(locale_path, 'zh_CN')\noriginal_print = builtins.print\npackage_name = 'src'\n\n\ndef translated_print(*args, **kwargs):\n    for arg in args:\n        if package_name in inspect.stack()[1].filename:\n            translated_arg = _tr(str(arg))\n        else:\n            translated_arg = str(arg)\n        original_print(translated_arg, **kwargs)\n"
  },
  {
    "path": "index.html",
    "content": "<!--\n    Project: DouyinLiveRecorder\n    Author: Hmily\n    Build: 2023.08.14 - 20:24:05\n    GitHub Project URL: https://github.com/ihmily/DouyinLiveRecorder\n-->\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta name=\"referrer\" content=\"never\"> \n    <title>M3U8 视频播放器</title>\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n    <link href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&amp;display=swap\" rel=\"stylesheet\">\n    <script src=\"https://cdn.jsdelivr.net/npm/hls.js@latest/dist/hls.min.js\"></script>\n    <script src=\"https://cdn.jsdelivr.net/npm/flv.js@1.6.2/dist/flv.min.js\"></script>\n\n    <style>\n        body {\n            font-family: 'Roboto', Arial, sans-serif;\n            background-color: #1a237e;\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            height: 100vh;\n            margin: 0;\n            padding: 0;\n            color: #ffffff;\n            background-image: linear-gradient(120deg, #1a237e 0%, #283593 50%, #4a148c 100%);\n        }\n\n\n        .container {\n            max-width: 640px;\n            width: 80%;\n            padding: 20px;\n            background-color: #ffffff;\n            border-radius: 10px;\n            box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.15);\n        }\n\n        #videoPlayer {\n            width: 100%;\n            height: 0;\n            padding-bottom: 56.25%;\n            position: relative;\n            background-color: #000;\n            border-radius: 5px;\n            box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.15);\n            display: none;\n        }\n\n        video {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n        }\n\n        #videoUrlInput{\n        \tdisplay: block;\n        \twidth: 100%;\n        \tmargin: 10px 0;\n        \tpadding: 8px;\n        \tborder-radius: 5px;\n        \tborder: 1px solid #ccc;\n            box-sizing: border-box;\n        }\n\n        #playButton {\n            display: block;\n            width: 100%;\n            padding: 10px;\n            background-color: #283593;\n            color: white;\n            font-weight: bold;\n            border-radius: 5px;\n            border: none;\n            cursor: pointer;\n            transition: background-color 0.3s;\n            margin: 0 0 10px 0;\n            box-shadow: 0px 2px 4px 0px rgba(0,0,0,0.15);\n        }\n\n        #playButton:hover {\n            background-color: #1a237e;\n        }\n\n        .description {\n            margin-top: 20px;\n            line-height: 1.4;\n            font-size: 14px;\n            text-align: left;\n            background-color: #f8f9fa;\n            padding: 15px;\n            border-radius: 5px;\n            \n            box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.15);\n            display: block;\n        }\n        .footer {\n          margin-top: 30px;\n          text-align: center;\n          font-size: 14px;\n          color: white;\n        }\n\n        p{\n            color: black;\n        }\n        \n        a.no_style {\n            color: inherit;\n            text-decoration: none;\n        }\n        @media screen and (max-width: 768px) {\n            .container {\n                width: 90%;\n                border-radius: 0;\n                box-shadow: none;\n                margin-top:30px;\n            }\n            body {\n                overflow-y: scroll;\n            }\n            #videoUrlInput{\n                \n            \tmargin-top: 30px;\n            \tmargin-bottom: 10px;\n            }\n        \n        }\n\n    </style>\n</head>\n<body>\n\t<div class=\"container\">\n\t    <input type=\"text\" id=\"videoUrlInput\" placeholder=\"请输入 M3U8或者FLV 视频链接\">\n\t    <button id=\"playButton\">播放视频</button>\n\t    <div id=\"videoPlayer\">\n\t        <video controls></video>\n\t    </div>\n\t    <div class=\"description\">\n            <p><strong>说明</strong><p>\n            <p>M3U8文件格式</p>\n            <p>M3U8文件是采用UTF-8编码格式的M3U文件。M3U文件本身是一个纯文本索引文件，其核心功能是记录多媒体文件链接。当用户打开此类文件时，播放软件会根据索引查找相应的音视频文件网络地址，然后进行在线播放。</p>\n            <p>M3U最初设计用于播放音频文件，例如MP3。但随着时间推移，更多的播放器和软件开始使用M3U来播放视频文件列表，同时也支持在线流媒体音频源的指定。目前，许多播放器和软件都兼容M3U文件格式。</p>\n            <p>FLV文件格式（Flash Video Format）是Adobe公司开发的一种专门用于网页视频播放的文件格式。FLV格式的视频文件通常用于播放短视频和在线流媒体，可以嵌入到网页中供用户观看。FLV视频通常由Adobe Flash Player播放器播放，而其他第三方播放器也支持此格式。</p>\n        </div>\n        <div class=\"footer\">\n          <p>&copy; 2023 <a href='https://github.com/ihmily/DouyinLiveRecorder' class=\"no_style\" target=\"_blank\">Hmily</a>. All rights reserved.</p>\n        </div>\n\t    <script>\n    \t    function httpToHttps(url) {\n              if (url.startsWith(\"http://\")) {\n                return url.replace(\"http://\", \"https://\");\n              }\n              return url;\n            }\n\t        function playVideo() {\n            let videoUrl = document.getElementById('videoUrlInput').value;\n            const video = document.querySelector('#videoPlayer video');\n            const description = document.querySelector('.description');\n            if (videoUrl == ''){\n                alert('请输入视频链接');\n                return;\n            }\n            videoUrl = httpToHttps(videoUrl);\n            if (videoUrl.includes('.m3u8')) {\n                videoPlayer.style.display = 'block';\n                description.style.display = 'none';\n                if (Hls.isSupported()) {\n                    const hls = new Hls();\n                    hls.attachMedia(video);\n                    hls.on(Hls.Events.MEDIA_ATTACHED, () => {\n                        hls.loadSource(videoUrl);\n                    });\n                } else if (video.canPlayType('application/vnd.apple.mpegurl')) {\n                    video.src = videoUrl;\n                } else {\n                    alert('M3U8 格式不受您的浏览器支持。');\n                    console.error('M3U8 格式不受您的浏览器支持。');\n                    return;\n                }\n            } else if (videoUrl.includes('.flv')) {\n                if (flvjs.isSupported()) {\n                    const flvPlayer = flvjs.createPlayer({\n                        type: 'flv',\n                        url: videoUrl\n                    });\n                    flvPlayer.attachMediaElement(video);\n                    flvPlayer.load();\n                    flvPlayer.play();\n                } else {\n                    alert('FLV 格式不受您的浏览器支持。');\n                    console.error('FLV 格式不受您的浏览器支持。');\n                    return;\n                }\n\n                videoPlayer.style.display = 'block';\n                description.style.display = 'none';\n            } else {\n                console.error('不支持播放该视频格式。');\n                alert('不支持播放该视频格式。');\n            }\n        }\n\n        document.getElementById('playButton').addEventListener('click', playVideo);\n\t    </script>\n\t</div>\n</body>\n</html>\n"
  },
  {
    "path": "main.py",
    "content": "# -*- encoding: utf-8 -*-\n\n\"\"\"\nAuthor: Hmily\nGitHub: https://github.com/ihmily\nDate: 2023-07-17 23:52:05\nUpdate: 2025-10-23 19:48:05\nCopyright (c) 2023-2025 by Hmily, All Rights Reserved.\nFunction: Record live stream video.\n\"\"\"\nimport asyncio\nimport os\nimport sys\nimport builtins\nimport subprocess\nimport signal\nimport threading\nimport time\nimport datetime\nimport re\nimport shutil\nimport random\nimport uuid\nfrom pathlib import Path\nimport urllib.request\nfrom urllib.error import URLError, HTTPError\nfrom typing import Any\nimport configparser\nimport httpx\nfrom src import spider, stream\nfrom src.proxy import ProxyDetector\nfrom src.utils import logger\nfrom src import utils\nfrom msg_push import (\n    dingtalk, xizhi, tg_bot, send_email, bark, ntfy, pushplus\n)\nfrom ffmpeg_install import (\n    check_ffmpeg, ffmpeg_path, current_env_path\n)\n\nversion = \"v4.0.7\"\nplatforms = (\"\\n国内站点：抖音|快手|虎牙|斗鱼|YY|B站|小红书|bigo|blued|网易CC|千度热播|猫耳FM|Look|TwitCasting|百度|微博|\"\n             \"酷狗|花椒|流星|Acfun|畅聊|映客|音播|知乎|嗨秀|VV星球|17Live|浪Live|漂漂|六间房|乐嗨|花猫|淘宝|京东|咪咕|连接|来秀\"\n             \"\\n海外站点：TikTok|SOOP|PandaTV|WinkTV|FlexTV|PopkonTV|TwitchTV|LiveMe|ShowRoom|CHZZK|Shopee|\"\n             \"Youtube|Faceit|Picarto\")\n\nrecording = set()\nerror_count = 0\npre_max_request = 10\nmax_request_lock = threading.Lock()\nerror_window = []\nerror_window_size = 10\nerror_threshold = 5\nmonitoring = 0\nrunning_list = []\nurl_tuples_list = []\nurl_comments = []\ntext_no_repeat_url = []\ncreate_var = locals()\nfirst_start = True\nexit_recording = False\nneed_update_line_list = []\nfirst_run = True\nnot_record_list = []\nstart_display_time = datetime.datetime.now()\nglobal_proxy = False\nrecording_time_list = {}\nscript_path = os.path.split(os.path.realpath(sys.argv[0]))[0]\nconfig_file = f'{script_path}/config/config.ini'\nurl_config_file = f'{script_path}/config/URL_config.ini'\nbackup_dir = f'{script_path}/backup_config'\ntext_encoding = 'utf-8-sig'\nrstr = r\"[\\/\\\\\\:\\*\\？?\\\"\\<\\>\\|&#.。,， ~！· ]\"\ndefault_path = f'{script_path}/downloads'\nos.makedirs(default_path, exist_ok=True)\nfile_update_lock = threading.Lock()\nos_type = os.name\nclear_command = \"cls\" if os_type == 'nt' else \"clear\"\ncolor_obj = utils.Color()\nos.environ['PATH'] = ffmpeg_path + os.pathsep + current_env_path\n\n\ndef signal_handler(_signal, _frame):\n    sys.exit(0)\n\n\nsignal.signal(signal.SIGTERM, signal_handler)\n\n\ndef display_info() -> None:\n    global start_display_time\n    time.sleep(5)\n    while True:\n        try:\n            sys.stdout.flush()\n            time.sleep(5)\n            if Path(sys.executable).name != 'pythonw.exe':\n                os.system(clear_command)\n            print(f\"\\r共监测{monitoring}个直播中\", end=\" | \")\n            print(f\"同一时间访问网络的线程数: {max_request}\", end=\" | \")\n            print(f\"是否开启代理录制: {'是' if use_proxy else '否'}\", end=\" | \")\n            if split_video_by_time:\n                print(f\"录制分段开启: {split_time}秒\", end=\" | \")\n            else:\n                print(\"录制分段开启: 否\", end=\" | \")\n            if create_time_file:\n                print(\"是否生成时间文件: 是\", end=\" | \")\n            print(f\"录制视频质量为: {video_record_quality}\", end=\" | \")\n            print(f\"录制视频格式为: {video_save_type}\", end=\" | \")\n            print(f\"目前瞬时错误数为: {error_count}\", end=\" | \")\n            now = time.strftime(\"%H:%M:%S\", time.localtime())\n            print(f\"当前时间: {now}\")\n\n            if len(recording) == 0:\n                time.sleep(5)\n                if monitoring == 0:\n                    print(\"\\r没有正在监测和录制的直播\")\n                else:\n                    print(f\"\\r没有正在录制的直播 循环监测间隔时间：{delay_default}秒\")\n            else:\n                now_time = datetime.datetime.now()\n                print(\"x\" * 60)\n                no_repeat_recording = list(set(recording))\n                print(f\"正在录制{len(no_repeat_recording)}个直播: \")\n                for recording_live in no_repeat_recording:\n                    rt, qa = recording_time_list[recording_live]\n                    have_record_time = now_time - rt\n                    print(f\"{recording_live}[{qa}] 正在录制中 {str(have_record_time).split('.')[0]}\")\n\n                # print('\\n本软件已运行：'+str(now_time - start_display_time).split('.')[0])\n                print(\"x\" * 60)\n                start_display_time = now_time\n        except Exception as e:\n            logger.error(f\"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}\")\n\n\ndef update_file(file_path: str, old_str: str, new_str: str, start_str: str = None) -> str | None:\n    if old_str == new_str and start_str is None:\n        return old_str\n    with file_update_lock:\n        file_data = []\n        with open(file_path, \"r\", encoding=text_encoding) as f:\n            try:\n                for text_line in f:\n                    if old_str in text_line:\n                        text_line = text_line.replace(old_str, new_str)\n                        if start_str:\n                            text_line = f'{start_str}{text_line}'\n                    if text_line not in file_data:\n                        file_data.append(text_line)\n            except RuntimeError as e:\n                logger.error(f\"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}\")\n                if ini_URL_content:\n                    with open(file_path, \"w\", encoding=text_encoding) as f2:\n                        f2.write(ini_URL_content)\n                    return old_str\n        if file_data:\n            with open(file_path, \"w\", encoding=text_encoding) as f:\n                f.write(''.join(file_data))\n        return new_str\n\n\ndef delete_line(file_path: str, del_line: str, delete_all: bool = False) -> None:\n    with file_update_lock:\n        with open(file_path, 'r+', encoding=text_encoding) as f:\n            lines = f.readlines()\n            f.seek(0)\n            f.truncate()\n            skip_line = False\n            for txt_line in lines:\n                if del_line in txt_line:\n                    if delete_all or not skip_line:\n                        skip_line = True\n                        continue\n                else:\n                    skip_line = False\n                f.write(txt_line)\n\n\ndef get_startup_info(system_type: str):\n    if system_type == 'nt':\n        startup_info = subprocess.STARTUPINFO()\n        startup_info.dwFlags |= subprocess.STARTF_USESHOWWINDOW\n    else:\n        startup_info = None\n    return startup_info\n\n\ndef segment_video(converts_file_path: str, segment_save_file_path: str, segment_format: str, segment_time: str,\n                  is_original_delete: bool = True) -> None:\n    try:\n        if os.path.exists(converts_file_path) and os.path.getsize(converts_file_path) > 0:\n            ffmpeg_command = [\n                \"ffmpeg\",\n                \"-i\", converts_file_path,\n                \"-c:v\", \"copy\",\n                \"-c:a\", \"copy\",\n                \"-map\", \"0\",\n                \"-f\", \"segment\",\n                \"-segment_time\", segment_time,\n                \"-segment_format\", segment_format,\n                \"-reset_timestamps\", \"1\",\n                \"-movflags\", \"+frag_keyframe+empty_moov\",\n                segment_save_file_path,\n            ]\n            _output = subprocess.check_output(\n                ffmpeg_command, stderr=subprocess.STDOUT, startupinfo=get_startup_info(os_type)\n            )\n            if is_original_delete:\n                time.sleep(1)\n                if os.path.exists(converts_file_path):\n                    os.remove(converts_file_path)\n    except subprocess.CalledProcessError as e:\n        logger.error(f'Error occurred during conversion: {e}')\n    except Exception as e:\n        logger.error(f'An unknown error occurred: {e}')\n\n\ndef converts_mp4(converts_file_path: str, is_original_delete: bool = True) -> None:\n    try:\n        if os.path.exists(converts_file_path) and os.path.getsize(converts_file_path) > 0:\n            if converts_to_h264:\n                color_obj.print_colored(\"正在转码为MP4格式并重新编码为h264\\n\", color_obj.YELLOW)\n                ffmpeg_command = [\n                    \"ffmpeg\", \"-i\", converts_file_path,\n                    \"-c:v\", \"libx264\",\n                    \"-preset\", \"veryfast\",\n                    \"-crf\", \"23\",\n                    \"-vf\", \"format=yuv420p\",\n                    \"-c:a\", \"copy\",\n                    \"-f\", \"mp4\", converts_file_path.rsplit('.', maxsplit=1)[0] + \".mp4\",\n                ]\n            else:\n                color_obj.print_colored(\"正在转码为MP4格式\\n\", color_obj.YELLOW)\n                ffmpeg_command = [\n                    \"ffmpeg\", \"-i\", converts_file_path,\n                    \"-c:v\", \"copy\",\n                    \"-c:a\", \"copy\",\n                    \"-f\", \"mp4\", converts_file_path.rsplit('.', maxsplit=1)[0] + \".mp4\",\n                ]\n            _output = subprocess.check_output(\n                ffmpeg_command, stderr=subprocess.STDOUT, startupinfo=get_startup_info(os_type)\n            )\n            if is_original_delete:\n                time.sleep(1)\n                if os.path.exists(converts_file_path):\n                    os.remove(converts_file_path)\n    except subprocess.CalledProcessError as e:\n        logger.error(f'Error occurred during conversion: {e}')\n    except Exception as e:\n        logger.error(f'An unknown error occurred: {e}')\n\n\ndef converts_m4a(converts_file_path: str, is_original_delete: bool = True) -> None:\n    try:\n        if os.path.exists(converts_file_path) and os.path.getsize(converts_file_path) > 0:\n            _output = subprocess.check_output([\n                \"ffmpeg\", \"-i\", converts_file_path,\n                \"-n\", \"-vn\",\n                \"-c:a\", \"aac\", \"-bsf:a\", \"aac_adtstoasc\", \"-ab\", \"320k\",\n                converts_file_path.rsplit('.', maxsplit=1)[0] + \".m4a\",\n            ], stderr=subprocess.STDOUT, startupinfo=get_startup_info(os_type))\n            if is_original_delete:\n                time.sleep(1)\n                if os.path.exists(converts_file_path):\n                    os.remove(converts_file_path)\n    except subprocess.CalledProcessError as e:\n        logger.error(f'Error occurred during conversion: {e}')\n    except Exception as e:\n        logger.error(f'An unknown error occurred: {e}')\n\n\ndef generate_subtitles(record_name: str, ass_filename: str, sub_format: str = 'srt') -> None:\n    index_time = 0\n    today = datetime.datetime.now()\n    re_datatime = today.strftime('%Y-%m-%d %H:%M:%S')\n\n    def transform_int_to_time(seconds: int) -> str:\n        m, s = divmod(seconds, 60)\n        h, m = divmod(m, 60)\n        return f\"{h:02d}:{m:02d}:{s:02d}\"\n\n    while True:\n        index_time += 1\n        txt = str(index_time) + \"\\n\" + transform_int_to_time(index_time) + ',000 --> ' + transform_int_to_time(\n            index_time + 1) + ',000' + \"\\n\" + str(re_datatime) + \"\\n\\n\"\n\n        with open(f\"{ass_filename}.{sub_format.lower()}\", 'a', encoding=text_encoding) as f:\n            f.write(txt)\n\n        if record_name not in recording:\n            return\n        time.sleep(1)\n        today = datetime.datetime.now()\n        re_datatime = today.strftime('%Y-%m-%d %H:%M:%S')\n\n\ndef adjust_max_request() -> None:\n    global max_request, error_count, pre_max_request, error_window\n    preset = max_request\n\n    while True:\n        time.sleep(5)\n        with max_request_lock:\n            if error_window:\n                error_rate = sum(error_window) / len(error_window)\n            else:\n                error_rate = 0\n\n            if error_rate > error_threshold:\n                max_request = max(1, max_request - 1)\n            elif error_rate < error_threshold / 2 and max_request < preset:\n                max_request += 1\n            else:\n                pass\n\n            if pre_max_request != max_request:\n                pre_max_request = max_request\n                print(f\"\\r同一时间访问网络的线程数动态改为 {max_request}\")\n\n        error_window.append(error_count)\n        if len(error_window) > error_window_size:\n            error_window.pop(0)\n        error_count = 0\n\n\ndef push_message(record_name: str, live_url: str, content: str) -> None:\n    msg_title = push_message_title.strip() or \"直播间状态更新通知\"\n    push_functions = {\n        '微信': lambda: xizhi(xizhi_api_url, msg_title, content),\n        '钉钉': lambda: dingtalk(dingtalk_api_url, content, dingtalk_phone_num, dingtalk_is_atall),\n        '邮箱': lambda: send_email(\n            email_host, login_email, email_password, sender_email, sender_name,\n            to_email, msg_title, content, smtp_port, open_smtp_ssl\n        ),\n        'TG': lambda: tg_bot(tg_chat_id, tg_token, content),\n        'BARK': lambda: bark(\n            bark_msg_api, title=msg_title, content=content, level=bark_msg_level, sound=bark_msg_ring\n        ),\n        'NTFY': lambda: ntfy(\n            ntfy_api, title=msg_title, content=content, tags=ntfy_tags, action_url=live_url, email=ntfy_email\n        ),\n        'PUSHPLUS': lambda: pushplus(pushplus_token, msg_title, content),\n    }\n\n    for platform, func in push_functions.items():\n        if platform in live_status_push.upper():\n            try:\n                result = func()\n                print(f'提示信息：已经将[{record_name}]直播状态消息推送至你的{platform},'\n                      f' 成功{len(result[\"success\"])}, 失败{len(result[\"error\"])}')\n            except Exception as e:\n                color_obj.print_colored(f\"直播消息推送到{platform}失败: {e}\", color_obj.RED)\n\n\ndef run_script(command: str) -> None:\n    try:\n        process = subprocess.Popen(\n            command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=get_startup_info(os_type)\n        )\n        stdout, stderr = process.communicate()\n        stdout_decoded = stdout.decode('utf-8')\n        stderr_decoded = stderr.decode('utf-8')\n        if stdout_decoded.strip():\n            print(stdout_decoded)\n        if stderr_decoded.strip():\n            print(stderr_decoded)\n    except PermissionError as e:\n        logger.error(e)\n        logger.error('脚本无执行权限!, 若是Linux环境, 请先执行:chmod +x your_script.sh 授予脚本可执行权限')\n    except OSError as e:\n        logger.error(e)\n        logger.error('Please add `#!/bin/bash` at the beginning of your bash script file.')\n\n\ndef clear_record_info(record_name: str, record_url: str) -> None:\n    global monitoring\n    recording.discard(record_name)\n    if record_url in url_comments and record_url in running_list:\n        running_list.remove(record_url)\n        monitoring -= 1\n        color_obj.print_colored(f\"[{record_name}]已经从录制列表中移除\\n\", color_obj.YELLOW)\n\n\ndef direct_download_stream(source_url: str, save_path: str, record_name: str, live_url: str, platform: str) -> bool:\n    try:\n        with open(save_path, 'wb') as f:\n            client = httpx.Client(timeout=None)\n\n            headers = {}\n            header_params = get_record_headers(platform, live_url)\n            if header_params:\n                key, value = header_params.split(\":\", 1)\n                headers[key] = value\n\n            with client.stream('GET', source_url, headers=headers, follow_redirects=True) as response:\n                if response.status_code != 200:\n                    logger.error(f\"请求直播流失败，状态码: {response.status_code}\")\n                    return False\n\n                downloaded = 0\n                chunk_size = 1024 * 16\n\n                for chunk in response.iter_bytes(chunk_size):\n                    if live_url in url_comments or exit_recording:\n                        color_obj.print_colored(f\"[{record_name}]录制时已被注释或请求停止,下载中断\", color_obj.YELLOW)\n                        clear_record_info(record_name, live_url)\n                        return False\n\n                    if chunk:\n                        f.write(chunk)\n                        downloaded += len(chunk)\n                print()\n                return True\n    except Exception as e:\n        logger.error(f\"FLV下载错误: {e} 发生错误的行数: {e.__traceback__.tb_lineno}\")\n        return False\n\n\ndef check_subprocess(record_name: str, record_url: str, ffmpeg_command: list, save_type: str,\n                     script_command: str | None = None) -> bool:\n    save_file_path = ffmpeg_command[-1]\n    process = subprocess.Popen(\n        ffmpeg_command, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, startupinfo=get_startup_info(os_type)\n    )\n\n    subs_file_path = save_file_path.rsplit('.', maxsplit=1)[0]\n    subs_thread_name = f'subs_{Path(subs_file_path).name}'\n    if create_time_file and not split_video_by_time and '音频' not in save_type:\n        create_var[subs_thread_name] = threading.Thread(\n            target=generate_subtitles, args=(record_name, subs_file_path)\n        )\n        create_var[subs_thread_name].daemon = True\n        create_var[subs_thread_name].start()\n\n    while process.poll() is None:\n        if record_url in url_comments or exit_recording:\n            color_obj.print_colored(f\"[{record_name}]录制时已被注释,本条线程将会退出\", color_obj.YELLOW)\n            clear_record_info(record_name, record_url)\n            # process.terminate()\n            if os.name == 'nt':\n                if process.stdin:\n                    process.stdin.write(b'q')\n                    process.stdin.close()\n            else:\n                process.send_signal(signal.SIGINT)\n            process.wait()\n            return True\n        time.sleep(1)\n\n    return_code = process.returncode\n    stop_time = time.strftime('%Y-%m-%d %H:%M:%S')\n    if return_code == 0:\n        if converts_to_mp4 and save_type == 'TS':\n            if split_video_by_time:\n                file_paths = utils.get_file_paths(os.path.dirname(save_file_path))\n                prefix = os.path.basename(save_file_path).rsplit('_', maxsplit=1)[0]\n                for path in file_paths:\n                    if prefix in path:\n                        threading.Thread(target=converts_mp4, args=(path, delete_origin_file)).start()\n            else:\n                threading.Thread(target=converts_mp4, args=(save_file_path, delete_origin_file)).start()\n        print(f\"\\n{record_name} {stop_time} 直播录制完成\\n\")\n\n        if script_command:\n            logger.debug(\"开始执行脚本命令!\")\n            if \"python\" in script_command:\n                params = [\n                    f'--record_name \"{record_name}\"',\n                    f'--save_file_path \"{save_file_path}\"',\n                    f'--save_type {save_type}',\n                    f'--split_video_by_time {split_video_by_time}',\n                    f'--converts_to_mp4 {converts_to_mp4}',\n                ]\n            else:\n                params = [\n                    f'\"{record_name.split(\" \", maxsplit=1)[-1]}\"',\n                    f'\"{save_file_path}\"',\n                    save_type,\n                    f'split_video_by_time:{split_video_by_time}',\n                    f'converts_to_mp4:{converts_to_mp4}'\n                ]\n            script_command = script_command.strip() + ' ' + ' '.join(params)\n            run_script(script_command)\n            logger.debug(\"脚本命令执行结束!\")\n\n    else:\n        color_obj.print_colored(f\"\\n{record_name} {stop_time} 直播录制出错,返回码: {return_code}\\n\", color_obj.RED)\n\n    recording.discard(record_name)\n    return False\n\n\ndef clean_name(input_text):\n    cleaned_name = re.sub(rstr, \"_\", input_text.strip()).strip('_')\n    cleaned_name = cleaned_name.replace(\"（\", \"(\").replace(\"）\", \")\")\n    if clean_emoji:\n        cleaned_name = utils.remove_emojis(cleaned_name, '_').strip('_')\n    return cleaned_name or '空白昵称'\n\n\ndef get_quality_code(qn):\n    QUALITY_MAPPING = {\n        \"原画\": \"OD\",\n        \"蓝光\": \"BD\",\n        \"超清\": \"UHD\",\n        \"高清\": \"HD\",\n        \"标清\": \"SD\",\n        \"流畅\": \"LD\"\n    }\n    return QUALITY_MAPPING.get(qn)\n\n\ndef get_record_headers(platform, live_url):\n    live_domain = '/'.join(live_url.split('/')[0:3])\n    record_headers = {\n        'PandaTV': 'origin:https://www.pandalive.co.kr',\n        'WinkTV': 'origin:https://www.winktv.co.kr',\n        'PopkonTV': 'origin:https://www.popkontv.com',\n        'FlexTV': 'origin:https://www.flextv.co.kr',\n        '千度热播': 'referer:https://qiandurebo.com',\n        '17Live': 'referer:https://17.live/en/live/6302408',\n        '浪Live': 'referer:https://www.lang.live',\n        'shopee': f'origin:{live_domain}',\n        'Blued直播': 'referer:https://app.blued.cn'\n    }\n    return record_headers.get(platform)\n\n\ndef is_flv_preferred_platform(link):\n    return any(i in link for i in [\"douyin\", \"tiktok\"])\n\n\ndef select_source_url(link, stream_info):\n    if is_flv_preferred_platform(link):\n        codec = utils.get_query_params(stream_info.get('flv_url'), \"codec\")\n        if codec and codec[0] == 'h265':\n            logger.warning(\"FLV is not supported for h265 codec, use HLS source instead\")\n        else:\n            return stream_info.get('flv_url')\n\n    return stream_info.get('record_url')\n\n\ndef start_record(url_data: tuple, count_variable: int = -1) -> None:\n    global error_count\n\n    while True:\n        try:\n            record_finished = False\n            run_once = False\n            start_pushed = False\n            new_record_url = ''\n            count_time = time.time()\n            retry = 0\n            record_quality_zh, record_url, anchor_name = url_data\n            record_quality = get_quality_code(record_quality_zh)\n            proxy_address = proxy_addr\n            platform = '未知平台'\n            live_domain = '/'.join(record_url.split('/')[0:3])\n\n            if proxy_addr:\n                proxy_address = None\n                for platform in enable_proxy_platform_list:\n                    if platform and platform.strip() in record_url:\n                        proxy_address = proxy_addr\n                        break\n\n            if not proxy_address:\n                if extra_enable_proxy_platform_list:\n                    for pt in extra_enable_proxy_platform_list:\n                        if pt and pt.strip() in record_url:\n                            proxy_address = proxy_addr_bak or None\n\n            # print(f'\\r代理地址:{proxy_address}')\n            # print(f'\\r全局代理:{global_proxy}')\n            while True:\n                try:\n                    port_info = []\n                    if record_url.find(\"douyin.com/\") > -1:\n                        platform = '抖音直播'\n                        with semaphore:\n                            if 'v.douyin.com' not in record_url and '/user/' not in record_url:\n                                json_data = asyncio.run(spider.get_douyin_web_stream_data(\n                                    url=record_url,\n                                    proxy_addr=proxy_address,\n                                    cookies=dy_cookie))\n                            else:\n                                json_data = asyncio.run(spider.get_douyin_app_stream_data(\n                                    url=record_url,\n                                    proxy_addr=proxy_address,\n                                    cookies=dy_cookie))\n                            port_info = asyncio.run(\n                                stream.get_douyin_stream_url(json_data, record_quality, proxy_address))\n\n                    elif record_url.find(\"https://www.tiktok.com/\") > -1:\n                        platform = 'TikTok直播'\n                        with semaphore:\n                            if global_proxy or proxy_address:\n                                json_data = asyncio.run(spider.get_tiktok_stream_data(\n                                    url=record_url,\n                                    proxy_addr=proxy_address,\n                                    cookies=tiktok_cookie))\n                                port_info = asyncio.run(\n                                    stream.get_tiktok_stream_url(json_data, record_quality, proxy_address))\n                            else:\n                                logger.error(\"错误信息: 网络异常，请检查网络是否能正常访问TikTok平台\")\n\n                    elif record_url.find(\"https://live.kuaishou.com/\") > -1:\n                        platform = '快手直播'\n                        with semaphore:\n                            json_data = asyncio.run(spider.get_kuaishou_stream_data(\n                                url=record_url,\n                                proxy_addr=proxy_address,\n                                cookies=ks_cookie))\n                            port_info = asyncio.run(stream.get_kuaishou_stream_url(json_data, record_quality))\n\n                    elif record_url.find(\"https://www.huya.com/\") > -1:\n                        platform = '虎牙直播'\n                        with semaphore:\n                            if record_quality not in ['OD', 'BD', 'UHD']:\n                                json_data = asyncio.run(spider.get_huya_stream_data(\n                                    url=record_url,\n                                    proxy_addr=proxy_address,\n                                    cookies=hy_cookie))\n                                port_info = asyncio.run(stream.get_huya_stream_url(json_data, record_quality))\n                            else:\n                                port_info = asyncio.run(spider.get_huya_app_stream_url(\n                                    url=record_url,\n                                    proxy_addr=proxy_address,\n                                    cookies=hy_cookie\n                                ))\n\n                    elif record_url.find(\"https://www.douyu.com/\") > -1:\n                        platform = '斗鱼直播'\n                        with semaphore:\n                            json_data = asyncio.run(spider.get_douyu_info_data(\n                                url=record_url, proxy_addr=proxy_address, cookies=douyu_cookie))\n                            port_info = asyncio.run(stream.get_douyu_stream_url(\n                                json_data, video_quality=record_quality, cookies=douyu_cookie, proxy_addr=proxy_address\n                            ))\n\n                    elif record_url.find(\"https://www.yy.com/\") > -1:\n                        platform = 'YY直播'\n                        with semaphore:\n                            json_data = asyncio.run(spider.get_yy_stream_data(\n                                url=record_url, proxy_addr=proxy_address, cookies=yy_cookie))\n                            port_info = asyncio.run(stream.get_yy_stream_url(json_data))\n\n                    elif record_url.find(\"https://live.bilibili.com/\") > -1:\n                        platform = 'B站直播'\n                        with semaphore:\n                            json_data = asyncio.run(spider.get_bilibili_room_info(\n                                url=record_url, proxy_addr=proxy_address, cookies=bili_cookie))\n                            port_info = asyncio.run(stream.get_bilibili_stream_url(\n                                json_data, video_quality=record_quality, cookies=bili_cookie, proxy_addr=proxy_address))\n\n                    elif record_url.find(\"http://xhslink.com/\") > -1 or \\\n                            record_url.find(\"https://www.xiaohongshu.com/\") > -1:\n                        platform = '小红书直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_xhs_stream_url(\n                                record_url, proxy_addr=proxy_address, cookies=xhs_cookie))\n                            retry += 1\n\n                    elif record_url.find(\"www.bigo.tv/\") > -1 or record_url.find(\"slink.bigovideo.tv/\") > -1:\n                        platform = 'Bigo直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_bigo_stream_url(\n                                record_url, proxy_addr=proxy_address, cookies=bigo_cookie))\n\n                    elif record_url.find(\"https://app.blued.cn/\") > -1:\n                        platform = 'Blued直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_blued_stream_url(\n                                record_url, proxy_addr=proxy_address, cookies=blued_cookie))\n\n                    elif record_url.find(\"sooplive.co.kr/\") > -1 or record_url.find(\"sooplive.com/\") > -1:\n                        platform = 'SOOP'\n                        with semaphore:\n                            if global_proxy or proxy_address:\n                                json_data = asyncio.run(spider.get_sooplive_stream_data(\n                                    url=record_url, proxy_addr=proxy_address,\n                                    cookies=sooplive_cookie,\n                                    username=sooplive_username,\n                                    password=sooplive_password\n                                ))\n                                if json_data and json_data.get('new_cookies'):\n                                    utils.update_config(\n                                        config_file, 'Cookie', 'sooplive_cookie', json_data['new_cookies']\n                                    )\n                                port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))\n                            else:\n                                logger.error(\"错误信息: 网络异常，请检查本网络是否能正常访问SOOP平台\")\n\n                    elif record_url.find(\"cc.163.com/\") > -1:\n                        platform = '网易CC直播'\n                        with semaphore:\n                            json_data = asyncio.run(spider.get_netease_stream_data(\n                                url=record_url, cookies=netease_cookie))\n                            port_info = asyncio.run(stream.get_netease_stream_url(json_data, record_quality))\n\n                    elif record_url.find(\"qiandurebo.com/\") > -1:\n                        platform = '千度热播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_qiandurebo_stream_data(\n                                url=record_url, proxy_addr=proxy_address, cookies=qiandurebo_cookie))\n\n                    elif record_url.find(\"www.pandalive.co.kr/\") > -1:\n                        platform = 'PandaTV'\n                        with semaphore:\n                            if global_proxy or proxy_address:\n                                json_data = asyncio.run(spider.get_pandatv_stream_data(\n                                    url=record_url,\n                                    proxy_addr=proxy_address,\n                                    cookies=pandatv_cookie\n                                ))\n                                port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))\n                            else:\n                                logger.error(\"错误信息: 网络异常，请检查本网络是否能正常访问PandaTV直播平台\")\n\n                    elif record_url.find(\"fm.missevan.com/\") > -1:\n                        platform = '猫耳FM直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_maoerfm_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=maoerfm_cookie))\n\n                    elif record_url.find(\"www.winktv.co.kr/\") > -1:\n                        platform = 'WinkTV'\n                        with semaphore:\n                            if global_proxy or proxy_address:\n                                json_data = asyncio.run(spider.get_winktv_stream_data(\n                                    url=record_url,\n                                    proxy_addr=proxy_address,\n                                    cookies=winktv_cookie))\n                                port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))\n                            else:\n                                logger.error(\"错误信息: 网络异常，请检查本网络是否能正常访问WinkTV直播平台\")\n\n                    elif record_url.find(\"www.flextv.co.kr/\") > -1 or record_url.find(\"www.ttinglive.com/\") > -1:\n                        platform = 'FlexTV'\n                        with semaphore:\n                            if global_proxy or proxy_address:\n                                json_data = asyncio.run(spider.get_flextv_stream_data(\n                                    url=record_url,\n                                    proxy_addr=proxy_address,\n                                    cookies=flextv_cookie,\n                                    username=flextv_username,\n                                    password=flextv_password\n                                ))\n                                if json_data and json_data.get('new_cookies'):\n                                    utils.update_config(\n                                        config_file, 'Cookie', 'flextv_cookie', json_data['new_cookies']\n                                    )\n                                if 'play_url_list' in json_data:\n                                    port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))\n                                else:\n                                    port_info = json_data\n                            else:\n                                logger.error(\"错误信息: 网络异常，请检查本网络是否能正常访问FlexTV直播平台\")\n\n                    elif record_url.find(\"look.163.com/\") > -1:\n                        platform = 'Look直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_looklive_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=look_cookie\n                            ))\n\n                    elif record_url.find(\"www.popkontv.com/\") > -1:\n                        platform = 'PopkonTV'\n                        with semaphore:\n                            if global_proxy or proxy_address:\n                                port_info = asyncio.run(spider.get_popkontv_stream_url(\n                                    url=record_url,\n                                    proxy_addr=proxy_address,\n                                    access_token=popkontv_access_token,\n                                    username=popkontv_username,\n                                    password=popkontv_password,\n                                    partner_code=popkontv_partner_code\n                                ))\n                                if port_info and port_info.get('new_token'):\n                                    utils.update_config(\n                                        file_path=config_file, section='Authorization', key='popkontv_token',\n                                        new_value=port_info['new_token']\n                                    )\n\n                            else:\n                                logger.error(\"错误信息: 网络异常，请检查本网络是否能正常访问PopkonTV直播平台\")\n\n                    elif record_url.find(\"twitcasting.tv/\") > -1:\n                        platform = 'TwitCasting'\n                        with semaphore:\n                            json_data = asyncio.run(spider.get_twitcasting_stream_url(\n                                url=record_url,\n                                proxy_addr=proxy_address,\n                                cookies=twitcasting_cookie,\n                                account_type=twitcasting_account_type,\n                                username=twitcasting_username,\n                                password=twitcasting_password\n                            ))\n                            port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=False))\n\n                            if port_info and port_info.get('new_cookies'):\n                                utils.update_config(\n                                    file_path=config_file, section='Cookie', key='twitcasting_cookie',\n                                    new_value=port_info['new_cookies']\n                                )\n\n                    elif record_url.find(\"live.baidu.com/\") > -1:\n                        platform = '百度直播'\n                        with semaphore:\n                            json_data = asyncio.run(spider.get_baidu_stream_data(\n                                url=record_url,\n                                proxy_addr=proxy_address,\n                                cookies=baidu_cookie))\n                            port_info = asyncio.run(stream.get_stream_url(json_data, record_quality))\n\n                    elif record_url.find(\"weibo.com/\") > -1:\n                        platform = '微博直播'\n                        with semaphore:\n                            json_data = asyncio.run(spider.get_weibo_stream_data(\n                                url=record_url, proxy_addr=proxy_address, cookies=weibo_cookie))\n                            port_info = asyncio.run(stream.get_stream_url(\n                                json_data, record_quality, hls_extra_key='m3u8_url'))\n\n                    elif record_url.find(\"kugou.com/\") > -1:\n                        platform = '酷狗直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_kugou_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=kugou_cookie))\n\n                    elif record_url.find(\"www.twitch.tv/\") > -1:\n                        platform = 'TwitchTV'\n                        with semaphore:\n                            if global_proxy or proxy_address:\n                                json_data = asyncio.run(spider.get_twitchtv_stream_data(\n                                    url=record_url,\n                                    proxy_addr=proxy_address,\n                                    cookies=twitch_cookie\n                                ))\n                                port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))\n                            else:\n                                logger.error(\"错误信息: 网络异常，请检查本网络是否能正常访问TwitchTV直播平台\")\n\n                    elif record_url.find(\"www.liveme.com/\") > -1:\n                        if global_proxy or proxy_address:\n                            platform = 'LiveMe'\n                            with semaphore:\n                                port_info = asyncio.run(spider.get_liveme_stream_url(\n                                    url=record_url, proxy_addr=proxy_address, cookies=liveme_cookie))\n                        else:\n                            logger.error(\"错误信息: 网络异常，请检查本网络是否能正常访问LiveMe直播平台\")\n\n                    elif record_url.find(\"www.huajiao.com/\") > -1:\n                        platform = '花椒直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_huajiao_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=huajiao_cookie))\n\n                    elif record_url.find(\"7u66.com/\") > -1:\n                        platform = '流星直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_liuxing_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=liuxing_cookie))\n\n                    elif record_url.find(\"showroom-live.com/\") > -1:\n                        platform = 'ShowRoom'\n                        with semaphore:\n                            json_data = asyncio.run(spider.get_showroom_stream_data(\n                                url=record_url, proxy_addr=proxy_address, cookies=showroom_cookie))\n                            port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))\n\n                    elif record_url.find(\"live.acfun.cn/\") > -1 or record_url.find(\"m.acfun.cn/\") > -1:\n                        platform = 'Acfun'\n                        with semaphore:\n                            json_data = asyncio.run(spider.get_acfun_stream_data(\n                                url=record_url, proxy_addr=proxy_address, cookies=acfun_cookie))\n                            port_info = asyncio.run(stream.get_stream_url(\n                                json_data, record_quality, url_type='flv', flv_extra_key='url'))\n\n                    elif record_url.find(\"live.tlclw.com/\") > -1:\n                        platform = '畅聊直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_changliao_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=changliao_cookie))\n\n                    elif record_url.find(\"ybw1666.com/\") > -1:\n                        platform = '音播直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_yinbo_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=yinbo_cookie))\n\n                    elif record_url.find(\"www.inke.cn/\") > -1:\n                        platform = '映客直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_yingke_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=yingke_cookie))\n\n                    elif record_url.find(\"www.zhihu.com/\") > -1:\n                        platform = '知乎直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_zhihu_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=zhihu_cookie))\n\n                    elif record_url.find(\"chzzk.naver.com/\") > -1:\n                        platform = 'CHZZK'\n                        with semaphore:\n                            json_data = asyncio.run(spider.get_chzzk_stream_data(\n                                url=record_url, proxy_addr=proxy_address, cookies=chzzk_cookie))\n                            port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))\n\n                    elif record_url.find(\"www.haixiutv.com/\") > -1:\n                        platform = '嗨秀直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_haixiu_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=haixiu_cookie))\n\n                    elif record_url.find(\"vvxqiu.com/\") > -1:\n                        platform = 'VV星球'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_vvxqiu_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=vvxqiu_cookie))\n\n                    elif record_url.find(\"17.live/\") > -1:\n                        platform = '17Live'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_17live_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=yiqilive_cookie))\n\n                    elif record_url.find(\"www.lang.live/\") > -1:\n                        platform = '浪Live'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_langlive_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=langlive_cookie))\n\n                    elif record_url.find(\"m.pp.weimipopo.com/\") > -1:\n                        platform = '漂漂直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_pplive_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=pplive_cookie))\n\n                    elif record_url.find(\".6.cn/\") > -1:\n                        platform = '六间房直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_6room_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=six_room_cookie))\n\n                    elif record_url.find(\"lehaitv.com/\") > -1:\n                        platform = '乐嗨直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_haixiu_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=lehaitv_cookie))\n\n                    elif record_url.find(\"h.catshow168.com/\") > -1:\n                        platform = '花猫直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_pplive_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=huamao_cookie))\n\n                    elif record_url.find(\"live.shopee\") > -1 or record_url.find(\"shp.ee/\") > -1:\n                        platform = 'shopee'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_shopee_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=shopee_cookie))\n                            if port_info.get('uid'):\n                                new_record_url = record_url.split('?')[0] + '?' + str(port_info['uid'])\n\n                    elif record_url.find(\"www.youtube.com/\") > -1 or record_url.find(\"youtu.be/\") > -1:\n                        platform = 'Youtube'\n                        with semaphore:\n                            json_data = asyncio.run(spider.get_youtube_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=youtube_cookie))\n                            port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))\n\n                    elif record_url.find(\"tb.cn\") > -1:\n                        platform = '淘宝直播'\n                        with semaphore:\n                            json_data = asyncio.run(spider.get_taobao_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=taobao_cookie))\n                            port_info = asyncio.run(stream.get_stream_url(\n                                json_data, record_quality,\n                                url_type='all', hls_extra_key='hlsUrl', flv_extra_key='flvUrl'\n                            ))\n\n                    elif record_url.find(\"3.cn\") > -1 or record_url.find(\"m.jd.com\") > -1:\n                        platform = '京东直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_jd_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=jd_cookie))\n\n                    elif record_url.find(\"faceit.com/\") > -1:\n                        platform = 'faceit'\n                        with semaphore:\n                            if global_proxy or proxy_address:\n                                with semaphore:\n                                    json_data = asyncio.run(spider.get_faceit_stream_data(\n                                        url=record_url, proxy_addr=proxy_address, cookies=faceit_cookie))\n                                    port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True))\n                            else:\n                                logger.error(\"错误信息: 网络异常，请检查本网络是否能正常访问faceit直播平台\")\n\n                    elif record_url.find(\"www.miguvideo.com\") > -1 or record_url.find(\"m.miguvideo.com\") > -1:\n                        platform = '咪咕直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_migu_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=migu_cookie))\n\n                    elif record_url.find(\"show.lailianjie.com\") > -1:\n                        platform = '连接直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_lianjie_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=lianjie_cookie))\n\n                    elif record_url.find(\"www.imkktv.com\") > -1:\n                        platform = '来秀直播'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_laixiu_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=laixiu_cookie))\n\n                    elif record_url.find(\"www.picarto.tv\") > -1:\n                        platform = 'Picarto'\n                        with semaphore:\n                            port_info = asyncio.run(spider.get_picarto_stream_url(\n                                url=record_url, proxy_addr=proxy_address, cookies=picarto_cookie))\n\n                    elif record_url.find(\".m3u8\") > -1 or record_url.find(\".flv\") > -1:\n                        platform = '自定义录制直播'\n                        port_info = {\n                            \"anchor_name\": platform + '_' + str(uuid.uuid4())[:8],\n                            \"is_live\": True,\n                            \"record_url\": record_url,\n                        }\n                        if '.flv' in record_url:\n                            port_info['flv_url'] = record_url\n                        else:\n                            port_info['m3u8_url'] = record_url\n\n                    else:\n                        logger.error(f'{record_url} {platform}直播地址')\n                        return\n\n                    if anchor_name:\n                        if '主播:' in anchor_name:\n                            anchor_split: list = anchor_name.split('主播:')\n                            if len(anchor_split) > 1 and anchor_split[1].strip():\n                                anchor_name = anchor_split[1].strip()\n                            else:\n                                anchor_name = port_info.get(\"anchor_name\", '')\n                    else:\n                        anchor_name = port_info.get(\"anchor_name\", '')\n\n                    if not port_info.get(\"anchor_name\", ''):\n                        print(f'序号{count_variable} 网址内容获取失败,进行重试中...获取失败的地址是:{url_data}')\n                        with max_request_lock:\n                            error_count += 1\n                            error_window.append(1)\n                    else:\n                        anchor_name = clean_name(anchor_name)\n                        record_name = f'序号{count_variable} {anchor_name}'\n\n                        if record_url in url_comments:\n                            print(f\"[{anchor_name}]已被注释,本条线程将会退出\")\n                            clear_record_info(record_name, record_url)\n                            return\n\n                        if not url_data[-1] and run_once is False:\n                            if new_record_url:\n                                need_update_line_list.append(\n                                    f'{record_url}|{new_record_url},主播: {anchor_name.strip()}')\n                                not_record_list.append(new_record_url)\n                            else:\n                                need_update_line_list.append(f'{record_url}|{record_url},主播: {anchor_name.strip()}')\n                            run_once = True\n\n                        push_at = datetime.datetime.today().strftime('%Y-%m-%d %H:%M:%S')\n                        if port_info['is_live'] is False:\n                            print(f\"\\r{record_name} 等待直播... \")\n\n                            if start_pushed:\n                                if over_show_push:\n                                    push_content = \"直播间状态更新：[直播间名称] 直播已结束！时间：[时间]\"\n                                    if over_push_message_text:\n                                        push_content = over_push_message_text\n\n                                    push_content = (push_content.replace('[直播间名称]', record_name).\n                                                    replace('[时间]', push_at))\n                                    threading.Thread(\n                                        target=push_message,\n                                        args=(record_name, record_url, push_content.replace(r'\\n', '\\n')),\n                                        daemon=True\n                                    ).start()\n                                start_pushed = False\n\n                        else:\n                            content = f\"\\r{record_name} 正在直播中...\"\n                            print(content)\n\n                            if live_status_push and not start_pushed:\n                                if begin_show_push:\n                                    push_content = \"直播间状态更新：[直播间名称] 正在直播中，时间：[时间]\"\n                                    if begin_push_message_text:\n                                        push_content = begin_push_message_text\n\n                                    push_content = (push_content.replace('[直播间名称]', record_name).\n                                                    replace('[时间]', push_at))\n                                    threading.Thread(\n                                        target=push_message,\n                                        args=(record_name, record_url, push_content.replace(r'\\n', '\\n')),\n                                        daemon=True\n                                    ).start()\n                                start_pushed = True\n\n                            if disable_record:\n                                time.sleep(push_check_seconds)\n                                continue\n\n                            real_url = select_source_url(record_url, port_info)\n                            full_path = f'{default_path}/{platform}'\n                            if real_url:\n                                now = datetime.datetime.today().strftime(\"%Y-%m-%d_%H-%M-%S\")\n                                live_title = port_info.get('title')\n                                title_in_name = ''\n                                if live_title:\n                                    live_title = clean_name(live_title)\n                                    title_in_name = live_title + '_' if filename_by_title else ''\n\n                                try:\n                                    if len(video_save_path) > 0:\n                                        if not video_save_path.endswith(('/', '\\\\')):\n                                            full_path = f'{video_save_path}/{platform}'\n                                        else:\n                                            full_path = f'{video_save_path}{platform}'\n\n                                    full_path = full_path.replace(\"\\\\\", '/')\n                                    if folder_by_author:\n                                        full_path = f'{full_path}/{anchor_name}'\n                                    if folder_by_time:\n                                        full_path = f'{full_path}/{now[:10]}'\n                                    if folder_by_title and port_info.get('title'):\n                                        if folder_by_time:\n                                            full_path = f'{full_path}/{live_title}_{anchor_name}'\n                                        else:\n                                            full_path = f'{full_path}/{now[:10]}_{live_title}'\n                                    if not os.path.exists(full_path):\n                                        os.makedirs(full_path)\n                                except Exception as e:\n                                    logger.error(f\"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}\")\n\n                                if platform != '自定义录制直播':\n                                    if enable_https_recording and real_url.startswith(\"http://\"):\n                                        real_url = real_url.replace(\"http://\", \"https://\")\n\n                                    http_record_list = ['shopee', \"migu\"]\n                                    if platform in http_record_list:\n                                        real_url = real_url.replace(\"https://\", \"http://\")\n\n                                user_agent = (\"Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (\"\n                                              \"KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile \"\n                                              \"Safari/537.36\")\n\n                                rw_timeout = \"15000000\"\n                                analyzeduration = \"20000000\"\n                                probesize = \"10000000\"\n                                bufsize = \"8000k\"\n                                max_muxing_queue_size = \"1024\"\n                                for pt_host in overseas_platform_host:\n                                    if pt_host in record_url:\n                                        rw_timeout = \"50000000\"\n                                        analyzeduration = \"40000000\"\n                                        probesize = \"20000000\"\n                                        bufsize = \"15000k\"\n                                        max_muxing_queue_size = \"2048\"\n                                        break\n\n                                ffmpeg_command = [\n                                    'ffmpeg', \"-y\",\n                                    \"-v\", \"verbose\",\n                                    \"-rw_timeout\", rw_timeout,\n                                    \"-loglevel\", \"error\",\n                                    \"-hide_banner\",\n                                    \"-user_agent\", user_agent,\n                                    \"-protocol_whitelist\", \"rtmp,crypto,file,http,https,tcp,tls,udp,rtp,httpproxy\",\n                                    \"-thread_queue_size\", \"1024\",\n                                    \"-analyzeduration\", analyzeduration,\n                                    \"-probesize\", probesize,\n                                    \"-fflags\", \"+discardcorrupt\",\n                                    \"-re\", \"-i\", real_url,\n                                    \"-bufsize\", bufsize,\n                                    \"-sn\", \"-dn\",\n                                    \"-reconnect_delay_max\", \"60\",\n                                    \"-reconnect_streamed\", \"-reconnect_at_eof\",\n                                    \"-max_muxing_queue_size\", max_muxing_queue_size,\n                                    \"-correct_ts_overflow\", \"1\",\n                                    \"-avoid_negative_ts\", \"1\"\n                                ]\n\n                                headers = get_record_headers(platform, record_url)\n                                if headers:\n                                    ffmpeg_command.insert(11, \"-headers\")\n                                    ffmpeg_command.insert(12, headers)\n\n                                if proxy_address:\n                                    ffmpeg_command.insert(1, \"-http_proxy\")\n                                    ffmpeg_command.insert(2, proxy_address)\n\n                                recording.add(record_name)\n                                start_record_time = datetime.datetime.now()\n                                recording_time_list[record_name] = [start_record_time, record_quality_zh]\n                                rec_info = f\"\\r{anchor_name} 准备开始录制视频: {full_path}\"\n                                if show_url:\n                                    re_plat = ('WinkTV', 'PandaTV', 'ShowRoom', 'CHZZK', 'Youtube')\n                                    if platform in re_plat:\n                                        logger.info(\n                                            f\"{platform} | {anchor_name} | 直播源地址: {port_info.get('m3u8_url')}\")\n                                    else:\n                                        logger.info(\n                                            f\"{platform} | {anchor_name} | 直播源地址: {real_url}\")\n\n                                only_flv_record = False\n                                only_flv_platform_list = ['shopee', '花椒直播']\n                                if platform in only_flv_platform_list:\n                                    logger.debug(f\"提示: {platform} 将强制使用FLV格式录制\")\n                                    only_flv_record = True\n\n                                only_audio_record = False\n                                only_audio_platform_list = ['猫耳FM直播', 'Look直播']\n                                if platform in only_audio_platform_list:\n                                    only_audio_record = True\n\n                                record_save_type = video_save_type\n\n                                if is_flv_preferred_platform(record_url) and port_info.get('flv_url'):\n                                    codec = utils.get_query_params(port_info['flv_url'], \"codec\")\n                                    if codec and codec[0] == 'h265':\n                                        logger.warning(\"FLV is not supported for h265 codec, use TS format instead\")\n                                        record_save_type = \"TS\"\n\n                                if only_audio_record or any(i in record_save_type for i in ['MP3', 'M4A']):\n                                    try:\n                                        now = time.strftime(\"%Y-%m-%d_%H-%M-%S\", time.localtime())\n                                        extension = \"mp3\" if \"m4a\" not in record_save_type.lower() else \"m4a\"\n                                        name_format = \"_%03d\" if split_video_by_time else \"\"\n                                        save_file_path = (f\"{full_path}/{anchor_name}_{title_in_name}{now}\"\n                                                          f\"{name_format}.{extension}\")\n\n                                        if split_video_by_time:\n                                            print(f'\\r{anchor_name} 准备开始录制音频: {save_file_path}')\n\n                                            if \"MP3\" in record_save_type:\n                                                command = [\n                                                    \"-map\", \"0:a\",\n                                                    \"-c:a\", \"libmp3lame\",\n                                                    \"-ab\", \"320k\",\n                                                    \"-f\", \"segment\",\n                                                    \"-segment_time\", split_time,\n                                                    \"-reset_timestamps\", \"1\",\n                                                    save_file_path,\n                                                ]\n                                            else:\n                                                command = [\n                                                    \"-map\", \"0:a\",\n                                                    \"-c:a\", \"aac\",\n                                                    \"-bsf:a\", \"aac_adtstoasc\",\n                                                    \"-ab\", \"320k\",\n                                                    \"-f\", \"segment\",\n                                                    \"-segment_time\", split_time,\n                                                    \"-segment_format\", 'mpegts',\n                                                    \"-reset_timestamps\", \"1\",\n                                                    save_file_path,\n                                                ]\n\n                                        else:\n                                            if \"MP3\" in record_save_type:\n                                                command = [\n                                                    \"-map\", \"0:a\",\n                                                    \"-c:a\", \"libmp3lame\",\n                                                    \"-ab\", \"320k\",\n                                                    save_file_path,\n                                                ]\n\n                                            else:\n                                                command = [\n                                                    \"-map\", \"0:a\",\n                                                    \"-c:a\", \"aac\",\n                                                    \"-bsf:a\", \"aac_adtstoasc\",\n                                                    \"-ab\", \"320k\",\n                                                    \"-movflags\", \"+faststart\",\n                                                    save_file_path,\n                                                ]\n\n                                        ffmpeg_command.extend(command)\n                                        comment_end = check_subprocess(\n                                            record_name,\n                                            record_url,\n                                            ffmpeg_command,\n                                            record_save_type,\n                                            custom_script\n                                        )\n                                        if comment_end:\n                                            return\n\n                                    except subprocess.CalledProcessError as e:\n                                        logger.error(f\"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}\")\n                                        with max_request_lock:\n                                            error_count += 1\n                                            error_window.append(1)\n\n                                if only_flv_record:\n                                    logger.info(f\"Use Direct Downloader to Download FLV Stream: {record_url}\")\n                                    filename = anchor_name + f'_{title_in_name}' + now + '.flv'\n                                    save_file_path = f'{full_path}/{filename}'\n                                    print(f'{rec_info}/{filename}')\n\n                                    subs_file_path = save_file_path.rsplit('.', maxsplit=1)[0]\n                                    subs_thread_name = f'subs_{Path(subs_file_path).name}'\n                                    if create_time_file:\n                                        create_var[subs_thread_name] = threading.Thread(\n                                            target=generate_subtitles, args=(record_name, subs_file_path)\n                                        )\n                                        create_var[subs_thread_name].daemon = True\n                                        create_var[subs_thread_name].start()\n\n                                    try:\n                                        flv_url = port_info.get('flv_url')\n                                        if flv_url:\n                                            recording.add(record_name)\n                                            start_record_time = datetime.datetime.now()\n                                            recording_time_list[record_name] = [start_record_time, record_quality_zh]\n\n                                            download_success = direct_download_stream(\n                                                flv_url, save_file_path, record_name, record_url, platform\n                                            )\n\n                                            if download_success:\n                                                record_finished = True\n                                                print(\n                                                    f\"\\n{anchor_name} {time.strftime('%Y-%m-%d %H:%M:%S')} 直播录制完成\\n\")\n\n                                            recording.discard(record_name)\n                                        else:\n                                            logger.debug(\"未找到FLV直播流，跳过录制\")\n                                    except Exception as e:\n                                        clear_record_info(record_name, record_url)\n                                        color_obj.print_colored(\n                                            f\"\\n{anchor_name} {time.strftime('%Y-%m-%d %H:%M:%S')} 直播录制出错,请检查网络\\n\",\n                                            color_obj.RED)\n                                        logger.error(f\"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}\")\n                                        with max_request_lock:\n                                            error_count += 1\n                                            error_window.append(1)\n\n                                elif record_save_type == \"FLV\":\n                                    filename = anchor_name + f'_{title_in_name}' + now + \".flv\"\n                                    print(f'{rec_info}/{filename}')\n                                    save_file_path = full_path + '/' + filename\n\n                                    try:\n                                        if split_video_by_time:\n                                            now = time.strftime(\"%Y-%m-%d_%H-%M-%S\", time.localtime())\n                                            save_file_path = f\"{full_path}/{anchor_name}_{title_in_name}{now}_%03d.flv\"\n                                            command = [\n                                                \"-map\", \"0\",\n                                                \"-c:v\", \"copy\",\n                                                \"-c:a\", \"copy\",\n                                                \"-bsf:a\", \"aac_adtstoasc\",\n                                                \"-f\", \"segment\",\n                                                \"-segment_time\", split_time,\n                                                \"-segment_format\", \"flv\",\n                                                \"-reset_timestamps\", \"1\",\n                                                save_file_path\n                                            ]\n\n                                        else:\n                                            command = [\n                                                \"-map\", \"0\",\n                                                \"-c:v\", \"copy\",\n                                                \"-c:a\", \"copy\",\n                                                \"-bsf:a\", \"aac_adtstoasc\",\n                                                \"-f\", \"flv\",\n                                                \"{path}\".format(path=save_file_path),\n                                            ]\n                                        ffmpeg_command.extend(command)\n\n                                        comment_end = check_subprocess(\n                                            record_name,\n                                            record_url,\n                                            ffmpeg_command,\n                                            record_save_type,\n                                            custom_script\n                                        )\n                                        if comment_end:\n                                            return\n\n                                    except subprocess.CalledProcessError as e:\n                                        logger.error(f\"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}\")\n                                        with max_request_lock:\n                                            error_count += 1\n                                            error_window.append(1)\n\n                                    try:\n                                        if converts_to_mp4:\n                                            seg_file_path = f\"{full_path}/{anchor_name}_{title_in_name}{now}_%03d.mp4\"\n                                            if split_video_by_time:\n                                                segment_video(\n                                                    save_file_path, seg_file_path,\n                                                    segment_format='mp4', segment_time=split_time,\n                                                    is_original_delete=delete_origin_file\n                                                )\n                                            else:\n                                                threading.Thread(\n                                                    target=converts_mp4,\n                                                    args=(save_file_path, delete_origin_file)\n                                                ).start()\n\n                                        else:\n                                            seg_file_path = f\"{full_path}/{anchor_name}_{title_in_name}{now}_%03d.flv\"\n                                            if split_video_by_time:\n                                                segment_video(\n                                                    save_file_path, seg_file_path,\n                                                    segment_format='flv', segment_time=split_time,\n                                                    is_original_delete=delete_origin_file\n                                                )\n                                    except Exception as e:\n                                        logger.error(f\"转码失败: {e} \")\n\n                                elif record_save_type == \"MKV\":\n                                    filename = anchor_name + f'_{title_in_name}' + now + \".mkv\"\n                                    print(f'{rec_info}/{filename}')\n                                    save_file_path = full_path + '/' + filename\n\n                                    try:\n                                        if split_video_by_time:\n                                            now = time.strftime(\"%Y-%m-%d_%H-%M-%S\", time.localtime())\n                                            save_file_path = f\"{full_path}/{anchor_name}_{title_in_name}{now}_%03d.mkv\"\n                                            command = [\n                                                \"-flags\", \"global_header\",\n                                                \"-c:v\", \"copy\",\n                                                \"-c:a\", \"aac\",\n                                                \"-map\", \"0\",\n                                                \"-f\", \"segment\",\n                                                \"-segment_time\", split_time,\n                                                \"-segment_format\", \"matroska\",\n                                                \"-reset_timestamps\", \"1\",\n                                                save_file_path,\n                                            ]\n\n                                        else:\n                                            command = [\n                                                \"-flags\", \"global_header\",\n                                                \"-map\", \"0\",\n                                                \"-c:v\", \"copy\",\n                                                \"-c:a\", \"copy\",\n                                                \"-f\", \"matroska\",\n                                                \"{path}\".format(path=save_file_path),\n                                            ]\n                                        ffmpeg_command.extend(command)\n\n                                        comment_end = check_subprocess(\n                                            record_name,\n                                            record_url,\n                                            ffmpeg_command,\n                                            record_save_type,\n                                            custom_script\n                                        )\n                                        if comment_end:\n                                            return\n\n                                    except subprocess.CalledProcessError as e:\n                                        logger.error(f\"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}\")\n                                        with max_request_lock:\n                                            error_count += 1\n                                            error_window.append(1)\n\n                                elif record_save_type == \"MP4\":\n                                    filename = anchor_name + f'_{title_in_name}' + now + \".mp4\"\n                                    print(f'{rec_info}/{filename}')\n                                    save_file_path = full_path + '/' + filename\n\n                                    try:\n                                        if split_video_by_time:\n                                            now = time.strftime(\"%Y-%m-%d_%H-%M-%S\", time.localtime())\n                                            save_file_path = f\"{full_path}/{anchor_name}_{title_in_name}{now}_%03d.mp4\"\n                                            command = [\n                                                \"-c:v\", \"copy\",\n                                                \"-c:a\", \"aac\",\n                                                \"-map\", \"0\",\n                                                \"-f\", \"segment\",\n                                                \"-segment_time\", split_time,\n                                                \"-segment_format\", \"mp4\",\n                                                \"-reset_timestamps\", \"1\",\n                                                \"-movflags\", \"+frag_keyframe+empty_moov\",\n                                                save_file_path,\n                                            ]\n\n                                        else:\n                                            command = [\n                                                \"-map\", \"0\",\n                                                \"-c:v\", \"copy\",\n                                                \"-c:a\", \"copy\",\n                                                \"-f\", \"mp4\",\n                                                save_file_path,\n                                            ]\n\n                                        ffmpeg_command.extend(command)\n                                        comment_end = check_subprocess(\n                                            record_name,\n                                            record_url,\n                                            ffmpeg_command,\n                                            record_save_type,\n                                            custom_script\n                                        )\n                                        if comment_end:\n                                            return\n\n                                    except subprocess.CalledProcessError as e:\n                                        logger.error(f\"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}\")\n                                        with max_request_lock:\n                                            error_count += 1\n                                            error_window.append(1)\n\n                                else:\n                                    if split_video_by_time:\n                                        now = time.strftime(\"%Y-%m-%d_%H-%M-%S\", time.localtime())\n                                        filename = anchor_name + f'_{title_in_name}' + now + \".ts\"\n                                        print(f'{rec_info}/{filename}')\n\n                                        try:\n                                            save_file_path = f\"{full_path}/{anchor_name}_{title_in_name}{now}_%03d.ts\"\n                                            command = [\n                                                \"-c:v\", \"copy\",\n                                                \"-c:a\", \"copy\",\n                                                \"-map\", \"0\",\n                                                \"-f\", \"segment\",\n                                                \"-segment_time\", split_time,\n                                                \"-segment_format\", 'mpegts',\n                                                \"-reset_timestamps\", \"1\",\n                                                save_file_path,\n                                            ]\n\n                                            ffmpeg_command.extend(command)\n                                            comment_end = check_subprocess(\n                                                record_name,\n                                                record_url,\n                                                ffmpeg_command,\n                                                record_save_type,\n                                                custom_script\n                                            )\n                                            if comment_end:\n                                                if converts_to_mp4:\n                                                    file_paths = utils.get_file_paths(os.path.dirname(save_file_path))\n                                                    prefix = os.path.basename(save_file_path).rsplit('_', maxsplit=1)[0]\n                                                    for path in file_paths:\n                                                        if prefix in path:\n                                                            try:\n                                                                threading.Thread(\n                                                                    target=converts_mp4,\n                                                                    args=(path, delete_origin_file)\n                                                                ).start()\n                                                            except subprocess.CalledProcessError as e:\n                                                                logger.error(f\"转码失败: {e} \")\n                                                return\n\n                                        except subprocess.CalledProcessError as e:\n                                            logger.error(\n                                                f\"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}\")\n                                            with max_request_lock:\n                                                error_count += 1\n                                                error_window.append(1)\n\n                                    else:\n                                        filename = anchor_name + f'_{title_in_name}' + now + \".ts\"\n                                        print(f'{rec_info}/{filename}')\n                                        save_file_path = full_path + '/' + filename\n\n                                        try:\n                                            command = [\n                                                \"-c:v\", \"copy\",\n                                                \"-c:a\", \"copy\",\n                                                \"-map\", \"0\",\n                                                \"-f\", \"mpegts\",\n                                                save_file_path,\n                                            ]\n\n                                            ffmpeg_command.extend(command)\n                                            comment_end = check_subprocess(\n                                                record_name,\n                                                record_url,\n                                                ffmpeg_command,\n                                                record_save_type,\n                                                custom_script\n                                            )\n                                            if comment_end:\n                                                threading.Thread(\n                                                    target=converts_mp4, args=(save_file_path, delete_origin_file)\n                                                ).start()\n                                                return\n\n                                        except subprocess.CalledProcessError as e:\n                                            logger.error(f\"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}\")\n                                            with max_request_lock:\n                                                error_count += 1\n                                                error_window.append(1)\n\n                                count_time = time.time()\n\n                except Exception as e:\n                    logger.error(f\"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}\")\n                    with max_request_lock:\n                        error_count += 1\n                        error_window.append(1)\n\n                num = random.randint(-5, 5) + delay_default\n                if num < 0:\n                    num = 0\n                x = num\n\n                if error_count > 20:\n                    x = x + 60\n                    color_obj.print_colored(\"\\r瞬时错误太多,延迟加60秒\", color_obj.YELLOW)\n\n                # 这里是.如果录制结束后,循环时间会暂时变成30s后检测一遍. 这样一定程度上防止主播卡顿造成少录\n                # 当30秒过后检测一遍后. 会回归正常设置的循环秒数\n                if record_finished:\n                    count_time_end = time.time() - count_time\n                    if count_time_end < 60:\n                        x = 30\n                    record_finished = False\n\n                else:\n                    x = num\n\n                # 这里是正常循环\n                while x:\n                    x = x - 1\n                    if loop_time:\n                        print(f'\\r{anchor_name}循环等待{x}秒 ', end=\"\")\n                    time.sleep(1)\n                if loop_time:\n                    print('\\r检测直播间中...', end=\"\")\n        except Exception as e:\n            logger.error(f\"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}\")\n            with max_request_lock:\n                error_count += 1\n                error_window.append(1)\n            time.sleep(2)\n\n\ndef backup_file(file_path: str, backup_dir_path: str, limit_counts: int = 6) -> None:\n    try:\n        if not os.path.exists(backup_dir_path):\n            os.makedirs(backup_dir_path)\n\n        timestamp = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')\n        backup_file_name = os.path.basename(file_path) + '_' + timestamp\n        backup_file_path = os.path.join(backup_dir_path, backup_file_name).replace(\"\\\\\", \"/\")\n        shutil.copy2(file_path, backup_file_path)\n\n        files = os.listdir(backup_dir_path)\n        _files = [f for f in files if f.startswith(os.path.basename(file_path))]\n        _files.sort(key=lambda x: os.path.getmtime(os.path.join(backup_dir_path, x)))\n\n        while len(_files) > limit_counts:\n            oldest_file = _files[0]\n            os.remove(os.path.join(backup_dir_path, oldest_file))\n            _files = _files[1:]\n\n    except Exception as e:\n        logger.error(f'\\r备份配置文件 {file_path} 失败：{str(e)}')\n\n\ndef backup_file_start() -> None:\n    config_md5 = ''\n    url_config_md5 = ''\n\n    while True:\n        try:\n            if os.path.exists(config_file):\n                new_config_md5 = utils.check_md5(config_file)\n                if new_config_md5 != config_md5:\n                    backup_file(config_file, backup_dir)\n                    config_md5 = new_config_md5\n\n            if os.path.exists(url_config_file):\n                new_url_config_md5 = utils.check_md5(url_config_file)\n                if new_url_config_md5 != url_config_md5:\n                    backup_file(url_config_file, backup_dir)\n                    url_config_md5 = new_url_config_md5\n            time.sleep(600)\n        except Exception as e:\n            logger.error(f\"备份配置文件失败, 错误信息: {e}\")\n\n\ndef check_ffmpeg_existence() -> bool:\n    try:\n        result = subprocess.run(['ffmpeg', '-version'], check=True, capture_output=True, text=True)\n        if result.returncode == 0:\n            lines = result.stdout.splitlines()\n            version_line = lines[0]\n            built_line = lines[1]\n            print(version_line)\n            print(built_line)\n    except subprocess.CalledProcessError as e:\n        logger.error(e)\n    except FileNotFoundError:\n        pass\n    finally:\n        if check_ffmpeg():\n            time.sleep(1)\n            return True\n    return False\n\n\n# --------------------------初始化程序-------------------------------------\nprint(\"-----------------------------------------------------\")\nprint(\"|                DouyinLiveRecorder                 |\")\nprint(\"-----------------------------------------------------\")\n\nprint(f\"版本号: {version}\")\nprint(\"GitHub: https://github.com/ihmily/DouyinLiveRecorder\")\nprint(f'支持平台: {platforms}')\nprint('.....................................................')\nif not check_ffmpeg_existence():\n    logger.error(\"缺少ffmpeg无法进行录制，程序退出\")\n    sys.exit(1)\nos.makedirs(os.path.dirname(config_file), exist_ok=True)\nt3 = threading.Thread(target=backup_file_start, args=(), daemon=True)\nt3.start()\nutils.remove_duplicate_lines(url_config_file)\n\n\ndef read_config_value(config_parser: configparser.RawConfigParser, section: str, option: str, default_value: Any) \\\n        -> Any:\n    try:\n\n        config_parser.read(config_file, encoding=text_encoding)\n        if '录制设置' not in config_parser.sections():\n            config_parser.add_section('录制设置')\n        if '推送配置' not in config_parser.sections():\n            config_parser.add_section('推送配置')\n        if 'Cookie' not in config_parser.sections():\n            config_parser.add_section('Cookie')\n        if 'Authorization' not in config_parser.sections():\n            config_parser.add_section('Authorization')\n        if '账号密码' not in config_parser.sections():\n            config_parser.add_section('账号密码')\n        return config_parser.get(section, option)\n    except (configparser.NoSectionError, configparser.NoOptionError):\n        config_parser.set(section, option, str(default_value))\n        with open(config_file, 'w', encoding=text_encoding) as f:\n            config_parser.write(f)\n        return default_value\n\n\noptions = {\"是\": True, \"否\": False}\nconfig = configparser.RawConfigParser()\nlanguage = read_config_value(config, '录制设置', 'language(zh_cn/en)', \"zh_cn\")\nskip_proxy_check = options.get(read_config_value(config, '录制设置', '是否跳过代理检测(是/否)', \"否\"), False)\nif language and 'en' not in language.lower():\n    from i18n import translated_print\n\n    builtins.print = translated_print\n\ntry:\n    if skip_proxy_check:\n        global_proxy = True\n    else:\n        print('系统代理检测中，请耐心等待...')\n        response_g = urllib.request.urlopen(\"https://www.google.com/\", timeout=15)\n        global_proxy = True\n        print('\\r全局/规则网络代理已开启√')\n        pd = ProxyDetector()\n        if pd.is_proxy_enabled():\n            proxy_info = pd.get_proxy_info()\n            print(\"System Proxy: http://{}:{}\".format(proxy_info.ip, proxy_info.port))\nexcept HTTPError as err:\n    print(f\"HTTP error occurred: {err.code} - {err.reason}\")\nexcept URLError:\n    color_obj.print_colored(\"INFO：未检测到全局/规则网络代理，请检查代理配置（若无需录制海外直播请忽略此条提示）\",\n                            color_obj.YELLOW)\nexcept Exception as err:\n    print(\"An unexpected error occurred:\", err)\n\nwhile True:\n\n    try:\n        if not os.path.isfile(config_file):\n            with open(config_file, 'w', encoding=text_encoding) as file:\n                pass\n\n        ini_URL_content = ''\n        if os.path.isfile(url_config_file):\n            with open(url_config_file, 'r', encoding=text_encoding) as file:\n                ini_URL_content = file.read().strip()\n\n        if not ini_URL_content.strip():\n            input_url = input('请输入要录制的主播直播间网址（尽量使用PC网页端的直播间地址）:\\n')\n            with open(url_config_file, 'w', encoding=text_encoding) as file:\n                file.write(input_url)\n    except OSError as err:\n        logger.error(f\"发生 I/O 错误: {err}\")\n\n    video_save_path = read_config_value(config, '录制设置', '直播保存路径(不填则默认)', \"\")\n    folder_by_author = options.get(read_config_value(config, '录制设置', '保存文件夹是否以作者区分', \"是\"), False)\n    folder_by_time = options.get(read_config_value(config, '录制设置', '保存文件夹是否以时间区分', \"否\"), False)\n    folder_by_title = options.get(read_config_value(config, '录制设置', '保存文件夹是否以标题区分', \"否\"), False)\n    filename_by_title = options.get(read_config_value(config, '录制设置', '保存文件名是否包含标题', \"否\"), False)\n    clean_emoji = options.get(read_config_value(config, '录制设置', '是否去除名称中的表情符号', \"是\"), True)\n    video_save_type = read_config_value(config, '录制设置', '视频保存格式ts|mkv|flv|mp4|mp3音频|m4a音频', \"ts\")\n    video_record_quality = read_config_value(config, '录制设置', '原画|超清|高清|标清|流畅', \"原画\")\n    use_proxy = options.get(read_config_value(config, '录制设置', '是否使用代理ip(是/否)', \"是\"), False)\n    proxy_addr_bak = read_config_value(config, '录制设置', '代理地址', \"\")\n    proxy_addr = None if not use_proxy else proxy_addr_bak\n    max_request = int(read_config_value(config, '录制设置', '同一时间访问网络的线程数', 3))\n    semaphore = threading.Semaphore(max_request)\n    delay_default = int(read_config_value(config, '录制设置', '循环时间(秒)', 120))\n    local_delay_default = int(read_config_value(config, '录制设置', '排队读取网址时间(秒)', 0))\n    loop_time = options.get(read_config_value(config, '录制设置', '是否显示循环秒数', \"否\"), False)\n    show_url = options.get(read_config_value(config, '录制设置', '是否显示直播源地址', \"否\"), False)\n    split_video_by_time = options.get(read_config_value(config, '录制设置', '分段录制是否开启', \"否\"), False)\n    enable_https_recording = options.get(read_config_value(config, '录制设置', '是否强制启用https录制', \"否\"), False)\n    disk_space_limit = float(read_config_value(config, '录制设置', '录制空间剩余阈值(gb)', 1.0))\n    split_time = str(read_config_value(config, '录制设置', '视频分段时间(秒)', 1800))\n    converts_to_mp4 = options.get(read_config_value(config, '录制设置', '录制完成后自动转为mp4格式', \"否\"), False)\n    converts_to_h264 = options.get(read_config_value(config, '录制设置', 'mp4格式重新编码为h264', \"否\"), False)\n    delete_origin_file = options.get(read_config_value(config, '录制设置', '追加格式后删除原文件', \"否\"), False)\n    create_time_file = options.get(read_config_value(config, '录制设置', '生成时间字幕文件', \"否\"), False)\n    is_run_script = options.get(read_config_value(config, '录制设置', '是否录制完成后执行自定义脚本', \"否\"), False)\n    custom_script = read_config_value(config, '录制设置', '自定义脚本执行命令', \"\") if is_run_script else None\n    enable_proxy_platform = read_config_value(\n        config, '录制设置', '使用代理录制的平台(逗号分隔)',\n        'tiktok, soop, pandalive, winktv, flextv, popkontv, twitch, liveme, showroom, chzzk, shopee, shp, youtu, faceit'\n    )\n    enable_proxy_platform_list = enable_proxy_platform.replace('，', ',').split(',') if enable_proxy_platform else None\n    extra_enable_proxy = read_config_value(config, '录制设置', '额外使用代理录制的平台(逗号分隔)', '')\n    extra_enable_proxy_platform_list = extra_enable_proxy.replace('，', ',').split(',') if extra_enable_proxy else None\n    live_status_push = read_config_value(config, '推送配置', '直播状态推送渠道', \"\")\n    dingtalk_api_url = read_config_value(config, '推送配置', '钉钉推送接口链接', \"\")\n    xizhi_api_url = read_config_value(config, '推送配置', '微信推送接口链接', \"\")\n    bark_msg_api = read_config_value(config, '推送配置', 'bark推送接口链接', \"\")\n    bark_msg_level = read_config_value(config, '推送配置', 'bark推送中断级别', \"active\")\n    bark_msg_ring = read_config_value(config, '推送配置', 'bark推送铃声', \"bell\")\n    dingtalk_phone_num = read_config_value(config, '推送配置', '钉钉通知@对象(填手机号)', \"\")\n    dingtalk_is_atall = options.get(read_config_value(config, '推送配置', '钉钉通知@全体(是/否)', \"否\"), False)\n    tg_token = read_config_value(config, '推送配置', 'tgapi令牌', \"\")\n    tg_chat_id = read_config_value(config, '推送配置', 'tg聊天id(个人或者群组id)', \"\")\n    email_host = read_config_value(config, '推送配置', 'SMTP邮件服务器', \"\")\n    open_smtp_ssl = options.get(read_config_value(config, '推送配置', '是否使用SMTP服务SSL加密(是/否)', \"是\"), True)\n    smtp_port = read_config_value(config, '推送配置', 'SMTP邮件服务器端口', \"\")\n    login_email = read_config_value(config, '推送配置', '邮箱登录账号', \"\")\n    email_password = read_config_value(config, '推送配置', '发件人密码(授权码)', \"\")\n    sender_email = read_config_value(config, '推送配置', '发件人邮箱', \"\")\n    sender_name = read_config_value(config, '推送配置', '发件人显示昵称', \"\")\n    to_email = read_config_value(config, '推送配置', '收件人邮箱', \"\")\n    ntfy_api = read_config_value(config, '推送配置', 'ntfy推送地址', \"\")\n    ntfy_tags = read_config_value(config, '推送配置', 'ntfy推送标签', \"tada\")\n    ntfy_email = read_config_value(config, '推送配置', 'ntfy推送邮箱', \"\")\n    pushplus_token = read_config_value(config, '推送配置', 'pushplus推送token', \"\")\n    push_message_title = read_config_value(config, '推送配置', '自定义推送标题', \"直播间状态更新通知\")\n    begin_push_message_text = read_config_value(config, '推送配置', '自定义开播推送内容', \"\")\n    over_push_message_text = read_config_value(config, '推送配置', '自定义关播推送内容', \"\")\n    disable_record = options.get(read_config_value(config, '推送配置', '只推送通知不录制(是/否)', \"否\"), False)\n    push_check_seconds = int(read_config_value(config, '推送配置', '直播推送检测频率(秒)', 1800))\n    begin_show_push = options.get(read_config_value(config, '推送配置', '开播推送开启(是/否)', \"是\"), True)\n    over_show_push = options.get(read_config_value(config, '推送配置', '关播推送开启(是/否)', \"否\"), False)\n    sooplive_username = read_config_value(config, '账号密码', 'sooplive账号', '')\n    sooplive_password = read_config_value(config, '账号密码', 'sooplive密码', '')\n    flextv_username = read_config_value(config, '账号密码', 'flextv账号', '')\n    flextv_password = read_config_value(config, '账号密码', 'flextv密码', '')\n    popkontv_username = read_config_value(config, '账号密码', 'popkontv账号', '')\n    popkontv_partner_code = read_config_value(config, '账号密码', 'partner_code', 'P-00001')\n    popkontv_password = read_config_value(config, '账号密码', 'popkontv密码', '')\n    twitcasting_account_type = read_config_value(config, '账号密码', 'twitcasting账号类型', 'normal')\n    twitcasting_username = read_config_value(config, '账号密码', 'twitcasting账号', '')\n    twitcasting_password = read_config_value(config, '账号密码', 'twitcasting密码', '')\n    popkontv_access_token = read_config_value(config, 'Authorization', 'popkontv_token', '')\n    dy_cookie = read_config_value(config, 'Cookie', '抖音cookie', '')\n    ks_cookie = read_config_value(config, 'Cookie', '快手cookie', '')\n    tiktok_cookie = read_config_value(config, 'Cookie', 'tiktok_cookie', '')\n    hy_cookie = read_config_value(config, 'Cookie', '虎牙cookie', '')\n    douyu_cookie = read_config_value(config, 'Cookie', '斗鱼cookie', '')\n    yy_cookie = read_config_value(config, 'Cookie', 'yy_cookie', '')\n    bili_cookie = read_config_value(config, 'Cookie', 'B站cookie', '')\n    xhs_cookie = read_config_value(config, 'Cookie', '小红书cookie', '')\n    bigo_cookie = read_config_value(config, 'Cookie', 'bigo_cookie', '')\n    blued_cookie = read_config_value(config, 'Cookie', 'blued_cookie', '')\n    sooplive_cookie = read_config_value(config, 'Cookie', 'sooplive_cookie', '')\n    netease_cookie = read_config_value(config, 'Cookie', 'netease_cookie', '')\n    qiandurebo_cookie = read_config_value(config, 'Cookie', '千度热播_cookie', '')\n    pandatv_cookie = read_config_value(config, 'Cookie', 'pandatv_cookie', '')\n    maoerfm_cookie = read_config_value(config, 'Cookie', '猫耳fm_cookie', '')\n    winktv_cookie = read_config_value(config, 'Cookie', 'winktv_cookie', '')\n    flextv_cookie = read_config_value(config, 'Cookie', 'flextv_cookie', '')\n    look_cookie = read_config_value(config, 'Cookie', 'look_cookie', '')\n    twitcasting_cookie = read_config_value(config, 'Cookie', 'twitcasting_cookie', '')\n    baidu_cookie = read_config_value(config, 'Cookie', 'baidu_cookie', '')\n    weibo_cookie = read_config_value(config, 'Cookie', 'weibo_cookie', '')\n    kugou_cookie = read_config_value(config, 'Cookie', 'kugou_cookie', '')\n    twitch_cookie = read_config_value(config, 'Cookie', 'twitch_cookie', '')\n    liveme_cookie = read_config_value(config, 'Cookie', 'liveme_cookie', '')\n    huajiao_cookie = read_config_value(config, 'Cookie', 'huajiao_cookie', '')\n    liuxing_cookie = read_config_value(config, 'Cookie', 'liuxing_cookie', '')\n    showroom_cookie = read_config_value(config, 'Cookie', 'showroom_cookie', '')\n    acfun_cookie = read_config_value(config, 'Cookie', 'acfun_cookie', '')\n    changliao_cookie = read_config_value(config, 'Cookie', 'changliao_cookie', '')\n    yinbo_cookie = read_config_value(config, 'Cookie', 'yinbo_cookie', '')\n    yingke_cookie = read_config_value(config, 'Cookie', 'yingke_cookie', '')\n    zhihu_cookie = read_config_value(config, 'Cookie', 'zhihu_cookie', '')\n    chzzk_cookie = read_config_value(config, 'Cookie', 'chzzk_cookie', '')\n    haixiu_cookie = read_config_value(config, 'Cookie', 'haixiu_cookie', '')\n    vvxqiu_cookie = read_config_value(config, 'Cookie', 'vvxqiu_cookie', '')\n    yiqilive_cookie = read_config_value(config, 'Cookie', '17live_cookie', '')\n    langlive_cookie = read_config_value(config, 'Cookie', 'langlive_cookie', '')\n    pplive_cookie = read_config_value(config, 'Cookie', 'pplive_cookie', '')\n    six_room_cookie = read_config_value(config, 'Cookie', '6room_cookie', '')\n    lehaitv_cookie = read_config_value(config, 'Cookie', 'lehaitv_cookie', '')\n    huamao_cookie = read_config_value(config, 'Cookie', 'huamao_cookie', '')\n    shopee_cookie = read_config_value(config, 'Cookie', 'shopee_cookie', '')\n    youtube_cookie = read_config_value(config, 'Cookie', 'youtube_cookie', '')\n    taobao_cookie = read_config_value(config, 'Cookie', 'taobao_cookie', '')\n    jd_cookie = read_config_value(config, 'Cookie', 'jd_cookie', '')\n    faceit_cookie = read_config_value(config, 'Cookie', 'faceit_cookie', '')\n    migu_cookie = read_config_value(config, 'Cookie', 'migu_cookie', '')\n    lianjie_cookie = read_config_value(config, 'Cookie', 'lianjie_cookie', '')\n    laixiu_cookie = read_config_value(config, 'Cookie', 'laixiu_cookie', '')\n    picarto_cookie = read_config_value(config, 'Cookie', 'picarto_cookie', '')\n\n    video_save_type_list = (\"FLV\", \"MKV\", \"TS\", \"MP4\", \"MP3音频\", \"M4A音频\", \"MP3\", \"M4A\")\n    if video_save_type and video_save_type.upper() in video_save_type_list:\n        video_save_type = video_save_type.upper()\n    else:\n        video_save_type = \"TS\"\n\n    check_path = video_save_path or default_path\n    if utils.check_disk_capacity(check_path, show=first_run) < disk_space_limit:\n        exit_recording = True\n        if not recording:\n            logger.warning(f\"Disk space remaining is below {disk_space_limit} GB. \"\n                           f\"Exiting program due to the disk space limit being reached.\")\n            sys.exit(-1)\n\n\n    def contains_url(string: str) -> bool:\n        pattern = r\"(https?://)?(www\\.)?[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)+(:\\d+)?(/.*)?\"\n        return re.search(pattern, string) is not None\n\n\n    try:\n        url_comments, line_list, url_line_list = [[] for _ in range(3)]\n        with (open(url_config_file, \"r\", encoding=text_encoding, errors='ignore') as file):\n            for origin_line in file:\n                if origin_line in line_list:\n                    delete_line(url_config_file, origin_line)\n                line_list.append(origin_line)\n                line = origin_line.strip()\n                if len(line) < 18:\n                    continue\n\n                line_spilt = line.split('主播: ')\n                if len(line_spilt) > 2:\n                    line = update_file(url_config_file, line, f'{line_spilt[0]}主播: {line_spilt[-1]}')\n\n                is_comment_line = line.startswith(\"#\")\n                if is_comment_line:\n                    line = line.lstrip('#')\n\n                if re.search('[,，]', line):\n                    split_line = re.split('[,，]', line)\n                else:\n                    split_line = [line, '']\n\n                if len(split_line) == 1:\n                    url = split_line[0]\n                    quality, name = [video_record_quality, '']\n                elif len(split_line) == 2:\n                    if contains_url(split_line[0]):\n                        quality = video_record_quality\n                        url, name = split_line\n                    else:\n                        quality, url = split_line\n                        name = ''\n                else:\n                    quality, url, name = split_line\n\n                if quality not in (\"原画\", \"蓝光\", \"超清\", \"高清\", \"标清\", \"流畅\"):\n                    quality = '原画'\n\n                if url not in url_line_list:\n                    url_line_list.append(url)\n                else:\n                    delete_line(url_config_file, origin_line)\n\n                url = 'https://' + url if '://' not in url else url\n                url_host = url.split('/')[2]\n\n                platform_host = [\n                    'live.douyin.com',\n                    'v.douyin.com',\n                    'www.douyin.com',\n                    'live.kuaishou.com',\n                    'www.huya.com',\n                    'www.douyu.com',\n                    'www.yy.com',\n                    'live.bilibili.com',\n                    'www.redelight.cn',\n                    'www.xiaohongshu.com',\n                    'xhslink.com',\n                    'www.bigo.tv',\n                    'slink.bigovideo.tv',\n                    'app.blued.cn',\n                    'cc.163.com',\n                    'qiandurebo.com',\n                    'fm.missevan.com',\n                    'look.163.com',\n                    'twitcasting.tv',\n                    'live.baidu.com',\n                    'weibo.com',\n                    'fanxing.kugou.com',\n                    'fanxing2.kugou.com',\n                    'mfanxing.kugou.com',\n                    'www.huajiao.com',\n                    'www.7u66.com',\n                    'wap.7u66.com',\n                    'live.acfun.cn',\n                    'm.acfun.cn',\n                    'live.tlclw.com',\n                    'wap.tlclw.com',\n                    'live.ybw1666.com',\n                    'wap.ybw1666.com',\n                    'www.inke.cn',\n                    'www.zhihu.com',\n                    'www.haixiutv.com',\n                    \"h5webcdnp.vvxqiu.com\",\n                    \"17.live\",\n                    'www.lang.live',\n                    \"m.pp.weimipopo.com\",\n                    \"v.6.cn\",\n                    \"m.6.cn\",\n                    'www.lehaitv.com',\n                    'h.catshow168.com',\n                    'e.tb.cn',\n                    'huodong.m.taobao.com',\n                    '3.cn',\n                    'eco.m.jd.com',\n                    'www.miguvideo.com',\n                    'm.miguvideo.com',\n                    'show.lailianjie.com',\n                    'www.imkktv.com',\n                    'www.picarto.tv'\n                ]\n                overseas_platform_host = [\n                    'www.tiktok.com',\n                    'play.sooplive.co.kr',\n                    'm.sooplive.co.kr',\n                    'www.sooplive.com',\n                    'm.sooplive.com',\n                    'www.pandalive.co.kr',\n                    'www.winktv.co.kr',\n                    'www.flextv.co.kr',\n                    'www.ttinglive.com',\n                    'www.popkontv.com',\n                    'www.twitch.tv',\n                    'www.liveme.com',\n                    'www.showroom-live.com',\n                    'chzzk.naver.com',\n                    'm.chzzk.naver.com',\n                    'live.shopee.',\n                    '.shp.ee',\n                    'www.youtube.com',\n                    'youtu.be',\n                    'www.faceit.com'\n                ]\n\n                platform_host.extend(overseas_platform_host)\n                clean_url_host_list = (\n                    \"live.douyin.com\",\n                    \"live.bilibili.com\",\n                    \"www.huajiao.com\",\n                    \"www.zhihu.com\",\n                    \"www.huya.com\",\n                    \"chzzk.naver.com\",\n                    \"www.liveme.com\",\n                    \"www.haixiutv.com\",\n                    \"v.6.cn\",\n                    \"m.6.cn\",\n                    'www.lehaitv.com'\n                )\n\n                if 'live.shopee.' in url_host or '.shp.ee' in url_host:\n                    url_host = 'live.shopee.' if 'live.shopee.' in url_host else '.shp.ee'\n\n                if url_host in platform_host or any(ext in url for ext in (\".flv\", \".m3u8\")):\n                    if url_host in clean_url_host_list:\n                        url = update_file(url_config_file, old_str=url, new_str=url.split('?')[0])\n\n                    if 'xiaohongshu' in url:\n                        host_id = re.search('&host_id=(.*?)(?=&|$)', url)\n                        if host_id:\n                            new_url = url.split('?')[0] + f'?host_id={host_id.group(1)}'\n                            url = update_file(url_config_file, old_str=url, new_str=new_url)\n\n                    url_comments = [i for i in url_comments if url not in i]\n                    if is_comment_line:\n                        url_comments.append(url)\n                    else:\n                        new_line = (quality, url, name)\n                        url_tuples_list.append(new_line)\n                else:\n                    if not origin_line.startswith('#'):\n                        color_obj.print_colored(f\"\\r{origin_line.strip()} 本行包含未知链接.此条跳过\", color_obj.YELLOW)\n                        update_file(url_config_file, old_str=origin_line, new_str=origin_line, start_str='#')\n\n        while len(need_update_line_list):\n            a = need_update_line_list.pop()\n            replace_words = a.split('|')\n            if replace_words[0] != replace_words[1]:\n                if replace_words[1].startswith(\"#\"):\n                    start_with = '#'\n                    new_word = replace_words[1][1:]\n                else:\n                    start_with = None\n                    new_word = replace_words[1]\n                update_file(url_config_file, old_str=replace_words[0], new_str=new_word, start_str=start_with)\n\n        text_no_repeat_url = list(set(url_tuples_list))\n\n        if len(text_no_repeat_url) > 0:\n            for url_tuple in text_no_repeat_url:\n                monitoring = len(running_list)\n\n                if url_tuple[1] in not_record_list:\n                    continue\n\n                if url_tuple[1] not in running_list:\n                    print(f\"\\r{'新增' if not first_start else '传入'}地址: {url_tuple[1]}\")\n                    monitoring += 1\n                    args = [url_tuple, monitoring]\n                    create_var[f'thread_{monitoring}'] = threading.Thread(target=start_record, args=args)\n                    create_var[f'thread_{monitoring}'].daemon = True\n                    create_var[f'thread_{monitoring}'].start()\n                    running_list.append(url_tuple[1])\n                    time.sleep(local_delay_default)\n        url_tuples_list = []\n        first_start = False\n\n    except Exception as err:\n        logger.error(f\"错误信息: {err} 发生错误的行数: {err.__traceback__.tb_lineno}\")\n\n    if first_run:\n        t = threading.Thread(target=display_info, args=(), daemon=True)\n        t.start()\n        t2 = threading.Thread(target=adjust_max_request, args=(), daemon=True)\n        t2.start()\n        first_run = False\n\n    time.sleep(3)"
  },
  {
    "path": "msg_push.py",
    "content": "# -*- coding: utf-8 -*-\n\n\"\"\"\nAuthor: Hmily\nGitHub: https://github.com/ihmily\nDate: 2023-09-03 19:18:36\nUpdate: 2025-01-23 17:16:12\nCopyright (c) 2023-2024 by Hmily, All Rights Reserved.\n\"\"\"\nfrom typing import Dict, Any\nimport json\nimport base64\nimport urllib.request\nimport urllib.error\nimport smtplib\nfrom email.header import Header\nfrom email.mime.multipart import MIMEMultipart\nfrom email.mime.text import MIMEText\n\nno_proxy_handler = urllib.request.ProxyHandler({})\nopener = urllib.request.build_opener(no_proxy_handler)\nheaders: Dict[str, str] = {'Content-Type': 'application/json'}\n\n\ndef dingtalk(url: str, content: str, number: str = None, is_atall: bool = False) -> Dict[str, Any]:\n    success = []\n    error = []\n    api_list = url.replace('，', ',').split(',') if url.strip() else []\n    for api in api_list:\n        json_data = {\n            'msgtype': 'text',\n            'text': {\n                'content': content,\n            },\n            \"at\": {\n                \"atMobiles\": [\n                    number\n                ],\n                \"isAtAll\": is_atall\n            },\n        }\n        try:\n            data = json.dumps(json_data).encode('utf-8')\n            req = urllib.request.Request(api, data=data, headers=headers)\n            response = opener.open(req, timeout=10)\n            json_str = response.read().decode('utf-8')\n            json_data = json.loads(json_str)\n            if json_data['errcode'] == 0:\n                success.append(api)\n            else:\n                error.append(api)\n                print(f'钉钉推送失败, 推送地址：{api}, {json_data[\"errmsg\"]}')\n        except Exception as e:\n            error.append(api)\n            print(f'钉钉推送失败, 推送地址：{api}, 错误信息:{e}')\n    return {\"success\": success, \"error\": error}\n\n\ndef xizhi(url: str, title: str, content: str) -> Dict[str, Any]:\n    success = []\n    error = []\n    api_list = url.replace('，', ',').split(',') if url.strip() else []\n    for api in api_list:\n        json_data = {\n            'title': title,\n            'content': content\n        }\n        try:\n            data = json.dumps(json_data).encode('utf-8')\n            req = urllib.request.Request(api, data=data, headers=headers)\n            response = opener.open(req, timeout=10)\n            json_str = response.read().decode('utf-8')\n            json_data = json.loads(json_str)\n            if json_data['code'] == 200:\n                success.append(api)\n            else:\n                error.append(api)\n                print(f'微信推送失败, 推送地址：{api}, 失败信息：{json_data[\"msg\"]}')\n        except Exception as e:\n            error.append(api)\n            print(f'微信推送失败, 推送地址：{api}, 错误信息:{e}')\n    return {\"success\": success, \"error\": error}\n\n\ndef send_email(email_host: str, login_email: str, email_pass: str, sender_email: str, sender_name: str,\n               to_email: str, title: str, content: str, smtp_port: str = None, open_ssl: bool = True) -> Dict[str, Any]:\n    receivers = to_email.replace('，', ',').split(',') if to_email.strip() else []\n\n    try:\n        message = MIMEMultipart()\n        send_name = base64.b64encode(sender_name.encode(\"utf-8\")).decode()\n        message['From'] = f'=?UTF-8?B?{send_name}?= <{sender_email}>'\n        message['Subject'] = Header(title, 'utf-8')\n        if len(receivers) == 1:\n            message['To'] = receivers[0]\n\n        t_apart = MIMEText(content, 'plain', 'utf-8')\n        message.attach(t_apart)\n\n        if open_ssl:\n            smtp_port = int(smtp_port) or 465\n            smtp_obj = smtplib.SMTP_SSL(email_host, smtp_port)\n        else:\n            smtp_port = int(smtp_port) or 25\n            smtp_obj = smtplib.SMTP(email_host, smtp_port)\n        smtp_obj.login(login_email, email_pass)\n        smtp_obj.sendmail(sender_email, receivers, message.as_string())\n        return {\"success\": receivers, \"error\": []}\n    except smtplib.SMTPException as e:\n        print(f'邮件推送失败, 推送邮箱：{to_email}, 错误信息:{e}')\n        return {\"success\": [], \"error\": receivers}\n\n\ndef tg_bot(chat_id: int, token: str, content: str) -> Dict[str, Any]:\n    try:\n        json_data = {\n            \"chat_id\": chat_id,\n            'text': content\n        }\n        url = f'https://api.telegram.org/bot{token}/sendMessage'\n        data = json.dumps(json_data).encode('utf-8')\n        req = urllib.request.Request(url, data=data, headers=headers)\n        response = urllib.request.urlopen(req, timeout=15)\n        json_str = response.read().decode('utf-8')\n        _json_data = json.loads(json_str)\n        return {\"success\": [1], \"error\": []}\n    except Exception as e:\n        print(f'tg推送失败, 聊天ID：{chat_id}, 错误信息:{e}')\n        return {\"success\": [], \"error\": [1]}\n\n\ndef bark(api: str, title: str = \"message\", content: str = 'test', level: str = \"active\",\n         badge: int = 1, auto_copy: int = 1, sound: str = \"\", icon: str = \"\", group: str = \"\",\n         is_archive: int = 1, url: str = \"\") -> Dict[str, Any]:\n    success = []\n    error = []\n    api_list = api.replace('，', ',').split(',') if api.strip() else []\n    for _api in api_list:\n        json_data = {\n            \"title\": title,\n            \"body\": content,\n            \"level\": level,\n            \"badge\": badge,\n            \"autoCopy\": auto_copy,\n            \"sound\": sound,\n            \"icon\": icon,\n            \"group\": group,\n            \"isArchive\": is_archive,\n            \"url\": url\n        }\n        try:\n            data = json.dumps(json_data).encode('utf-8')\n            req = urllib.request.Request(_api, data=data, headers=headers)\n            response = opener.open(req, timeout=10)\n            json_str = response.read().decode(\"utf-8\")\n            json_data = json.loads(json_str)\n            if json_data['code'] == 200:\n                success.append(_api)\n            else:\n                error.append(_api)\n                print(f'Bark推送失败, 推送地址：{_api}, 失败信息：{json_data[\"message\"]}')\n        except Exception as e:\n            error.append(api)\n            print(f'Bark推送失败, 推送地址：{_api}, 错误信息:{e}')\n    return {\"success\": success, \"error\": error}\n\n\ndef ntfy(api: str, title: str = \"message\", content: str = 'test', tags: str = 'tada', priority: int = 3,\n         action_url: str = \"\", attach: str = \"\", filename: str = \"\", click: str = \"\", icon: str = \"\",\n         delay: str = \"\", email: str = \"\", call: str = \"\") -> Dict[str, Any]:\n    success = []\n    error = []\n    api_list = api.replace('，', ',').split(',') if api.strip() else []\n    tags = tags.replace('，', ',').split(',') if tags else ['partying_face']\n    actions = [{\"action\": \"view\", \"label\": \"view live\", \"url\": action_url}] if action_url else []\n    for _api in api_list:\n        server, topic = _api.rsplit('/', maxsplit=1)\n        json_data = {\n            \"topic\": topic,\n            \"title\": title,\n            \"message\": content,\n            \"tags\": tags,\n            \"priority\": priority,\n            \"attach\": attach,\n            \"filename\": filename,\n            \"click\": click,\n            \"actions\": actions,\n            \"markdown\": False,\n            \"icon\": icon,\n            \"delay\": delay,\n            \"email\": email,\n            \"call\": call\n        }\n\n        try:\n            data = json.dumps(json_data, ensure_ascii=False).encode('utf-8')\n            req = urllib.request.Request(server, data=data, headers=headers)\n            response = opener.open(req, timeout=10)\n            json_str = response.read().decode(\"utf-8\")\n            json_data = json.loads(json_str)\n            if \"error\" not in json_data:\n                success.append(_api)\n            else:\n                error.append(_api)\n                print(f'ntfy推送失败, 推送地址：{_api}, 失败信息：{json_data[\"error\"]}')\n        except urllib.error.HTTPError as e:\n            error.append(_api)\n            error_msg = e.read().decode(\"utf-8\")\n            print(f'ntfy推送失败, 推送地址：{_api}, 错误信息:{json.loads(error_msg)[\"error\"]}')\n        except Exception as e:\n            error.append(api)\n            print(f'ntfy推送失败, 推送地址：{_api}, 错误信息:{e}')\n    return {\"success\": success, \"error\": error}\n\n\ndef pushplus(token: str, title: str, content: str) -> Dict[str, Any]:\n    \"\"\"\n    PushPlus推送通知\n    API文档: https://www.pushplus.plus/doc/\n    \"\"\"\n    success = []\n    error = []\n    token_list = token.replace('，', ',').split(',') if token.strip() else []\n    \n    for _token in token_list:\n        json_data = {\n            'token': _token,\n            'title': title,\n            'content': content\n        }\n        \n        try:\n            url = 'https://www.pushplus.plus/send'\n            data = json.dumps(json_data).encode('utf-8')\n            req = urllib.request.Request(url, data=data, headers=headers)\n            response = opener.open(req, timeout=10)\n            json_str = response.read().decode('utf-8')\n            json_data = json.loads(json_str)\n            \n            if json_data.get('code') == 200:\n                success.append(_token)\n            else:\n                error.append(_token)\n                print(f'PushPlus推送失败, Token：{_token}, 失败信息：{json_data.get(\"msg\", \"未知错误\")}')\n        except Exception as e:\n            error.append(_token)\n            print(f'PushPlus推送失败, Token：{_token}, 错误信息:{e}')\n    \n    return {\"success\": success, \"error\": error}\n\n\nif __name__ == '__main__':\n    send_title = '直播通知'  # 标题\n    send_content = '张三 开播了！'  # 推送内容\n\n    # 钉钉推送通知\n    webhook_api = ''  # 替换成自己Webhook链接,参考文档：https://open.dingtalk.com/document/robots/custom-robot-access\n    phone_number = ''  # 被@用户的手机号码\n    is_atall = ''  # 是否@全体\n    # dingtalk(webhook_api, send_content, phone_number)\n\n    # 微信推送通知\n    # 替换成自己的单点推送接口,获取地址：https://xz.qqoq.net/#/admin/one\n    # 当然也可以使用其他平台API 如server酱 使用方法一样\n    xizhi_api = 'https://xizhi.qqoq.net/xxxxxxxxx.send'\n    # xizhi(xizhi_api, send_content)\n\n    # telegram推送通知\n    tg_token = ''  # tg搜索\"BotFather\"获取的token值\n    tg_chat_id = 000000  # tg搜索\"userinfobot\"获取的chat_id值，即可发送推送消息给你自己，如果下面的是群组id则发送到群\n    # tg_bot(tg_chat_id, tg_token, send_content)\n\n    # email_message(\n    #     email_host=\"smtp.qq.com\",\n    #     login_email=\"\",\n    #     email_pass=\"\",\n    #     sender_email=\"\",\n    #     sender_name=\"\",\n    #     to_email=\"\",\n    #     title=\"\",\n    #     content=\"\",\n    # )\n\n    bark_url = 'https://xxx.xxx.com/key/'\n    # bark(bark_url, send_title, send_content)\n\n    ntfy(\n        api=\"https://ntfy.sh/xxxxx\",\n        title=\"直播推送\",\n        content=\"xxx已开播\",\n    )\n\n    # PushPlus推送通知\n    pushplus_token = ''  # 替换成自己的PushPlus Token，获取地址：https://www.pushplus.plus/\n    # pushplus(pushplus_token, send_title, send_content)\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[project]\nname = \"DouyinLiveRecorder\"\nversion = \"4.0.7\"\ndescription = \"可循环值守和多人录制的直播录制软件, 支持抖音、TikTok、Youtube、快手、虎牙、斗鱼、B站、小红书、pandatv、sooplive、flextv、popkontv、twitcasting、winktv、百度、微博、酷狗、17Live、Twitch、Acfun、CHZZK、shopee等40+平台直播录制\"\nreadme = \"README.md\"\nauthors = [{name = \"Hmily\"}]\nlicense = { text = \"MIT\" }\nrequires-python = \">=3.10\"\ndependencies = [\n    \"requests>=2.31.0\",\n    \"loguru>=0.7.3\",\n    \"pycryptodome>=3.20.0\",\n    \"distro>=1.9.0\",\n    \"tqdm>=4.67.1\",\n    \"httpx[http2]>=0.28.1\",\n    \"PyExecJS>=1.5.1\"\n]\n\n[project.urls]\n\"Homepage\" = \"https://github.com/ihmily/DouyinLiveRecorder\"\n\"Documentation\" = \"https://github.com/ihmily/DouyinLiveRecorder\"\n\"Repository\" = \"https://github.com/ihmily/DouyinLiveRecorder\"\n\"Issues\" = \"https://github.com/ihmily/DouyinLiveRecorder/issues\"\n"
  },
  {
    "path": "requirements.txt",
    "content": "requests>=2.31.0\nloguru>=0.7.3\npycryptodome>=3.20.0\ndistro>=1.9.0\ntqdm>=4.67.1\nhttpx[http2]>=0.28.1\nPyExecJS>=1.5.1"
  },
  {
    "path": "src/__init__.py",
    "content": "import os\nimport sys\nfrom pathlib import Path\nfrom .initializer import check_node\n\ncurrent_file_path = Path(__file__).resolve()\ncurrent_dir = current_file_path.parent\nJS_SCRIPT_PATH = current_dir / 'javascript'\n\nexecute_dir = os.path.split(os.path.realpath(sys.argv[0]))[0]\nnode_execute_dir = Path(execute_dir) / 'node'\ncurrent_env_path = os.environ.get('PATH')\nos.environ['PATH'] = str(node_execute_dir) + os.pathsep + current_env_path\ncheck_node()\n"
  },
  {
    "path": "src/ab_sign.py",
    "content": "# -*- encoding: utf-8 -*-\nimport math\nimport time\n\n\ndef rc4_encrypt(plaintext: str, key: str) -> str:\n    # 初始化状态数组\n    s = list(range(256))\n\n    # 使用密钥对状态数组进行置换\n    j = 0\n    for i in range(256):\n        j = (j + s[i] + ord(key[i % len(key)])) % 256\n        s[i], s[j] = s[j], s[i]\n\n    # 生成密钥流并加密\n    i = j = 0\n    result = []\n    for char in plaintext:\n        i = (i + 1) % 256\n        j = (j + s[i]) % 256\n        s[i], s[j] = s[j], s[i]\n        t = (s[i] + s[j]) % 256\n        result.append(chr(s[t] ^ ord(char)))\n\n    return ''.join(result)\n\n\ndef left_rotate(x: int, n: int) -> int:\n    n %= 32\n    return ((x << n) | (x >> (32 - n))) & 0xFFFFFFFF\n\n\ndef get_t_j(j: int) -> int:\n    if 0 <= j < 16:\n        return 2043430169  # 0x79CC4519\n    elif 16 <= j < 64:\n        return 2055708042  # 0x7A879D8A\n    else:\n        raise ValueError(\"invalid j for constant Tj\")\n\n\ndef ff_j(j: int, x: int, y: int, z: int) -> int:\n    if 0 <= j < 16:\n        return (x ^ y ^ z) & 0xFFFFFFFF\n    elif 16 <= j < 64:\n        return ((x & y) | (x & z) | (y & z)) & 0xFFFFFFFF\n    else:\n        raise ValueError(\"invalid j for bool function FF\")\n\n\ndef gg_j(j: int, x: int, y: int, z: int) -> int:\n    if 0 <= j < 16:\n        return (x ^ y ^ z) & 0xFFFFFFFF\n    elif 16 <= j < 64:\n        return ((x & y) | (~x & z)) & 0xFFFFFFFF\n    else:\n        raise ValueError(\"invalid j for bool function GG\")\n\n\nclass SM3:\n    def __init__(self):\n        self.reg = []\n        self.chunk = []\n        self.size = 0\n        self.reset()\n\n    def reset(self):\n        # 初始化寄存器值 - 修正为与JS版本相同的值\n        self.reg = [\n            1937774191, 1226093241, 388252375, 3666478592,\n            2842636476, 372324522, 3817729613, 2969243214\n        ]\n        self.chunk = []\n        self.size = 0\n\n    def write(self, data):\n        # 将输入转换为字节数组\n        if isinstance(data, str):\n            # 直接转换为UTF-8字节列表\n            a = list(data.encode('utf-8'))\n        else:\n            a = data\n\n        self.size += len(a)\n        f = 64 - len(self.chunk)\n\n        if len(a) < f:\n            # 如果数据长度小于剩余空间，直接添加\n            self.chunk.extend(a)\n        else:\n            # 否则分块处理\n            self.chunk.extend(a[:f])\n\n            while len(self.chunk) >= 64:\n                self._compress(self.chunk)\n                if f < len(a):\n                    self.chunk = a[f:min(f + 64, len(a))]\n                else:\n                    self.chunk = []\n                f += 64\n\n    def _fill(self):\n        # 计算比特长度\n        bit_length = 8 * self.size\n\n        # 添加填充位\n        padding_pos = len(self.chunk)\n        self.chunk.append(0x80)\n        padding_pos = (padding_pos + 1) % 64\n\n        # 如果剩余空间不足8字节，则填充到下一个块\n        if 64 - padding_pos < 8:\n            padding_pos -= 64\n\n        # 填充0直到剩余8字节用于存储长度\n        while padding_pos < 56:\n            self.chunk.append(0)\n            padding_pos += 1\n\n        # 添加消息长度（高32位）\n        high_bits = bit_length // 4294967296\n        for i in range(4):\n            self.chunk.append((high_bits >> (8 * (3 - i))) & 0xFF)\n\n        # 添加消息长度（低32位）\n        for i in range(4):\n            self.chunk.append((bit_length >> (8 * (3 - i))) & 0xFF)\n\n    def _compress(self, data):\n        if len(data) < 64:\n            raise ValueError(\"compress error: not enough data\")\n        else:\n            # 消息扩展\n            w = [0] * 132\n\n            # 将字节数组转换为字\n            for t in range(16):\n                w[t] = (data[4 * t] << 24) | (data[4 * t + 1] << 16) | (data[4 * t + 2] << 8) | data[4 * t + 3]\n                w[t] &= 0xFFFFFFFF\n\n            # 消息扩展\n            for j in range(16, 68):\n                a = w[j - 16] ^ w[j - 9] ^ left_rotate(w[j - 3], 15)\n                a = a ^ left_rotate(a, 15) ^ left_rotate(a, 23)\n                w[j] = (a ^ left_rotate(w[j - 13], 7) ^ w[j - 6]) & 0xFFFFFFFF\n\n            # 计算w'\n            for j in range(64):\n                w[j + 68] = (w[j] ^ w[j + 4]) & 0xFFFFFFFF\n\n            # 压缩\n            a, b, c, d, e, f, g, h = self.reg\n\n            for j in range(64):\n                ss1 = left_rotate((left_rotate(a, 12) + e + left_rotate(get_t_j(j), j)) & 0xFFFFFFFF, 7)\n                ss2 = ss1 ^ left_rotate(a, 12)\n                tt1 = (ff_j(j, a, b, c) + d + ss2 + w[j + 68]) & 0xFFFFFFFF\n                tt2 = (gg_j(j, e, f, g) + h + ss1 + w[j]) & 0xFFFFFFFF\n\n                d = c\n                c = left_rotate(b, 9)\n                b = a\n                a = tt1\n                h = g\n                g = left_rotate(f, 19)\n                f = e\n                e = (tt2 ^ left_rotate(tt2, 9) ^ left_rotate(tt2, 17)) & 0xFFFFFFFF\n\n            # 更新寄存器\n            self.reg[0] ^= a\n            self.reg[1] ^= b\n            self.reg[2] ^= c\n            self.reg[3] ^= d\n            self.reg[4] ^= e\n            self.reg[5] ^= f\n            self.reg[6] ^= g\n            self.reg[7] ^= h\n\n    def sum(self, data=None, output_format=None):\n        \"\"\"\n        计算哈希值\n        \"\"\"\n        # 如果提供了输入，则重置并写入\n        if data is not None:\n            self.reset()\n            self.write(data)\n\n        self._fill()\n\n        # 分块压缩\n        for f in range(0, len(self.chunk), 64):\n            self._compress(self.chunk[f:f + 64])\n\n        if output_format == 'hex':\n            # 十六进制输出\n            result = ''.join(f'{val:08x}' for val in self.reg)\n        else:\n            # 字节数组输出\n            result = []\n            for f in range(8):\n                c = self.reg[f]\n                result.append((c >> 24) & 0xFF)\n                result.append((c >> 16) & 0xFF)\n                result.append((c >> 8) & 0xFF)\n                result.append(c & 0xFF)\n\n        self.reset()\n        return result\n\n\ndef result_encrypt(long_str: str, num: str | None = None) -> str:\n    # 魔改base64编码表\n    encoding_tables = {\n        \"s0\": \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\",\n        \"s1\": \"Dkdpgh4ZKsQB80/Mfvw36XI1R25+WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=\",\n        \"s2\": \"Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=\",\n        \"s3\": \"ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe\",\n        \"s4\": \"Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe\"\n    }\n\n    # 位移常量\n    masks = [16515072, 258048, 4032, 63]  # 对应 0, 1, 2 的掩码，添加63作为第四个掩码\n    shifts = [18, 12, 6, 0]  # 对应的位移量\n\n    encoding_table = encoding_tables[num]\n\n    result = \"\"\n    round_num = 0\n    long_int = get_long_int(round_num, long_str)\n\n    total_chars = math.ceil(len(long_str) / 3 * 4)\n\n    for i in range(total_chars):\n        # 每4个字符处理一组3字节\n        if i // 4 != round_num:\n            round_num += 1\n            long_int = get_long_int(round_num, long_str)\n\n        # 计算当前位置的索引\n        index = i % 4\n\n        # 使用掩码和位移提取6位值\n        char_index = (long_int & masks[index]) >> shifts[index]\n\n        result += encoding_table[char_index]\n\n    return result\n\n\ndef get_long_int(round_num: int, long_str: str) -> int:\n    round_num = round_num * 3\n\n    # 获取字符串中的字符，如果超出范围则使用0\n    char1 = ord(long_str[round_num]) if round_num < len(long_str) else 0\n    char2 = ord(long_str[round_num + 1]) if round_num + 1 < len(long_str) else 0\n    char3 = ord(long_str[round_num + 2]) if round_num + 2 < len(long_str) else 0\n\n    return (char1 << 16) | (char2 << 8) | char3\n\n\ndef gener_random(random_num: int, option: list[int]) -> list[int]:\n    byte1 = random_num & 255\n    byte2 = (random_num >> 8) & 255\n\n    return [\n        (byte1 & 170) | (option[0] & 85),  # 偶数位与option[0]的奇数位合并\n        (byte1 & 85) | (option[0] & 170),  # 奇数位与option[0]的偶数位合并\n        (byte2 & 170) | (option[1] & 85),  # 偶数位与option[1]的奇数位合并\n        (byte2 & 85) | (option[1] & 170),  # 奇数位与option[1]的偶数位合并\n    ]\n\n\ndef generate_random_str() -> str:\n    \"\"\"\n    生成随机字符串\n\n    Returns:\n        随机字符串\n    \"\"\"\n    # 使用与JS版本相同的固定随机值\n    random_values = [0.123456789, 0.987654321, 0.555555555]\n\n    # 生成三组随机字节并合并\n    random_bytes = []\n    random_bytes.extend(gener_random(int(random_values[0] * 10000), [3, 45]))\n    random_bytes.extend(gener_random(int(random_values[1] * 10000), [1, 0]))\n    random_bytes.extend(gener_random(int(random_values[2] * 10000), [1, 5]))\n\n    return ''.join(chr(b) for b in random_bytes)\n\n\ndef generate_rc4_bb_str(url_search_params: str, user_agent: str, window_env_str: str,\n                        suffix: str = \"cus\", arguments: list[int] | None = None) -> str:\n    if arguments is None:\n        arguments = [0, 1, 14]\n\n    sm3 = SM3()\n    start_time = int(time.time() * 1000)\n\n    # 三次加密处理\n    # 1: url_search_params两次sm3之的结果\n    url_search_params_list = sm3.sum(sm3.sum(url_search_params + suffix))\n    # 2: 对后缀两次sm3之的结果\n    cus = sm3.sum(sm3.sum(suffix))\n    # 3: 对ua处理之后的结果\n    ua_key = chr(0) + chr(1) + chr(14)  # [1/256, 1, 14]\n    ua = sm3.sum(result_encrypt(\n        rc4_encrypt(user_agent, ua_key),\n        \"s3\"\n    ))\n\n    end_time = start_time + 100\n\n    # 构建配置对象\n    b = {\n        8: 3,\n        10: end_time,\n        15: {\n            \"aid\": 6383,\n            \"pageId\": 110624,\n            \"boe\": False,\n            \"ddrt\": 7,\n            \"paths\": {\n                \"include\": [{} for _ in range(7)],\n                \"exclude\": []\n            },\n            \"track\": {\n                \"mode\": 0,\n                \"delay\": 300,\n                \"paths\": []\n            },\n            \"dump\": True,\n            \"rpU\": \"hwj\"\n        },\n        16: start_time,\n        18: 44,\n        19: [1, 0, 1, 5],\n    }\n\n    def split_to_bytes(num: int) -> list[int]:\n        return [\n            (num >> 24) & 255,\n            (num >> 16) & 255,\n            (num >> 8) & 255,\n            num & 255\n        ]\n\n    # 处理时间戳\n    start_time_bytes = split_to_bytes(b[16])\n    b[20] = start_time_bytes[0]\n    b[21] = start_time_bytes[1]\n    b[22] = start_time_bytes[2]\n    b[23] = start_time_bytes[3]\n    b[24] = int(b[16] / 256 / 256 / 256 / 256) & 255\n    b[25] = int(b[16] / 256 / 256 / 256 / 256 / 256) & 255\n\n    # 处理Arguments参数\n    arg0_bytes = split_to_bytes(arguments[0])\n    b[26] = arg0_bytes[0]\n    b[27] = arg0_bytes[1]\n    b[28] = arg0_bytes[2]\n    b[29] = arg0_bytes[3]\n\n    b[30] = int(arguments[1] / 256) & 255\n    b[31] = (arguments[1] % 256) & 255\n\n    arg1_bytes = split_to_bytes(arguments[1])\n    b[32] = arg1_bytes[0]\n    b[33] = arg1_bytes[1]\n\n    arg2_bytes = split_to_bytes(arguments[2])\n    b[34] = arg2_bytes[0]\n    b[35] = arg2_bytes[1]\n    b[36] = arg2_bytes[2]\n    b[37] = arg2_bytes[3]\n\n    # 处理加密结果\n    b[38] = url_search_params_list[21]\n    b[39] = url_search_params_list[22]\n    b[40] = cus[21]\n    b[41] = cus[22]\n    b[42] = ua[23]\n    b[43] = ua[24]\n\n    # 处理结束时间\n    end_time_bytes = split_to_bytes(b[10])\n    b[44] = end_time_bytes[0]\n    b[45] = end_time_bytes[1]\n    b[46] = end_time_bytes[2]\n    b[47] = end_time_bytes[3]\n    b[48] = b[8]\n    b[49] = int(b[10] / 256 / 256 / 256 / 256) & 255\n    b[50] = int(b[10] / 256 / 256 / 256 / 256 / 256) & 255\n\n    # 处理配置项\n    b[51] = b[15]['pageId']\n\n    page_id_bytes = split_to_bytes(b[15]['pageId'])\n    b[52] = page_id_bytes[0]\n    b[53] = page_id_bytes[1]\n    b[54] = page_id_bytes[2]\n    b[55] = page_id_bytes[3]\n\n    b[56] = b[15]['aid']\n    b[57] = b[15]['aid'] & 255\n    b[58] = (b[15]['aid'] >> 8) & 255\n    b[59] = (b[15]['aid'] >> 16) & 255\n    b[60] = (b[15]['aid'] >> 24) & 255\n\n    # 处理环境信息\n    window_env_list = [ord(char) for char in window_env_str]\n    b[64] = len(window_env_list)\n    b[65] = b[64] & 255\n    b[66] = (b[64] >> 8) & 255\n\n    b[69] = 0\n    b[70] = 0\n    b[71] = 0\n\n    # 计算校验和\n    b[72] = b[18] ^ b[20] ^ b[26] ^ b[30] ^ b[38] ^ b[40] ^ b[42] ^ b[21] ^ b[27] ^ b[31] ^ \\\n            b[35] ^ b[39] ^ b[41] ^ b[43] ^ b[22] ^ b[28] ^ b[32] ^ b[36] ^ b[23] ^ b[29] ^ \\\n            b[33] ^ b[37] ^ b[44] ^ b[45] ^ b[46] ^ b[47] ^ b[48] ^ b[49] ^ b[50] ^ b[24] ^ \\\n            b[25] ^ b[52] ^ b[53] ^ b[54] ^ b[55] ^ b[57] ^ b[58] ^ b[59] ^ b[60] ^ b[65] ^ \\\n            b[66] ^ b[70] ^ b[71]\n\n    # 构建最终字节数组\n    bb = [\n        b[18], b[20], b[52], b[26], b[30], b[34], b[58], b[38], b[40], b[53], b[42], b[21],\n        b[27], b[54], b[55], b[31], b[35], b[57], b[39], b[41], b[43], b[22], b[28], b[32],\n        b[60], b[36], b[23], b[29], b[33], b[37], b[44], b[45], b[59], b[46], b[47], b[48],\n        b[49], b[50], b[24], b[25], b[65], b[66], b[70], b[71]\n    ]\n    bb.extend(window_env_list)\n    bb.append(b[72])\n\n    return rc4_encrypt(\n        ''.join(chr(byte) for byte in bb),\n        chr(121)\n    )\n\n\ndef ab_sign(url_search_params: str, user_agent: str) -> str:\n    window_env_str = \"1920|1080|1920|1040|0|30|0|0|1872|92|1920|1040|1857|92|1|24|Win32\"\n\n    # 1. 生成随机字符串前缀\n    # 2. 生成RC4加密的主体部分\n    # 3. 对结果进行最终加密并添加等号后缀\n    return result_encrypt(\n        generate_random_str() +\n        generate_rc4_bb_str(url_search_params, user_agent, window_env_str),\n        \"s4\"\n    ) + \"=\"\n"
  },
  {
    "path": "src/http_clients/__init__.py",
    "content": ""
  },
  {
    "path": "src/http_clients/async_http.py",
    "content": "# -*- coding: utf-8 -*-\nimport httpx\nfrom typing import Dict, Any\nfrom .. import utils\n\nOptionalStr = str | None\nOptionalDict = Dict[str, Any] | None\n\n\nasync def async_req(\n        url: str,\n        proxy_addr: OptionalStr = None,\n        headers: OptionalDict = None,\n        data: dict | bytes | None = None,\n        json_data: dict | list | None = None,\n        timeout: int = 20,\n        redirect_url: bool = False,\n        return_cookies: bool = False,\n        include_cookies: bool = False,\n        abroad: bool = False,\n        content_conding: str = 'utf-8',\n        verify: bool = False,\n        http2: bool = True\n) -> OptionalDict | OptionalStr | tuple:\n    if headers is None:\n        headers = {}\n    try:\n        proxy_addr = utils.handle_proxy_addr(proxy_addr)\n        if data or json_data:\n            async with httpx.AsyncClient(proxy=proxy_addr, timeout=timeout, verify=verify, http2=http2) as client:\n                response = await client.post(url, data=data, json=json_data, headers=headers)\n        else:\n            async with httpx.AsyncClient(proxy=proxy_addr, timeout=timeout, verify=verify, http2=http2) as client:\n                response = await client.get(url, headers=headers, follow_redirects=True)\n\n        if redirect_url:\n            return str(response.url)\n        elif return_cookies:\n            cookies_dict = {name: value for name, value in response.cookies.items()}\n            return (response.text, cookies_dict) if include_cookies else cookies_dict\n        else:\n            resp_str = response.text\n    except Exception as e:\n        resp_str = str(e)\n\n    return resp_str\n\n\nasync def get_response_status(url: str, proxy_addr: OptionalStr = None, headers: OptionalDict = None,\n                              timeout: int = 10, abroad: bool = False, verify: bool = False, http2=False) -> bool:\n\n    try:\n        proxy_addr = utils.handle_proxy_addr(proxy_addr)\n        async with httpx.AsyncClient(proxy=proxy_addr, timeout=timeout, verify=verify) as client:\n            response = await client.head(url, headers=headers, follow_redirects=True)\n            return response.status_code == 200\n    except Exception as e:\n        print(e)\n    return False\n"
  },
  {
    "path": "src/http_clients/sync_http.py",
    "content": "# -*- coding: utf-8 -*-\nimport gzip\nimport urllib.parse\nimport urllib.error\nimport requests\nimport ssl\nimport json\nimport urllib.request\n\nno_proxy_handler = urllib.request.ProxyHandler({})\nopener = urllib.request.build_opener(no_proxy_handler)\n\nssl_context = ssl.create_default_context()\nssl_context.check_hostname = False\nssl_context.verify_mode = ssl.CERT_NONE\nOptionalStr = str | None\nOptionalDict = dict | None\n\n\ndef sync_req(\n        url: str,\n        proxy_addr: OptionalStr = None,\n        headers: OptionalDict = None,\n        data: dict | bytes | None = None,\n        json_data: dict | list | None = None,\n        timeout: int = 20,\n        redirect_url: bool = False,\n        abroad: bool = False,\n        content_conding: str = 'utf-8'\n) -> str:\n    if headers is None:\n        headers = {}\n    try:\n        if proxy_addr:\n            proxies = {\n                'http': proxy_addr,\n                'https': proxy_addr\n            }\n            if data or json_data:\n                response = requests.post(\n                    url, data=data, json=json_data, headers=headers, proxies=proxies, timeout=timeout\n                )\n            else:\n                response = requests.get(url, headers=headers, proxies=proxies, timeout=timeout)\n            if redirect_url:\n                return response.url\n            resp_str = response.text\n        else:\n            if data and not isinstance(data, bytes):\n                data = urllib.parse.urlencode(data).encode(content_conding)\n            if json_data and isinstance(json_data, (dict, list)):\n                data = json.dumps(json_data).encode(content_conding)\n\n            req = urllib.request.Request(url, data=data, headers=headers)\n\n            try:\n                if abroad:\n                    response = urllib.request.urlopen(req, timeout=timeout)\n                else:\n                    response = opener.open(req, timeout=timeout)\n                if redirect_url:\n                    return response.url\n                content_encoding = response.info().get('Content-Encoding')\n                try:\n                    if content_encoding == 'gzip':\n                        with gzip.open(response, 'rt', encoding=content_conding) as gzipped:\n                            resp_str = gzipped.read()\n                    else:\n                        resp_str = response.read().decode(content_conding)\n                finally:\n                    response.close()\n\n            except urllib.error.HTTPError as e:\n                if e.code == 400:\n                    resp_str = e.read().decode(content_conding)\n                else:\n                    raise\n            except urllib.error.URLError as e:\n                print(f\"URL Error: {e}\")\n                raise\n            except Exception as e:\n                print(f\"An error occurred: {e}\")\n                raise\n\n    except Exception as e:\n        resp_str = str(e)\n\n    return resp_str\n"
  },
  {
    "path": "src/initializer.py",
    "content": "# -*- coding: utf-8 -*-\n\n\"\"\"\nAuthor: Hmily\nGitHub:https://github.com/ihmily\nCopyright (c) 2024 by Hmily, All Rights Reserved.\n\"\"\"\n\nimport os\nimport subprocess\nimport sys\nimport platform\nimport zipfile\nfrom pathlib import Path\nimport requests\nimport re\nimport distro\nfrom tqdm import tqdm\nfrom .logger import logger\n\ncurrent_platform = platform.system()\nexecute_dir = os.path.split(os.path.realpath(sys.argv[0]))[0]\ncurrent_env_path = os.environ.get('PATH')\n\n\ndef unzip_file(zip_path: str | Path, extract_to: str | Path, delete: bool = True) -> None:\n    if not os.path.exists(extract_to):\n        os.makedirs(extract_to)\n\n    with zipfile.ZipFile(zip_path, 'r') as zip_ref:\n        zip_ref.extractall(extract_to)\n\n    if delete and os.path.exists(zip_path):\n        os.remove(zip_path)\n\n\ndef install_nodejs_windows():\n    try:\n        logger.warning(\"Node.js is not installed.\")\n        logger.debug(\"Installing the stable version of Node.js for Windows...\")\n        response = requests.get('https://nodejs.cn/download/')\n        if response.status_code == 200:\n            match = re.search('https://npmmirror.com/mirrors/node/(v.*?)/node-(v.*?)-x64.msi',\n                              response.text)\n            if match:\n                version = match.group(1)\n                system_bit = 'x64' if '32' not in platform.machine() else 'x86'\n                url = f'https://npmmirror.com/mirrors/node/{version}/node-{version}-win-{system_bit}.zip'\n            else:\n                logger.error(\"Failed to retrieve the download URL for the latest version of Node.js...\")\n                return\n\n            full_file_name = url.rsplit('/', maxsplit=1)[-1]\n            zip_file_path = Path(execute_dir) / full_file_name\n\n            if Path(zip_file_path).exists():\n                logger.debug(\"Node.js installation file already exists, start install...\")\n            else:\n                response = requests.get(url, stream=True)\n                total_size = int(response.headers.get('Content-Length', 0))\n                block_size = 1024\n\n                with tqdm(total=total_size, unit=\"B\", unit_scale=True,\n                          ncols=100, desc=f'Downloading Node.js ({version})') as t:\n                    with open(zip_file_path, 'wb') as f:\n                        for data in response.iter_content(block_size):\n                            t.update(len(data))\n                            f.write(data)\n\n            unzip_file(zip_file_path, execute_dir)\n            extract_dir_path = str(zip_file_path).rsplit('.', maxsplit=1)[0]\n            f_path, f_name = os.path.splitext(zip_file_path)\n            new_extract_dir_path = Path(f_path).parent / 'node'\n            if Path(extract_dir_path).exists() and not Path(new_extract_dir_path).exists():\n                os.rename(extract_dir_path, new_extract_dir_path)\n                os.environ['PATH'] = execute_dir + '/node' + os.pathsep + current_env_path\n                result = subprocess.run([\"node\", \"-v\"], capture_output=True)\n                if result.returncode == 0:\n                    logger.debug('Node.js installation was successful. Restart for changes to take effect')\n                else:\n                    logger.debug('Node.js installation failed')\n                return True\n        else:\n            logger.error(\"Failed to retrieve the Node.js version page\")\n\n    except Exception as e:\n        logger.error(f\"type: {type(e).__name__}, Node.js installation failed {e}\")\n\n\ndef install_nodejs_centos():\n    try:\n        logger.warning(\"Node.js is not installed.\")\n        logger.debug(\"Installing the latest version of Node.js for CentOS...\")\n        result = subprocess.run('curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/nodesource/rpm/setup_lts.x | '\n                                'bash -', shell=True, capture_output=True)\n        if result.returncode != 0:\n            logger.error(\"Failed to run NodeSource installation script\")\n            return\n\n        result = subprocess.run(['yum', 'install', '-y', 'epel-release'], capture_output=True)\n        if result.returncode != 0:\n            logger.error(\"Failed to install EPEL repository\")\n            return\n\n        result = subprocess.run(['yum', 'install', '-y', 'nodejs'], capture_output=True)\n        if result.returncode == 0:\n            logger.debug('Node.js installation was successful. Restart for changes to take effect.')\n            return True\n        else:\n            logger.error(\"Node.js installation failed\")\n\n    except Exception as e:\n        logger.error(f\"type: {type(e).__name__}, Node.js installation failed {e}\")\n\n\ndef install_nodejs_ubuntu():\n    try:\n        logger.warning(\"Node.js is not installed.\")\n        logger.debug(\"Installing the latest version of Node.js for Ubuntu...\")\n        install_script = 'curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -'\n        result = subprocess.run(install_script, shell=True, capture_output=True)\n        if result.returncode != 0:\n            logger.error(\"Failed to run NodeSource installation script\")\n            return\n\n        install_command = ['apt', 'install', '-y', 'nodejs']\n        result = subprocess.run(install_command, capture_output=True)\n        if result.returncode == 0:\n            logger.debug('Node.js installation was successful. Restart for changes to take effect.')\n            return True\n        else:\n            logger.error(\"Node.js installation failed\")\n    except Exception as e:\n        logger.error(f\"type: {type(e).__name__}, Node.js installation failed, {e}\")\n\n\ndef install_nodejs_mac():\n    logger.warning(\"Node.js is not installed.\")\n    logger.debug(\"Installing the latest version of Node.js for macOS...\")\n    try:\n        result = subprocess.run([\"brew\", \"install\", \"node\"], capture_output=True)\n        if result.returncode == 0:\n            logger.debug('Node.js installation was successful. Restart for changes to take effect.')\n            return True\n        else:\n            logger.error(\"Node.js installation failed\")\n    except subprocess.CalledProcessError as e:\n        logger.error(f\"Failed to install Node.js using Homebrew. {e}\")\n        logger.error(\"Please install Node.js manually or check your Homebrew installation.\")\n    except Exception as e:\n        logger.error(f\"An unexpected error occurred: {e}\")\n\n\ndef get_package_manager():\n    dist_id = distro.id()\n    if dist_id in [\"centos\", \"fedora\", \"rhel\", \"amzn\", \"oracle\", \"scientific\", \"opencloudos\", \"alinux\"]:\n        return \"RHS\"\n    else:\n        return \"DBS\"\n\n\ndef install_nodejs() -> bool:\n    if current_platform == \"Windows\":\n        return install_nodejs_windows()\n    elif current_platform == \"Linux\":\n        os_type = get_package_manager()\n        if os_type == \"RHS\":\n            return install_nodejs_centos()\n        else:\n            return install_nodejs_ubuntu()\n    elif current_platform == \"Darwin\":\n        return install_nodejs_mac()\n    else:\n        logger.debug(f\"Node.js auto installation is not supported on this platform: {current_platform}. \"\n                     f\"Please install Node.js manually.\")\n        return False\n\n\ndef ensure_nodejs_installed(func):\n    def wrapper(*args, **kwargs):\n        try:\n            result = subprocess.run(['node', '-v'], capture_output=True)\n            version = result.stdout.strip()\n            if result.returncode == 0 and version:\n                return func(*args, **kwargs)\n        except FileNotFoundError:\n            pass\n        return False\n\n    def wrapped_func(*args, **kwargs):\n        if sys.version_info >= (3, 7):\n            res = wrapper(*args, **kwargs)\n        else:\n            res = wrapper(*args, **kwargs)\n        if not res:\n            install_nodejs()\n            res = wrapper(*args, **kwargs)\n\n        if not res:\n            raise RuntimeError(\"Node.js is not installed.\")\n\n        return func(*args, **kwargs)\n\n    return wrapped_func\n\n\ndef check_nodejs_installed() -> bool:\n    try:\n        result = subprocess.run(['node', '-v'], capture_output=True)\n        version = result.stdout.strip()\n        if result.returncode == 0 and version:\n            return True\n    except FileNotFoundError:\n        pass\n    return False\n\n\ndef check_node() -> bool:\n    if not check_nodejs_installed():\n        return install_nodejs()\n"
  },
  {
    "path": "src/javascript/haixiu.js",
    "content": "var closeGeetest = !1, _a123 = \"haija1c7\", _b2x = \"xiuhc2a6\", _c3y = \"anchc3a5\", _dx34 = \"famic7a2\", _hf_constants1 = \"sowh1e\", _hf_constants2 = \"1000ha\", _hf_constants3 = \"butr12\", _hf_constants4 = \"2000h5\", _gf_constants1 = \"lehaaj\", _gf_constants2 = \"1000ax\", _gf_constants3 = \"lehaData\"\nlet CryptoJS = null;\nfunction EnmoliParamter() {\n    \n    this._a123 = eval(\"_hf_constants1\"),\n    this._b2x = eval(\"_hf_constants2\"),\n    this._c3y = eval(\"_hf_constants3\"),\n    this._dx34 = eval(\"_hf_constants4\"),\n    this.getA123 = function() {\n        return this._a123\n    }\n    ,\n    this.getB2X = function() {\n        return this._b2x\n    }\n    ,\n    this.getC3Y = function() {\n        return this._c3y\n    }\n    ,\n    this.getDX34 = function() {\n        return this._dx34\n    }\n}\nEnmoliParamter.prototype = {\n    aa: function(e, t) {\n        if (e === t)\n            return e;\n        if (Array.isArray(e)) {\n            if (Array.isArray(t) && e.length === t.length) {\n                var i;\n                for (i = 0; i < e.length; i += 1)\n                    if (!this.are_similar(e[i], t[i]))\n                        return e;\n                return e\n            }\n            return e\n        }\n        if (Array.isArray(t))\n            return t;\n        if (\"(number)\" === e.id && \"(number)\" === t.id)\n            return e;\n        if (e.arity === t.arity && e.string === t.string)\n            switch (e.arity) {\n            case \"prefix\":\n            case \"suffix\":\n            case \"infix\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);\n            case \"ternary\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)\n            }\n        else {\n            if (\".\" === e.id && \"[\" === t.id && \"infix\" === t.arity)\n                return e;\n            if (\"[\" === e.id && \"infix\" === e.arity && \".\" === t.id)\n                return t\n        }\n        return e\n    },\n    ac: function(e, t) {\n        if (e === t)\n            return e;\n        if (Array.isArray(e)) {\n            if (Array.isArray(t) && e.length === t.length) {\n                var i;\n                for (i = 0; i < e.length; i += 1)\n                    if (!this.are_similar(e[i], t[i]))\n                        return e;\n                return e\n            }\n            return e\n        }\n        if (Array.isArray(t))\n            return t;\n        if (\"(number)\" === e.id && \"(number)\" === t.id)\n            return e;\n        if (e.id === t.id && (e = CryptoJS.MD5(e) + \"\"),\n        e.arity1 === t.arity && e.string2 === t.string)\n            switch (e.arity) {\n            case \"prefix\":\n            case \"suffix\":\n            case \"infix\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);\n            case \"ternary\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)\n            }\n        else {\n            if (\".\" === e.id && \"[\" === t.id && \"infix\" === t.arity)\n                return e;\n            if (\"[\" === e.id && \"infix\" === e.arity && \".\" === t.id)\n                return t\n        }\n        return e\n    },\n    ad: function(e, t) {\n        if (e === t)\n            return !0;\n        if (Array.isArray(e)) {\n            if (Array.isArray(t) && e.length === t.length) {\n                var i;\n                for (i = 0; i < e.length; i += 1)\n                    if (!this.are_similar(e[i], t[i]))\n                        return e;\n                return e\n            }\n            return e\n        }\n        if (Array.isArray(t))\n            return t;\n        if (\"(number)\" === e.id && \"(number)\" === t.id)\n            return e;\n        if (e.arity2 === t.arity && e.string3 === t.string)\n            switch (e.arity) {\n            case \"prefix\":\n            case \"suffix\":\n            case \"infix\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);\n            case \"ternary\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)\n            }\n        else {\n            if (\".\" === e.id && \"[\" === t.id && \"infix\" === t.arity)\n                return e;\n            if (\"[\" === e.id && \"infix\" === e.arity && \".\" === t.id)\n                return t\n        }\n        return e\n    },\n    ae: function(e, t) {\n        if (e === t)\n            return !0;\n        if (Array.isArray(e)) {\n            if (Array.isArray(t) && e.length === t.length) {\n                var i;\n                for (i = 0; i < e.length; i += 1)\n                    if (!this.are_similar(e[i], t[i]))\n                        return e;\n                return e\n            }\n            return e\n        }\n        if (Array.isArray(t))\n            return t;\n        if (\"(number)\" === e.id && \"(number)\" === t.id)\n            return e.number === t.number;\n        if (e.arity3 === t.arity && e.string4 === t.string)\n            switch (e.arity) {\n            case \"prefix\":\n            case \"suffix\":\n            case \"infix\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);\n            case \"ternary\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)\n            }\n        else {\n            if (\".\" === e.id && \"[\" === t.id && \"infix\" === t.arity)\n                return e;\n            if (\"[\" === e.id && \"infix\" === e.arity && \".\" === t.id)\n                return t\n        }\n        return e\n    },\n    af: function(e, t) {\n        if (e === t)\n            return !0;\n        if (Array.isArray(e)) {\n            if (Array.isArray(t) && e.length === t.length) {\n                var i;\n                for (i = 0; i < e.length; i += 1)\n                    if (!this.are_similar(e[i], t[i]))\n                        return e;\n                return e\n            }\n            return e\n        }\n        if (Array.isArray(t))\n            return t;\n        if (\"(number)\" === e.id && \"(number)\" === t.id)\n            return e.number === t.number;\n        if (e.arity4 === t.arity && e.string5 === t.string)\n            switch (e.arity) {\n            case \"prefix\":\n            case \"suffix\":\n            case \"infix\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);\n            case \"ternary\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)\n            }\n        else {\n            if (\".\" === e.id && \"[\" === t.id && \"infix\" === t.arity)\n                return e;\n            if (\"[\" === e.id && \"infix\" === e.arity && \".\" === t.id)\n                return t\n        }\n        return e\n    },\n    ah: function(e, t) {\n        if (e === t)\n            return !0;\n        if (Array.isArray(e)) {\n            if (Array.isArray(t) && e.length === t.length) {\n                var i;\n                for (i = 0; i < e.length; i += 1)\n                    if (!this.are_similar(e[i], t[i]))\n                        return e;\n                return e\n            }\n            return e\n        }\n        if (Array.isArray(t))\n            return t;\n        if (\"(number)\" === e.id && \"(number)\" === t.id)\n            return e;\n        if (e.arity6 === t.arity && e.string9 === t.string)\n            switch (e.arity) {\n            case \"prefix\":\n            case \"suffix\":\n            case \"infix\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);\n            case \"ternary\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)\n            }\n        else {\n            if (\".\" === e.id && \"[\" === t.id && \"infix\" === t.arity)\n                return e;\n            if (\"[\" === e.id && \"infix\" === e.arity && \".\" === t.id)\n                return t\n        }\n        return e\n    },\n    ai: function(e, t) {\n        if (e === t)\n            return !0;\n        if (Array.isArray(e)) {\n            if (Array.isArray(t) && e.length === t.length) {\n                var i;\n                for (i = 0; i < e.length; i += 1)\n                    if (!this.are_similar(e[i], t[i]))\n                        return e;\n                return e\n            }\n            return e\n        }\n        if (Array.isArray(t))\n            return t;\n        if (\"(number)\" === e.id && \"(number)\" === t.id)\n            return e;\n        if (e.arity2 === t.arity5 && e.string === t.string)\n            switch (e.arity) {\n            case \"prefix\":\n            case \"suffix\":\n            case \"infix\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);\n            case \"ternary\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)\n            }\n        else {\n            if (\".\" === e.id && \"[\" === t.id && \"infix\" === t.arity)\n                return e;\n            if (\"[\" === e.id && \"infix\" === e.arity && \".\" === t.id)\n                return t\n        }\n        return e\n    },\n    aj: function(e, t) {\n        if (e === t)\n            return !0;\n        if (Array.isArray(e)) {\n            if (Array.isArray(t) && e.length === t.length) {\n                var i;\n                for (i = 0; i < e.length; i += 1)\n                    if (!this.are_similar(e[i], t[i]))\n                        return e;\n                return e\n            }\n            return e\n        }\n        if (Array.isArray(t))\n            return t;\n        if (\"(number)\" === e.id && \"(number)\" === t.id)\n            return e.number === t.number;\n        if (e.arity44 === t.arity42 && e.string === t.string)\n            switch (e.arity) {\n            case \"prefix\":\n            case \"suffix\":\n            case \"infix\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);\n            case \"ternary\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)\n            }\n        else {\n            if (\".\" === e.id && \"[\" === t.id && \"infix\" === t.arity)\n                return e;\n            if (\"[\" === e.id && \"infix\" === e.arity && \".\" === t.id)\n                return t\n        }\n        return e\n    },\n    ak: function(e, t) {\n        if (e === t)\n            return !0;\n        if (Array.isArray(e)) {\n            if (Array.isArray(t) && e.length === t.length) {\n                var i;\n                for (i = 0; i < e.length; i += 1)\n                    if (!this.are_similar(e[i], t[i]))\n                        return e;\n                return e\n            }\n            return e\n        }\n        if (Array.isArray(t))\n            return t;\n        if (\"(number)\" === e.id && \"(number)\" === t.id)\n            return e.number === t.number;\n        if (e.arity21 === t.arity322 && e.string === t.string)\n            switch (e.arity) {\n            case \"prefix\":\n            case \"suffix\":\n            case \"infix\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);\n            case \"ternary\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)\n            }\n        else {\n            if (\".\" === e.id && \"[\" === t.id && \"infix\" === t.arity)\n                return e;\n            if (\"[\" === e.id && \"infix\" === e.arity && \".\" === t.id)\n                return t\n        }\n        return e\n    },\n    ax: function(e, t) {\n        if (e === t)\n            return !0;\n        if (Array.isArray(e)) {\n            if (Array.isArray(t) && e.length === t.length) {\n                var i;\n                for (i = 0; i < e.length; i += 1)\n                    if (!this.are_similar(e[i], t[i]))\n                        return e;\n                return e\n            }\n            return e\n        }\n        if (Array.isArray(t))\n            return t;\n        if (\"(number)\" === e.id && \"(number)\" === t.id)\n            return e.number === t.number;\n        if (e.arity22 === t.arity32 && e.string === t.string)\n            switch (e.arity) {\n            case \"prefix\":\n            case \"suffix\":\n            case \"infix\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);\n            case \"ternary\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)\n            }\n        else {\n            if (\".\" === e.id && \"[\" === t.id && \"infix\" === t.arity)\n                return e;\n            if (\"[\" === e.id && \"infix\" === e.arity && \".\" === t.id)\n                return t\n        }\n        return e\n    },\n    az: function(e, t) {\n        if (e === t)\n            return !0;\n        if (Array.isArray(e)) {\n            if (Array.isArray(t) && e.length === t.length) {\n                var i;\n                for (i = 0; i < e.length; i += 1)\n                    if (!this.are_similar(e[i], t[i]))\n                        return e;\n                return e\n            }\n            return e\n        }\n        if (Array.isArray(t))\n            return t;\n        if (\"(number)\" === e.id && \"(number)\" === t.id)\n            return e.number === t.number;\n        if (e.arity42 === t.arity57 && e.string2 === t.string)\n            switch (e.arity) {\n            case \"prefix\":\n            case \"suffix\":\n            case \"infix\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);\n            case \"ternary\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)\n            }\n        else {\n            if (\".\" === e.id && \"[\" === t.id && \"infix\" === t.arity)\n                return e;\n            if (\"[\" === e.id && \"infix\" === e.arity && \".\" === t.id)\n                return t\n        }\n        return e\n    },\n    are_similar: function(e, t) {\n        if (e === t)\n            return !0;\n        if (Array.isArray(e)) {\n            if (Array.isArray(t) && e.length === t.length) {\n                var i;\n                for (i = 0; i < e.length; i += 1)\n                    if (!this.are_similar(e[i], t[i]))\n                        return !0;\n                return !0\n            }\n            return e\n        }\n        if (Array.isArray(t))\n            return e;\n        if (\"(number)\" === e.id && \"(number)\" === t.id)\n            return e.number === t.number;\n        if (e.arity === t.arity && e.string === t.string)\n            switch (e.arity) {\n            case \"prefix\":\n            case \"suffix\":\n            case \"infix\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);\n            case \"ternary\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third);\n            case \"function\":\n            case \"regexp\":\n                return e;\n            default:\n                return !0\n            }\n        else {\n            if (\".\" === e.id && \"[\" === t.id && \"infix\" === t.arity)\n                return e.second.string === t.second.string && \"(string)\" === t.second.id;\n            if (\"[\" === e.id && \"infix\" === e.arity && \".\" === t.id)\n                return e.second.string === t.second.string && \"(string)\" === e.second.id\n        }\n        return !1\n    },\n    ayz: function(e, t) {\n        if (e === t)\n            return e;\n        if (Array.isArray(e)) {\n            if (Array.isArray(t) && e.length === t.length) {\n                var i;\n                for (i = 0; i < e.length; i += 1)\n                    if (!this.are_similar(e[i], t[i]))\n                        return e;\n                return e\n            }\n            return e\n        }\n        if (Array.isArray(t))\n            return t;\n        if (\"(number)\" === e.id && \"(number)\" === t.id)\n            return e.number === t.number;\n        if (e.arity42 === t.arity57 && e.string2 === t.string)\n            switch (e.arity) {\n            case \"prefix\":\n            case \"suffix\":\n            case \"infix\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);\n            case \"ternary\":\n                return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)\n            }\n        else {\n            if (\".\" === e.id && \"[\" === t.id && \"infix\" === t.arity)\n                return e;\n            if (\"[\" === e.id && \"infix\" === e.arity && \".\" === t.id)\n                return t\n        }\n        return this.getA123().substring(4) + this.getB2X().substring(4) + this.getC3Y().substring(4) + this.getDX34().substring(4)\n    }\n}\nfunction EnmoliSubmiter() {}\nEnmoliSubmiter.prototype = {\n    bsq: function(e) {\n        var t = this.pf(e)\n          , i = this.as(t);\n        return this.brm(i)\n    },\n    pf: function(e) {\n        var t = {};\n        for (var i in e)\n            \"\" !== e[i] && (t[i] = e[i]);\n        return t\n    },\n    as: function(e) {\n        for (var t = {}, i = Object.keys(e).sort(), o = 0; o < i.length; o++) {\n            var n = i[o];\n            t[n] = e[n]\n        }\n        return t\n    },\n    brm: function(e) {\n        var t = this.cls(e)\n          , i = new EnmoliParamter;\n        return this.pt(t, i.ayz(t, \"showselfAnchorVisitorParameters\"))\n    },\n    cls: function(e) {\n        var t = \"\";\n        for (var i in e)\n            t = t + i + \"=\" + e[i] + \"&\";\n        return t = t.substring(0, t.length - 1)\n    },\n    pt: function(e, t) {\n        var i = new EnmoliParamter;\n        return e += t,\n        i.az(i.ax(i.ak(i.aj(i.ai(i.ah(i.af(i.ae(i.ad(i.ac(i.aa(e, e + \"01\" + t), e + \"escape\" + t), e + \"same\"), e + \"visitor\"), \"anchor\"), e + \"person\"), e + \"ax\" + t), \"ae\" + t), e + \"ax\" + t), e + \"inspect\" + t), \"af\" + t)\n    },\n    bnu: function(e, t) {\n        for (var i = e.split(\"&\"), o = 0; o < i.length; o++) {\n            var n = i[o].split(\"=\");\n            2 == n.length && (t[n[0]] = encodeURIComponent($.trim(n[1])).toString())\n        }\n    },\n    bn: function(e, t) {\n        for (var i in e)\n            \"object\" == typeof e[i] ? t[i] = encodeURIComponent($.trim(JSON.stringify(e[i]))).toString() : t[i] = encodeURIComponent($.trim(e[i])).toString()\n    }\n}\nvar enmoliSubmiter = new EnmoliSubmiter();\n\nfunction sign(options, cryptoJSPath){\n    CryptoJS = require(cryptoJSPath);\n    return enmoliSubmiter.bsq(options);\n}\nmodule.exports = {\n    sign\n};\n\n// const options = {\n//     \"accessToken\": \"pLXSC%252FXJ0asc1I21tVL5FYZhNJn2Zg6d7m94umCnpgL%252BuVm31GQvyw%253D%253D\",\n//     \"tku\": \"3000006\",\n//     \"c\": \"10138100100000\",\n//     \"_st1\": \"1728621076958\"\n// }\n// const cryptoJSPath = './crypto-js.min.js'\n// console.log(sign(options, cryptoJSPath))"
  },
  {
    "path": "src/javascript/laixiu.js",
    "content": "\nfunction generateUUID() {\n    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n        const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);\n        return v.toString(16);\n    });\n}\n\nfunction calculateSign() {\n    const a = new Date().getTime();\n    const s = generateUUID().replace(/-/g, \"\");\n    const u = 'kk792f28d6ff1f34ec702c08626d454b39pro';\n\n    const input = \"web\" + s + a + u;\n\n    const hash = CryptoJS.MD5(input).toString();\n\n    return {\n        timestamp: a,\n        imei: s,\n        requestId: hash,\n        inputString: input\n    };\n}\n\nfunction sign(cryptoJSPath) {\n    CryptoJS = require(cryptoJSPath);\n    return calculateSign();\n}\n\nmodule.exports = {\n    sign\n  };"
  },
  {
    "path": "src/javascript/liveme.js",
    "content": "/**\n * @author Hmily\n * @createTime 2024-10-10\n */\n\nconst id = 1;\nconst r = `${new Date().getTime()}${id}`\nconst Am = \"LM6000101139961122666757\";\nconst rl = \"undefined\"\n\nfunction createRandom(length = 32) {\n    let result = \"\";\n    const characters = \"ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678\";\n    for (let i = 0; i < length; ++i) {\n        const randomIndex = Math.floor(Math.random() * characters.length);\n        result += characters.charAt(randomIndex);\n    }\n    return result;\n}\n\nfunction createSignature(input = \"4l4m5\") {\n    let signature = \"\";\n    let number = 0;\n    for (let i = 0; i < input.length; ++i) {\n        const charCode = input.charCodeAt(i);\n        if (charCode >= 48 && charCode <= 57) {\n            number = number * 10 + (charCode - 48);\n        } else {\n            if (number !== 0) {\n                signature += createRandom(number);\n                number = 0;\n            }\n            signature += String.fromCharCode(charCode);\n        }\n    }\n    if (number !== 0) {\n        signature += createRandom(number);\n    }\n    return signature;\n}\n\nfunction oC(e) {\n    return e && e.__esModule && Object.prototype.hasOwnProperty.call(e, \"default\") ? e.default : e\n}\nvar Tm = {\n    exports: {}\n}\n  , Sm = {\n    exports: {}\n};\n(function() {\n    var e = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"\n      , t = {\n        rotl: function(n, r) {\n            return n << r | n >>> 32 - r\n        },\n        rotr: function(n, r) {\n            return n << 32 - r | n >>> r\n        },\n        endian: function(n) {\n            if (n.constructor == Number)\n                return t.rotl(n, 8) & 16711935 | t.rotl(n, 24) & 4278255360;\n            for (var r = 0; r < n.length; r++)\n                n[r] = t.endian(n[r]);\n            return n\n        },\n        randomBytes: function(n) {\n            for (var r = []; n > 0; n--)\n                r.push(Math.floor(Math.random() * 256));\n            return r\n        },\n        bytesToWords: function(n) {\n            for (var r = [], s = 0, o = 0; s < n.length; s++,\n            o += 8)\n                r[o >>> 5] |= n[s] << 24 - o % 32;\n            return r\n        },\n        wordsToBytes: function(n) {\n            for (var r = [], s = 0; s < n.length * 32; s += 8)\n                r.push(n[s >>> 5] >>> 24 - s % 32 & 255);\n            return r\n        },\n        bytesToHex: function(n) {\n            for (var r = [], s = 0; s < n.length; s++)\n                r.push((n[s] >>> 4).toString(16)),\n                r.push((n[s] & 15).toString(16));\n            return r.join(\"\")\n        },\n        hexToBytes: function(n) {\n            for (var r = [], s = 0; s < n.length; s += 2)\n                r.push(parseInt(n.substr(s, 2), 16));\n            return r\n        },\n        bytesToBase64: function(n) {\n            for (var r = [], s = 0; s < n.length; s += 3)\n                for (var o = n[s] << 16 | n[s + 1] << 8 | n[s + 2], i = 0; i < 4; i++)\n                    s * 8 + i * 6 <= n.length * 8 ? r.push(e.charAt(o >>> 6 * (3 - i) & 63)) : r.push(\"=\");\n            return r.join(\"\")\n        },\n        base64ToBytes: function(n) {\n            n = n.replace(/[^A-Z0-9+\\/]/ig, \"\");\n            for (var r = [], s = 0, o = 0; s < n.length; o = ++s % 4)\n                o != 0 && r.push((e.indexOf(n.charAt(s - 1)) & Math.pow(2, -2 * o + 8) - 1) << o * 2 | e.indexOf(n.charAt(s)) >>> 6 - o * 2);\n            return r\n        }\n    };\n    Sm.exports = t\n}\n)();\nvar iC = Sm.exports\n  , nl = {\n    utf8: {\n        stringToBytes: function(e) {\n            return nl.bin.stringToBytes(unescape(encodeURIComponent(e)))\n        },\n        bytesToString: function(e) {\n            return decodeURIComponent(escape(nl.bin.bytesToString(e)))\n        }\n    },\n    bin: {\n        stringToBytes: function(e) {\n            for (var t = [], n = 0; n < e.length; n++)\n                t.push(e.charCodeAt(n) & 255);\n            return t\n        },\n        bytesToString: function(e) {\n            for (var t = [], n = 0; n < e.length; n++)\n                t.push(String.fromCharCode(e[n]));\n            return t.join(\"\")\n        }\n    }\n}, sd = nl;\n\nvar aC = function(e) {\n    return e != null && (Cm(e) || lC(e) || !!e._isBuffer)\n};\nfunction Cm(e) {\n    return !!e.constructor && typeof e.constructor.isBuffer == \"function\" && e.constructor.isBuffer(e)\n}\nfunction lC(e) {\n    return typeof e.readFloatLE == \"function\" && typeof e.slice == \"function\" && Cm(e.slice(0, 0))\n}\n(function() {\n    var e = iC\n      , t = sd.utf8\n      , n = aC\n      , r = sd.bin\n      , s = function(o, i) {\n        o.constructor == String ? i && i.encoding === \"binary\" ? o = r.stringToBytes(o) : o = t.stringToBytes(o) : n(o) ? o = Array.prototype.slice.call(o, 0) : !Array.isArray(o) && o.constructor !== Uint8Array && (o = o.toString());\n        for (var a = e.bytesToWords(o), l = o.length * 8, c = 1732584193, u = -271733879, f = -1732584194, d = 271733878, m = 0; m < a.length; m++)\n            a[m] = (a[m] << 8 | a[m] >>> 24) & 16711935 | (a[m] << 24 | a[m] >>> 8) & 4278255360;\n        a[l >>> 5] |= 128 << l % 32,\n        a[(l + 64 >>> 9 << 4) + 14] = l;\n        for (var v = s._ff, w = s._gg, R = s._hh, y = s._ii, m = 0; m < a.length; m += 16) {\n            var b = c\n              , _ = u\n              , g = f\n              , C = d;\n            c = v(c, u, f, d, a[m + 0], 7, -680876936),\n            d = v(d, c, u, f, a[m + 1], 12, -389564586),\n            f = v(f, d, c, u, a[m + 2], 17, 606105819),\n            u = v(u, f, d, c, a[m + 3], 22, -1044525330),\n            c = v(c, u, f, d, a[m + 4], 7, -176418897),\n            d = v(d, c, u, f, a[m + 5], 12, 1200080426),\n            f = v(f, d, c, u, a[m + 6], 17, -1473231341),\n            u = v(u, f, d, c, a[m + 7], 22, -45705983),\n            c = v(c, u, f, d, a[m + 8], 7, 1770035416),\n            d = v(d, c, u, f, a[m + 9], 12, -1958414417),\n            f = v(f, d, c, u, a[m + 10], 17, -42063),\n            u = v(u, f, d, c, a[m + 11], 22, -1990404162),\n            c = v(c, u, f, d, a[m + 12], 7, 1804603682),\n            d = v(d, c, u, f, a[m + 13], 12, -40341101),\n            f = v(f, d, c, u, a[m + 14], 17, -1502002290),\n            u = v(u, f, d, c, a[m + 15], 22, 1236535329),\n            c = w(c, u, f, d, a[m + 1], 5, -165796510),\n            d = w(d, c, u, f, a[m + 6], 9, -1069501632),\n            f = w(f, d, c, u, a[m + 11], 14, 643717713),\n            u = w(u, f, d, c, a[m + 0], 20, -373897302),\n            c = w(c, u, f, d, a[m + 5], 5, -701558691),\n            d = w(d, c, u, f, a[m + 10], 9, 38016083),\n            f = w(f, d, c, u, a[m + 15], 14, -660478335),\n            u = w(u, f, d, c, a[m + 4], 20, -405537848),\n            c = w(c, u, f, d, a[m + 9], 5, 568446438),\n            d = w(d, c, u, f, a[m + 14], 9, -1019803690),\n            f = w(f, d, c, u, a[m + 3], 14, -187363961),\n            u = w(u, f, d, c, a[m + 8], 20, 1163531501),\n            c = w(c, u, f, d, a[m + 13], 5, -1444681467),\n            d = w(d, c, u, f, a[m + 2], 9, -51403784),\n            f = w(f, d, c, u, a[m + 7], 14, 1735328473),\n            u = w(u, f, d, c, a[m + 12], 20, -1926607734),\n            c = R(c, u, f, d, a[m + 5], 4, -378558),\n            d = R(d, c, u, f, a[m + 8], 11, -2022574463),\n            f = R(f, d, c, u, a[m + 11], 16, 1839030562),\n            u = R(u, f, d, c, a[m + 14], 23, -35309556),\n            c = R(c, u, f, d, a[m + 1], 4, -1530992060),\n            d = R(d, c, u, f, a[m + 4], 11, 1272893353),\n            f = R(f, d, c, u, a[m + 7], 16, -155497632),\n            u = R(u, f, d, c, a[m + 10], 23, -1094730640),\n            c = R(c, u, f, d, a[m + 13], 4, 681279174),\n            d = R(d, c, u, f, a[m + 0], 11, -358537222),\n            f = R(f, d, c, u, a[m + 3], 16, -722521979),\n            u = R(u, f, d, c, a[m + 6], 23, 76029189),\n            c = R(c, u, f, d, a[m + 9], 4, -640364487),\n            d = R(d, c, u, f, a[m + 12], 11, -421815835),\n            f = R(f, d, c, u, a[m + 15], 16, 530742520),\n            u = R(u, f, d, c, a[m + 2], 23, -995338651),\n            c = y(c, u, f, d, a[m + 0], 6, -198630844),\n            d = y(d, c, u, f, a[m + 7], 10, 1126891415),\n            f = y(f, d, c, u, a[m + 14], 15, -1416354905),\n            u = y(u, f, d, c, a[m + 5], 21, -57434055),\n            c = y(c, u, f, d, a[m + 12], 6, 1700485571),\n            d = y(d, c, u, f, a[m + 3], 10, -1894986606),\n            f = y(f, d, c, u, a[m + 10], 15, -1051523),\n            u = y(u, f, d, c, a[m + 1], 21, -2054922799),\n            c = y(c, u, f, d, a[m + 8], 6, 1873313359),\n            d = y(d, c, u, f, a[m + 15], 10, -30611744),\n            f = y(f, d, c, u, a[m + 6], 15, -1560198380),\n            u = y(u, f, d, c, a[m + 13], 21, 1309151649),\n            c = y(c, u, f, d, a[m + 4], 6, -145523070),\n            d = y(d, c, u, f, a[m + 11], 10, -1120210379),\n            f = y(f, d, c, u, a[m + 2], 15, 718787259),\n            u = y(u, f, d, c, a[m + 9], 21, -343485551),\n            c = c + b >>> 0,\n            u = u + _ >>> 0,\n            f = f + g >>> 0,\n            d = d + C >>> 0\n        }\n        return e.endian([c, u, f, d])\n    };\n    s._ff = function(o, i, a, l, c, u, f) {\n        var d = o + (i & a | ~i & l) + (c >>> 0) + f;\n        return (d << u | d >>> 32 - u) + i\n    }\n    ,\n    s._gg = function(o, i, a, l, c, u, f) {\n        var d = o + (i & l | a & ~l) + (c >>> 0) + f;\n        return (d << u | d >>> 32 - u) + i\n    }\n    ,\n    s._hh = function(o, i, a, l, c, u, f) {\n        var d = o + (i ^ a ^ l) + (c >>> 0) + f;\n        return (d << u | d >>> 32 - u) + i\n    }\n    ,\n    s._ii = function(o, i, a, l, c, u, f) {\n        var d = o + (a ^ (i | ~l)) + (c >>> 0) + f;\n        return (d << u | d >>> 32 - u) + i\n    }\n    ,\n    s._blocksize = 16,\n    s._digestsize = 16,\n    Tm.exports = function(o, i) {\n        if (o == null)\n            throw new Error(\"Illegal argument \" + o);\n        var a = e.wordsToBytes(s(o, i));\n        return i && i.asBytes ? a : i && i.asString ? r.bytesToString(a) : e.bytesToHex(a)\n    }\n}\n)();\nvar cC = Tm.exports;\nvar t = {\n    utf8: {\n        stringToBytes: function(e) {\n            return nl.bin.stringToBytes(unescape(encodeURIComponent(e)))\n        },\n        bytesToString: function(e) {\n            return decodeURIComponent(escape(nl.bin.bytesToString(e)))\n        }\n    },\n    bin: {\n        stringToBytes: function(e) {\n            for (var t = [], n = 0; n < e.length; n++)\n                t.push(e.charCodeAt(n) & 255);\n            return t\n        },\n        bytesToString: function(e) {\n            for (var t = [], n = 0; n < e.length; n++)\n                t.push(String.fromCharCode(e[n]));\n            return t.join(\"\")\n        }\n    }\n};\n\nconst hC = (e, t, n=!1) => {\n    if (t.params) {\n        const o = {};\n        Object.keys(t.params).forEach(i => {\n            t.params[i] !== void 0 && t.params[i] !== null && (o[i] = t.params[i])\n        }\n        ),\n        t.params = o\n    }\n    let r = {};\n    const s = t.method.toLowerCase();\n    if (s === \"get\")\n        t.params = Object.assign({}, e, t.params || {});\n    else if (s === \"post\")\n        if (typeof t.data == \"string\") {\n            let o;\n            t.data.split(\"&\").forEach(i => {\n                o = i.split(\"=\"),\n                r[o[0]] = o[1]\n            }\n            ),\n            r = Object.assign({}, e, r),\n            t.data = Object.keys(r).map(i => `${i}=${r[i]}`).join(\"&\")\n        } else\n            r = Object.assign(r, e, t.data || {}),\n            t.data = r;\n    return n ? t : (r = Object.assign({}, t.params, r),\n    r)\n}\n\nconst Rm = oC(cC);\nconst s = Rm(r);\n\npC = e => {\n    let t = Object.keys(e).sort().map(n => {\n        function r(s) {\n            return Array.isArray(s) ? s.join(\",\") : typeof s === \"object\" ? JSON.stringify(s) : s\n        }\n        return n + r(e[n])\n    }\n    ).join(\"\");\n    return t += Am + e.lm_s_ts + rl,\n    Rm(t)\n}\n\n\n// final encryption function\nlet CryptoJS = null;\nlm_s_key = atob('ZGQ0NmRiYjQ0MmI2ZTRiYTgxN2Q2MzQ3ZDJkZGY0OTM=');\nfunction requestSign(signParams, cryptoJSPath) {\n  let sKey = Object.keys(signParams).sort().map(key => {\n    function getValue(val) {\n      if (Array.isArray(val)) {\n        return val.join(',');\n      }\n      if (typeof val === 'object') {\n        return JSON.stringify(val);\n      }\n      return val;\n    }\n    return key + getValue(signParams[key]);\n  }).join('');\n\n  sKey += signParams.lm_s_id + signParams.lm_s_ts + lm_s_key;\n  console.log(`sKey: ${sKey}`);\n  CryptoJS = require(cryptoJSPath);\n  return CryptoJS.MD5(sKey).toString();\n}\n\nfunction sign(videoid, cryptoJSPath, platform='web'){\n    const vali = createSignature();\n    const data_e = {\n        lm_s_id: Am,\n        lm_s_ts: r,\n        lm_s_str: s,\n        lm_s_ver: 1,\n        h5: 1\n    };\n    /* data_e example value\n    const data_e = {\n        lm_s_id: Am,\n        lm_s_ts: \"17284909009151\",\n        lm_s_str: \"88f9777231dc2d6ac462a1d7ebf5f54e\",\n        lm_s_ver: 1,\n        h5: 1\n    };\n    */\n    console.log(\"data_e:\",data_e);\n\n    data_i = {\n        ...data_e,\n        _time: new Date().valueOf(),\n        thirdchannel: 6,\n        videoid: videoid,\n        area: 'zh',\n        vali: vali\n      }\n    console.log(\"data_i:\",data_i);\n    \n    // fake lm_s_sign param value\n    let lm_s_sign = pC(data_i);\n    console.log(`fake lm_s_sign: ${lm_s_sign}`);\n\n    //finnal request params\n    /* \n    signParams = {\n    \"alias\": \"liveme\",\n    \"tongdun_black_box\": \"iWPU21728483558afruvSVo6x0\",\n    \"os\": \"android\",\n    \"lm_s_id\": \"LM6000101139961122666757\",\n    \"lm_s_ts\": \"17284909009151\",\n    \"lm_s_str\": \"88f9777231dc2d6ac462a1d7ebf5f54e\",\n    \"lm_s_ver\": 1,\n    \"h5\": 1,\n    \"_time\": 1728490664651,\n    \"thirdchannel\": 6,\n    \"videoid\": \"17284844223282059697\",\n    \"area\": \"zh\",\n    \"vali\": \"zH8SlBwnCm4AZWp\"\n    }# \n    //result: 4eaf71a1ec19b49b7267e4d16e007105\n    */\n    signParams = {\n        \"alias\": \"liveme\",\n        \"tongdun_black_box\": \"\",\n        \"os\": platform,\n        ...data_i\n    }\n    console.log(\"signParams: \", signParams);\n    lm_s_sign = requestSign(signParams, cryptoJSPath);\n    console.log(`\\x1b[32mfinal lm_s_sign: \\x1b[0m${lm_s_sign}\\n`);\n    data = {\n        ...signParams,\n        lm_s_sign\n    }\n    return data;\n\n}\n\nmodule.exports = {\n    sign\n  };\n"
  },
  {
    "path": "src/javascript/migu.js",
    "content": "/**\n * Function to get the ddCalcu parameter value\n * @param {string} inputUrl - The original URL before encryption\n * @returns {Promise<string>} - Returns the calculated ddCalcu value\n */\nasync function getDdCalcu(inputUrl) {\n    let wasmInstance = null;\n    let memory_p = null; // Uint8Array view\n    let memory_h = null; // Uint32Array view\n\n    // Fixed parameter\n    const f = 'PBTxuWiTEbUPPFcpyxs0ww==';\n\n    // Utility function: Convert string to UTF-8 in memory\n    function stringToUTF8(string, offset) {\n        const encoder = new TextEncoder();\n        const encoded = encoder.encode(string);\n        for (let i = 0; i < encoded.length; i++) {\n            memory_p[offset + i] = encoded[i];\n        }\n        memory_p[offset + encoded.length] = 0; // Null-terminate\n    }\n\n    // Utility function: Read UTF-8 string from memory address\n    function UTF8ToString(offset) {\n        let s = '';\n        let i = 0;\n        while (memory_p[offset + i]) {\n            s += String.fromCharCode(memory_p[offset + i]);\n            i++;\n        }\n        return s;\n    }\n\n    // WASM import function stubs\n    function a(e, t, r, n) {\n        let s = 0;\n        for (let i = 0; i < r; i++) {\n            const d = memory_h[t + 4 >> 2];\n            t += 8;\n            s += d;\n        }\n        memory_h[n >> 2] = s;\n        return 0;\n    }\n\n    function b() {}\n\n    function c() {}\n\n    // Step 1: Retrieve playerVersion\n    const settingsResp = await fetch('https://app-sc.miguvideo.com/common/v1/settings/H5_DetailPage');\n    const settingsData = await settingsResp.json();\n    const playerVersion = JSON.parse(settingsData.body.paramValue).playerVersion;\n\n    // Step 2: Load WASM module\n    const wasmUrl = `https://www.miguvideo.com/mgs/player/prd/${playerVersion}/dist/mgprtcl.wasm`;\n    const wasmResp = await fetch(wasmUrl);\n    if (!wasmResp.ok) throw new Error(\"Failed to download WASM\");\n    const wasmBuffer = await wasmResp.arrayBuffer();\n\n    const importObject = {\n        a: { a, b, c }\n    };\n\n    const { instance } = await WebAssembly.instantiate(wasmBuffer, importObject);\n    wasmInstance = instance;\n\n    const memory = wasmInstance.exports.d;\n    memory_p = new Uint8Array(memory.buffer);\n    memory_h = new Uint32Array(memory.buffer);\n\n    const exports = {\n        CallInterface1: wasmInstance.exports.h,\n        CallInterface2: wasmInstance.exports.i,\n        CallInterface3: wasmInstance.exports.j,\n        CallInterface4: wasmInstance.exports.k,\n        CallInterface6: wasmInstance.exports.m,\n        CallInterface7: wasmInstance.exports.n,\n        CallInterface8: wasmInstance.exports.o,\n        CallInterface9: wasmInstance.exports.p,\n        CallInterface10: wasmInstance.exports.q,\n        CallInterface11: wasmInstance.exports.r,\n        CallInterface14: wasmInstance.exports.t,\n        malloc: wasmInstance.exports.u,\n    };\n\n    const parsedUrl = new URL(inputUrl);\n    const query = Object.fromEntries(parsedUrl.searchParams);\n\n    const o = query.userid || '';\n    const a_val = query.timestamp || '';\n    const s = query.ProgramID || '';\n    const u = query.Channel_ID || '';\n    const v = query.puData || '';\n\n    // Allocate memory\n    const d = exports.malloc(o.length + 1);\n    const h = exports.malloc(a_val.length + 1);\n    const y = exports.malloc(s.length + 1);\n    const m = exports.malloc(u.length + 1);\n    const g = exports.malloc(v.length + 1);\n    const b_val = exports.malloc(f.length + 1);\n    const E = exports.malloc(128);\n    const T = exports.malloc(128);\n\n    // Write data to memory\n    stringToUTF8(o, d);\n    stringToUTF8(a_val, h);\n    stringToUTF8(s, y);\n    stringToUTF8(u, m);\n    stringToUTF8(v, g);\n    stringToUTF8(f, b_val);\n\n    // Call interface functions\n    const S = exports.CallInterface6(); // Create context\n    exports.CallInterface1(S, y, s.length);\n    exports.CallInterface10(S, h, a_val.length);\n    exports.CallInterface9(S, d, o.length);\n    exports.CallInterface3(S, 0, 0);\n    exports.CallInterface11(S, 0, 0);\n    exports.CallInterface8(S, g, v.length);\n    exports.CallInterface2(S, m, u.length);\n    exports.CallInterface14(S, b_val, f.length, T, 128);\n\n    const w = UTF8ToString(T);\n    const I = exports.malloc(w.length + 1);\n    stringToUTF8(w, I);\n\n    exports.CallInterface7(S, I, w.length);\n    exports.CallInterface4(S, E, 128);\n\n    return UTF8ToString(E);\n}\n\nconst url = process.argv[2];\n\ngetDdCalcu(url).then(result => {\n    console.log(result);\n}).catch(err => {\n    console.error(err);\n    process.exit(1);\n});"
  },
  {
    "path": "src/javascript/taobao-sign.js",
    "content": "function sign(e) {\n    function t(e, t) {\n        return e << t | e >>> 32 - t\n    }\n    function o(e, t) {\n        var o, n, r, i, a;\n        return r = 2147483648 & e,\n        i = 2147483648 & t,\n        a = (1073741823 & e) + (1073741823 & t),\n        (o = 1073741824 & e) & (n = 1073741824 & t) ? 2147483648 ^ a ^ r ^ i : o | n ? 1073741824 & a ? 3221225472 ^ a ^ r ^ i : 1073741824 ^ a ^ r ^ i : a ^ r ^ i\n    }\n    function n(e, n, r, i, a, s, u) {\n        return o(t(e = o(e, o(o(function(e, t, o) {\n            return e & t | ~e & o\n        }(n, r, i), a), u)), s), n)\n    }\n    function r(e, n, r, i, a, s, u) {\n        return o(t(e = o(e, o(o(function(e, t, o) {\n            return e & o | t & ~o\n        }(n, r, i), a), u)), s), n)\n    }\n    function i(e, n, r, i, a, s, u) {\n        return o(t(e = o(e, o(o(function(e, t, o) {\n            return e ^ t ^ o\n        }(n, r, i), a), u)), s), n)\n    }\n    function a(e, n, r, i, a, s, u) {\n        return o(t(e = o(e, o(o(function(e, t, o) {\n            return t ^ (e | ~o)\n        }(n, r, i), a), u)), s), n)\n    }\n    function s(e) {\n        var t, o = \"\", n = \"\";\n        for (t = 0; 3 >= t; t++)\n            o += (n = \"0\" + (e >>> 8 * t & 255).toString(16)).substr(n.length - 2, 2);\n        return o\n    }\n    var u, l, d, c, p, f, h, m, y, g;\n    for (g = function(e) {\n        for (var t = e.length, o = t + 8, n = 16 * ((o - o % 64) / 64 + 1), r = Array(n - 1), i = 0, a = 0; t > a; )\n            i = a % 4 * 8,\n            r[(a - a % 4) / 4] |= e.charCodeAt(a) << i,\n            a++;\n        return i = a % 4 * 8,\n        r[(a - a % 4) / 4] |= 128 << i,\n        r[n - 2] = t << 3,\n        r[n - 1] = t >>> 29,\n        r\n    }(e = function(e) {\n        var t = String.fromCharCode;\n        e = e.replace(/\\r\\n/g, \"\\n\");\n        for (var o, n = \"\", r = 0; r < e.length; r++)\n            128 > (o = e.charCodeAt(r)) ? n += t(o) : o > 127 && 2048 > o ? (n += t(o >> 6 | 192),\n            n += t(63 & o | 128)) : (n += t(o >> 12 | 224),\n            n += t(o >> 6 & 63 | 128),\n            n += t(63 & o | 128));\n        return n\n    }(e)),\n    f = 1732584193,\n    h = 4023233417,\n    m = 2562383102,\n    y = 271733878,\n    u = 0; u < g.length; u += 16)\n        l = f,\n        d = h,\n        c = m,\n        p = y,\n        h = a(h = a(h = a(h = a(h = i(h = i(h = i(h = i(h = r(h = r(h = r(h = r(h = n(h = n(h = n(h = n(h, m = n(m, y = n(y, f = n(f, h, m, y, g[u + 0], 7, 3614090360), h, m, g[u + 1], 12, 3905402710), f, h, g[u + 2], 17, 606105819), y, f, g[u + 3], 22, 3250441966), m = n(m, y = n(y, f = n(f, h, m, y, g[u + 4], 7, 4118548399), h, m, g[u + 5], 12, 1200080426), f, h, g[u + 6], 17, 2821735955), y, f, g[u + 7], 22, 4249261313), m = n(m, y = n(y, f = n(f, h, m, y, g[u + 8], 7, 1770035416), h, m, g[u + 9], 12, 2336552879), f, h, g[u + 10], 17, 4294925233), y, f, g[u + 11], 22, 2304563134), m = n(m, y = n(y, f = n(f, h, m, y, g[u + 12], 7, 1804603682), h, m, g[u + 13], 12, 4254626195), f, h, g[u + 14], 17, 2792965006), y, f, g[u + 15], 22, 1236535329), m = r(m, y = r(y, f = r(f, h, m, y, g[u + 1], 5, 4129170786), h, m, g[u + 6], 9, 3225465664), f, h, g[u + 11], 14, 643717713), y, f, g[u + 0], 20, 3921069994), m = r(m, y = r(y, f = r(f, h, m, y, g[u + 5], 5, 3593408605), h, m, g[u + 10], 9, 38016083), f, h, g[u + 15], 14, 3634488961), y, f, g[u + 4], 20, 3889429448), m = r(m, y = r(y, f = r(f, h, m, y, g[u + 9], 5, 568446438), h, m, g[u + 14], 9, 3275163606), f, h, g[u + 3], 14, 4107603335), y, f, g[u + 8], 20, 1163531501), m = r(m, y = r(y, f = r(f, h, m, y, g[u + 13], 5, 2850285829), h, m, g[u + 2], 9, 4243563512), f, h, g[u + 7], 14, 1735328473), y, f, g[u + 12], 20, 2368359562), m = i(m, y = i(y, f = i(f, h, m, y, g[u + 5], 4, 4294588738), h, m, g[u + 8], 11, 2272392833), f, h, g[u + 11], 16, 1839030562), y, f, g[u + 14], 23, 4259657740), m = i(m, y = i(y, f = i(f, h, m, y, g[u + 1], 4, 2763975236), h, m, g[u + 4], 11, 1272893353), f, h, g[u + 7], 16, 4139469664), y, f, g[u + 10], 23, 3200236656), m = i(m, y = i(y, f = i(f, h, m, y, g[u + 13], 4, 681279174), h, m, g[u + 0], 11, 3936430074), f, h, g[u + 3], 16, 3572445317), y, f, g[u + 6], 23, 76029189), m = i(m, y = i(y, f = i(f, h, m, y, g[u + 9], 4, 3654602809), h, m, g[u + 12], 11, 3873151461), f, h, g[u + 15], 16, 530742520), y, f, g[u + 2], 23, 3299628645), m = a(m, y = a(y, f = a(f, h, m, y, g[u + 0], 6, 4096336452), h, m, g[u + 7], 10, 1126891415), f, h, g[u + 14], 15, 2878612391), y, f, g[u + 5], 21, 4237533241), m = a(m, y = a(y, f = a(f, h, m, y, g[u + 12], 6, 1700485571), h, m, g[u + 3], 10, 2399980690), f, h, g[u + 10], 15, 4293915773), y, f, g[u + 1], 21, 2240044497), m = a(m, y = a(y, f = a(f, h, m, y, g[u + 8], 6, 1873313359), h, m, g[u + 15], 10, 4264355552), f, h, g[u + 6], 15, 2734768916), y, f, g[u + 13], 21, 1309151649), m = a(m, y = a(y, f = a(f, h, m, y, g[u + 4], 6, 4149444226), h, m, g[u + 11], 10, 3174756917), f, h, g[u + 2], 15, 718787259), y, f, g[u + 9], 21, 3951481745),\n        f = o(f, l),\n        h = o(h, d),\n        m = o(m, c),\n        y = o(y, p);\n    return (s(f) + s(h) + s(m) + s(y)).toLowerCase()\n}\n\n// 正确sign值：05748e8359cd3e6deaab02d15caafc11\n// var sg =sign('5655b7041ca049730330701082886efd&1719411639403&12574478&{\"componentKey\":\"wp_pc_shop_basic_info\",\"params\":\"{\\\\\"memberId\\\\\":\\\\\"b2b-22133374292418351a\\\\\"}\"}')\n// console.log(sg)"
  },
  {
    "path": "src/javascript/x-bogus.js",
    "content": "var window = null;\n\nfunction _0x5cd844(e) {\n    var b = {\n        exports: {}\n    };\n    return e(b, b.exports), b.exports\n}\njsvmp = function(e, b, a) {\n    function f(e, b, a) {\n        return (f = function() {\n            if (\"undefined\" == typeof Reflect || !Reflect.construct || Reflect.construct.sham) return !1;\n            if (\"function\" == typeof Proxy) return !0;\n            try {\n                return Date.prototype.toString.call(Reflect.construct(Date, [], function() {})), !0\n            } catch (e) {\n                return !1\n            }\n        }() ? Reflect.construct : function(e, b, a) {\n            var f = [null];\n            f.push.apply(f, b);\n            var c = new(Function.bind.apply(e, f));\n            return a && function(e, b) {\n                (Object.setPrototypeOf || function(e, b) {\n                    return e.__proto__ = b, e\n                })(e, b)\n            }(c, a.prototype), c\n        }).apply(null, arguments)\n    }\n\n    function c(e) {\n        return function(e) {\n            if (Array.isArray(e)) {\n                for (var b = 0, a = new Array(e.length); b < e.length; b++) a[b] = e[b];\n                return a\n            }\n        }(e) || function(e) {\n            if (Symbol.iterator in Object(e) || \"[object Arguments]\" === Object.prototype.toString.call(e)) return Array.from(e)\n        }(e) || function() {\n            throw new TypeError(\"Invalid attempt to spread non-iterable instance\")\n        }()\n    }\n    for (var r = [], t = 0, d = [], i = 0, n = function(e, b) {\n            var a = e[b++],\n                f = e[b],\n                c = parseInt(\"\" + a + f, 16);\n            if (c >> 7 == 0) return [1, c];\n            if (c >> 6 == 2) {\n                var r = parseInt(\"\" + e[++b] + e[++b], 16);\n                return c &= 63, [2, r = (c <<= 8) + r]\n            }\n            if (c >> 6 == 3) {\n                var t = parseInt(\"\" + e[++b] + e[++b], 16),\n                    d = parseInt(\"\" + e[++b] + e[++b], 16);\n                return c &= 63, [3, d = (c <<= 16) + (t <<= 8) + d]\n            }\n        }, s = function(e, b) {\n            var a = parseInt(\"\" + e[b] + e[b + 1], 16);\n            return a > 127 ? -256 + a : a\n        }, o = function(e, b) {\n            var a = parseInt(\"\" + e[b] + e[b + 1] + e[b + 2] + e[b + 3], 16);\n            return a > 32767 ? -65536 + a : a\n        }, l = function(e, b) {\n            var a = parseInt(\"\" + e[b] + e[b + 1] + e[b + 2] + e[b + 3] + e[b + 4] + e[b + 5] + e[b + 6] + e[b + 7], 16);\n            return a > 2147483647 ? 0 + a : a\n        }, _ = function(e, b) {\n            return parseInt(\"\" + e[b] + e[b + 1], 16)\n        }, x = function(e, b) {\n            return parseInt(\"\" + e[b] + e[b + 1] + e[b + 2] + e[b + 3], 16)\n        }, u = u || this || window, h = (e.length, 0), p = \"\", y = h; y < h + 16; y++) {\n        var v = \"\" + e[y++] + e[y];\n        v = parseInt(v, 16), p += String.fromCharCode(v)\n    }\n    if (\"HNOJ@?RC\" != p) throw new Error(\"error magic number \" + p);\n    parseInt(\"\" + e[h += 16] + e[h + 1], 16), h += 8, t = 0;\n    for (var g = 0; g < 4; g++) {\n        var w = h + 2 * g,\n            A = parseInt(\"\" + e[w++] + e[w], 16);\n        t += (3 & A) << 2 * g\n    }\n    h += 16;\n    var C = parseInt(\"\" + e[h += 8] + e[h + 1] + e[h + 2] + e[h + 3] + e[h + 4] + e[h + 5] + e[h + 6] + e[h + 7], 16),\n        m = C,\n        S = h += 8,\n        z = x(e, h += C);\n    z[1], h += 4, r = {\n        p: [],\n        q: []\n    };\n    for (var B = 0; B < z; B++) {\n        for (var R = n(e, h), q = h += 2 * R[0], I = r.p.length, k = 0; k < R[1]; k++) {\n            var j = n(e, q);\n            r.p.push(j[1]), q += 2 * j[0]\n        }\n        h = q, r.q.push([I, r.p.length])\n    }\n    var O = {\n            5: 1,\n            6: 1,\n            70: 1,\n            22: 1,\n            23: 1,\n            37: 1,\n            73: 1\n        },\n        U = {\n            72: 1\n        },\n        D = {\n            74: 1\n        },\n        N = {\n            11: 1,\n            12: 1,\n            24: 1,\n            26: 1,\n            27: 1,\n            31: 1\n        },\n        J = {\n            10: 1\n        },\n        L = {\n            2: 1,\n            29: 1,\n            30: 1,\n            20: 1\n        },\n        T = [],\n        E = [];\n\n    function M(e, b, a) {\n        for (var f = b; f < b + a;) {\n            var c = _(e, f);\n            T[f] = c, f += 2, U[c] ? (E[f] = s(e, f), f += 2) : O[c] ? (E[f] = o(e, f), f += 4) : D[c] ? (E[f] = l(e, f), f += 8) : N[c] ? (E[f] = _(e, f), f += 2) : J[c] ? (E[f] = x(e, f), f += 4) : L[c] && (E[f] = x(e, f), f += 4)\n        }\n    }\n    return F(e, S, m / 2, [], b, a);\n\n    function P(e, b, a, n, h, p, y, v) {\n        null == p && (p = this);\n        var g, w, A, C, m = [],\n            S = 0;\n        y && (w = y);\n        var z, B, R = b,\n            q = R + 2 * a;\n        if (!v)\n            for (; R < q;) {\n                var I = parseInt(\"\" + e[R] + e[R + 1], 16);\n                R += 2;\n                var j = 3 & (z = 13 * I % 241);\n                if (z >>= 2, j < 1)\n                    if (j = 3 & z, z >>= 2, j < 1) {\n                        if ((j = z) < 1) return [1, m[S--]];\n                        j < 5 ? (w = m[S--], m[S] = m[S] * w) : j < 7 ? (w = m[S--], m[S] = m[S] != w) : j < 14 ? (A = m[S--], C = m[S--], (j = m[S--]).x === P ? j.y >= 1 ? m[++S] = F(e, j.c, j.l, A, j.z, C, null, 1) : (m[++S] = F(e, j.c, j.l, A, j.z, C, null, 0), j.y++) : m[++S] = j.apply(C, A)) : j < 16 && (B = o(e, R), (g = function b() {\n                            var a = arguments;\n                            return b.y > 0 || b.y++, F(e, b.c, b.l, a, b.z, this, null, 0)\n                        }).c = R + 4, g.l = B - 2, g.x = P, g.y = 0, g.z = h, m[S] = g, R += 2 * B - 2)\n                    } else if (j < 2)(j = z) > 8 ? (w = m[S--], m[S] = typeof w) : j > 4 ? m[S -= 1] = m[S][m[S + 1]] : j > 2 && (A = m[S--], (j = m[S]).x === P ? j.y >= 1 ? m[S] = F(e, j.c, j.l, [A], j.z, C, null, 1) : (m[S] = F(e, j.c, j.l, [A], j.z, C, null, 0), j.y++) : m[S] = j(A));\n                else if (j < 3) {\n                    if ((j = z) < 9) {\n                        for (w = m[S--], B = x(e, R), j = \"\", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]);\n                        R += 4, m[S--][j] = w\n                    } else if (j < 13) throw m[S--]\n                } else(j = z) < 1 ? m[++S] = null : j < 3 ? (w = m[S--], m[S] = m[S] >= w) : j < 12 && (m[++S] = void 0);\n                else if (j < 2)\n                    if (j = 3 & z, z >>= 2, j < 1)\n                        if ((j = z) < 5) {\n                            B = o(e, R);\n                            try {\n                                if (d[i][2] = 1, 1 == (w = P(e, R + 4, B - 3, [], h, p, null, 0))[0]) return w\n                            } catch (b) {\n                                if (d[i] && d[i][1] && 1 == (w = P(e, d[i][1][0], d[i][1][1], [], h, p, b, 0))[0]) return w\n                            } finally {\n                                if (d[i] && d[i][0] && 1 == (w = P(e, d[i][0][0], d[i][0][1], [], h, p, null, 0))[0]) return w;\n                                d[i] = 0, i--\n                            }\n                            R += 2 * B - 2\n                        } else j < 7 ? (B = _(e, R), R += 2, m[S -= B] = 0 === B ? new m[S] : f(m[S], c(m.slice(S + 1, S + B + 1)))) : j < 9 && (w = m[S--], m[S] = m[S] & w);\n                else if (j < 2)\n                    if ((j = z) > 12) m[++S] = s(e, R), R += 2;\n                    else if (j > 10) w = m[S--], m[S] = m[S] << w;\n                else if (j > 8) {\n                    for (B = x(e, R), j = \"\", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]);\n                    R += 4, m[S] = m[S][j]\n                } else j > 6 && (A = m[S--], w = delete m[S--][A]);\n                else if (j < 3)(j = z) < 2 ? m[++S] = w : j < 11 ? (w = m[S -= 2][m[S + 1]] = m[S + 2], S--) : j < 13 && (w = m[S], m[++S] = w);\n                else if ((j = z) > 12) m[++S] = p;\n                else if (j > 5) w = m[S--], m[S] = m[S] !== w;\n                else if (j > 3) w = m[S--], m[S] = m[S] / w;\n                else if (j > 1) {\n                    if ((B = o(e, R)) < 0) {\n                        v = 1, M(e, b, 2 * a), R += 2 * B - 2;\n                        break\n                    }\n                    R += 2 * B - 2\n                } else j > -1 && (m[S] = !m[S]);\n                else if (j < 3)\n                    if (j = 3 & z, z >>= 2, j < 1)(j = z) > 13 ? (m[++S] = o(e, R), R += 4) : j > 11 ? (w = m[S--], m[S] = m[S] >> w) : j > 9 ? (B = _(e, R), R += 2, w = m[S--], h[B] = w) : j > 7 ? (B = x(e, R), R += 4, A = S + 1, m[S -= B - 1] = B ? m.slice(S, A) : []) : j > 0 && (w = m[S--], m[S] = m[S] > w);\n                    else if (j < 2)(j = z) > 12 ? (w = m[S - 1], A = m[S], m[++S] = w, m[++S] = A) : j > 3 ? (w = m[S--], m[S] = m[S] == w) : j > 1 ? (w = m[S--], m[S] = m[S] + w) : j > -1 && (m[++S] = u);\n                else if (j < 3) {\n                    if ((j = z) > 13) m[++S] = !1;\n                    else if (j > 6) w = m[S--], m[S] = m[S] instanceof w;\n                    else if (j > 4) w = m[S--], m[S] = m[S] % w;\n                    else if (j > 2)\n                        if (m[S--]) R += 4;\n                        else {\n                            if ((B = o(e, R)) < 0) {\n                                v = 1, M(e, b, 2 * a), R += 2 * B - 2;\n                                break\n                            }\n                            R += 2 * B - 2\n                        }\n                    else if (j > 0) {\n                        for (B = x(e, R), w = \"\", k = r.q[B][0]; k < r.q[B][1]; k++) w += String.fromCharCode(t ^ r.p[k]);\n                        m[++S] = w, R += 4\n                    }\n                } else(j = z) > 7 ? (w = m[S--], m[S] = m[S] | w) : j > 5 ? (B = _(e, R), R += 2, m[++S] = h[\"$\" + B]) : j > 3 && (B = o(e, R), d[i][0] && !d[i][2] ? d[i][1] = [R + 4, B - 3] : d[i++] = [0, [R + 4, B - 3], 0], R += 2 * B - 2);\n                else if (j = 3 & z, z >>= 2, j > 2)(j = z) > 13 ? (m[++S] = l(e, R), R += 8) : j > 11 ? (w = m[S--], m[S] = m[S] >>> w) : j > 9 ? m[++S] = !0 : j > 7 ? (B = _(e, R), R += 2, m[S] = m[S][B]) : j > 0 && (w = m[S--], m[S] = m[S] < w);\n                else if (j > 1)(j = z) > 10 ? (B = o(e, R), d[++i] = [\n                    [R + 4, B - 3], 0, 0\n                ], R += 2 * B - 2) : j > 8 ? (w = m[S--], m[S] = m[S] ^ w) : j > 6 && (w = m[S--]);\n                else if (j > 0) {\n                    if ((j = z) > 7) w = m[S--], m[S] = m[S] in w;\n                    else if (j > 5) m[S] = ++m[S];\n                    else if (j > 3) B = _(e, R), R += 2, w = h[B], m[++S] = w;\n                    else if (j > 1) {\n                        var O = 0,\n                            U = m[S].length,\n                            D = m[S];\n                        m[++S] = function() {\n                            var e = O < U;\n                            if (e) {\n                                var b = D[O++];\n                                m[++S] = b\n                            }\n                            m[++S] = e\n                        }\n                    }\n                } else if ((j = z) > 13) w = m[S], m[S] = m[S - 1], m[S - 1] = w;\n                else if (j > 4) w = m[S--], m[S] = m[S] === w;\n                else if (j > 2) w = m[S--], m[S] = m[S] - w;\n                else if (j > 0) {\n                    for (B = x(e, R), j = \"\", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]);\n                    j = +j, R += 4, m[++S] = j\n                }\n            }\n        if (v)\n            for (; R < q;)\n                if (I = T[R], R += 2, j = 3 & (z = 13 * I % 241), z >>= 2, j > 2)\n                    if (j = 3 & z, z >>= 2, j > 2)(j = z) < 2 ? (w = m[S--], m[S] = m[S] < w) : j < 9 ? (B = E[R], R += 2, m[S] = m[S][B]) : j < 11 ? m[++S] = !0 : j < 13 ? (w = m[S--], m[S] = m[S] >>> w) : j < 15 && (m[++S] = E[R], R += 8);\n                    else if (j > 1)(j = z) < 6 || (j < 8 ? w = m[S--] : j < 10 ? (w = m[S--], m[S] = m[S] ^ w) : j < 12 && (B = E[R], d[++i] = [\n            [R + 4, B - 3], 0, 0\n        ], R += 2 * B - 2));\n        else if (j > 0)(j = z) > 7 ? (w = m[S--], m[S] = m[S] in w) : j > 5 ? m[S] = ++m[S] : j > 3 ? (B = E[R], R += 2, w = h[B], m[++S] = w) : j > 1 && (O = 0, U = m[S].length, D = m[S], m[++S] = function() {\n            var e = O < U;\n            if (e) {\n                var b = D[O++];\n                m[++S] = b\n            }\n            m[++S] = e\n        });\n        else if ((j = z) < 2) {\n            for (B = E[R], j = \"\", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]);\n            j = +j, R += 4, m[++S] = j\n        } else j < 4 ? (w = m[S--], m[S] = m[S] - w) : j < 6 ? (w = m[S--], m[S] = m[S] === w) : j < 15 && (w = m[S], m[S] = m[S - 1], m[S - 1] = w);\n        else if (j > 1)\n            if (j = 3 & z, z >>= 2, j < 1)(j = z) > 13 ? (m[++S] = E[R], R += 4) : j > 11 ? (w = m[S--], m[S] = m[S] >> w) : j > 9 ? (B = E[R], R += 2, w = m[S--], h[B] = w) : j > 7 ? (B = E[R], R += 4, A = S + 1, m[S -= B - 1] = B ? m.slice(S, A) : []) : j > 0 && (w = m[S--], m[S] = m[S] > w);\n            else if (j < 2)(j = z) < 1 ? m[++S] = u : j < 3 ? (w = m[S--], m[S] = m[S] + w) : j < 5 ? (w = m[S--], m[S] = m[S] == w) : j < 14 && (w = m[S - 1], A = m[S], m[++S] = w, m[++S] = A);\n        else if (j < 3) {\n            if ((j = z) > 13) m[++S] = !1;\n            else if (j > 6) w = m[S--], m[S] = m[S] instanceof w;\n            else if (j > 4) w = m[S--], m[S] = m[S] % w;\n            else if (j > 2) m[S--] ? R += 4 : R += 2 * (B = E[R]) - 2;\n            else if (j > 0) {\n                for (B = E[R], w = \"\", k = r.q[B][0]; k < r.q[B][1]; k++) w += String.fromCharCode(t ^ r.p[k]);\n                m[++S] = w, R += 4\n            }\n        } else(j = z) > 7 ? (w = m[S--], m[S] = m[S] | w) : j > 5 ? (B = E[R], R += 2, m[++S] = h[\"$\" + B]) : j > 3 && (B = E[R], d[i][0] && !d[i][2] ? d[i][1] = [R + 4, B - 3] : d[i++] = [0, [R + 4, B - 3], 0], R += 2 * B - 2);\n        else if (j > 0)\n            if (j = 3 & z, z >>= 2, j < 1) {\n                if ((j = z) > 9);\n                else if (j > 7) w = m[S--], m[S] = m[S] & w;\n                else if (j > 5) B = E[R], R += 2, m[S -= B] = 0 === B ? new m[S] : f(m[S], c(m.slice(S + 1, S + B + 1)));\n                else if (j > 3) {\n                    B = E[R];\n                    try {\n                        if (d[i][2] = 1, 1 == (w = P(e, R + 4, B - 3, [], h, p, null, 0))[0]) return w\n                    } catch (b) {\n                        if (d[i] && d[i][1] && 1 == (w = P(e, d[i][1][0], d[i][1][1], [], h, p, b, 0))[0]) return w\n                    } finally {\n                        if (d[i] && d[i][0] && 1 == (w = P(e, d[i][0][0], d[i][0][1], [], h, p, null, 0))[0]) return w;\n                        d[i] = 0, i--\n                    }\n                    R += 2 * B - 2\n                }\n            } else if (j < 2)\n            if ((j = z) < 8) A = m[S--], w = delete m[S--][A];\n            else if (j < 10) {\n            for (B = E[R], j = \"\", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]);\n            R += 4, m[S] = m[S][j]\n        } else j < 12 ? (w = m[S--], m[S] = m[S] << w) : j < 14 && (m[++S] = E[R], R += 2);\n        else j < 3 ? (j = z) < 2 ? m[++S] = w : j < 11 ? (w = m[S -= 2][m[S + 1]] = m[S + 2], S--) : j < 13 && (w = m[S], m[++S] = w) : (j = z) > 12 ? m[++S] = p : j > 5 ? (w = m[S--], m[S] = m[S] !== w) : j > 3 ? (w = m[S--], m[S] = m[S] / w) : j > 1 ? R += 2 * (B = E[R]) - 2 : j > -1 && (m[S] = !m[S]);\n        else if (j = 3 & z, z >>= 2, j < 1) {\n            if ((j = z) < 1) return [1, m[S--]];\n            j < 5 ? (w = m[S--], m[S] = m[S] * w) : j < 7 ? (w = m[S--], m[S] = m[S] != w) : j < 14 ? (A = m[S--], C = m[S--], (j = m[S--]).x === P ? j.y >= 1 ? m[++S] = F(e, j.c, j.l, A, j.z, C, null, 1) : (m[++S] = F(e, j.c, j.l, A, j.z, C, null, 0), j.y++) : m[++S] = j.apply(C, A)) : j < 16 && (B = E[R], (g = function b() {\n                var a = arguments;\n                return b.y > 0 || b.y++, F(e, b.c, b.l, a, b.z, this, null, 0)\n            }).c = R + 4, g.l = B - 2, g.x = P, g.y = 0, g.z = h, m[S] = g, R += 2 * B - 2)\n        } else if (j < 2)(j = z) > 8 ? (w = m[S--], m[S] = typeof w) : j > 4 ? m[S -= 1] = m[S][m[S + 1]] : j > 2 && (A = m[S--], (j = m[S]).x === P ? j.y >= 1 ? m[S] = F(e, j.c, j.l, [A], j.z, C, null, 1) : (m[S] = F(e, j.c, j.l, [A], j.z, C, null, 0), j.y++) : m[S] = j(A));\n        else if (j < 3) {\n            if ((j = z) < 9) {\n                for (w = m[S--], B = E[R], j = \"\", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]);\n                R += 4, m[S--][j] = w\n            } else if (j < 13) throw m[S--]\n        } else(j = z) < 1 ? m[++S] = null : j < 3 ? (w = m[S--], m[S] = m[S] >= w) : j < 12 && (m[++S] = void 0);\n        return [0, null]\n    }\n\n    function F(e, b, a, f, c, r, t, d) {\n        null == r && (r = this), c && !c.d && (c.d = 0, c.$0 = c, c[1] = {});\n        var i, n, s = {},\n            o = s.d = c ? c.d + 1 : 0;\n        for (s[\"$\" + o] = s, n = 0; n < o; n++) s[i = \"$\" + n] = c[i];\n        for (n = 0, o = s.length = f.length; n < o; n++) s[n] = f[n];\n        return d && !T[b] && M(e, b, 2 * a), T[b] ? P(e, b, a, 0, s, r, null, 1)[1] : P(e, b, a, 0, s, r, null, 0)[1]\n    }\n};\nvar _0x397dc7 = \"undefined\" != typeof globalThis ? globalThis : void 0 !== window ? window : \"undefined\" != typeof global ? global : \"undefined\" != typeof self ? self : {},\n    _0x124d1a = _0x5cd844(function(_0x770f81) {\n        ! function() {\n            var _0x250d36 = \"input is invalid type\",\n                _0x4cfaee = !1,\n                _0x1702f9 = {},\n                _0x5ccbb3 = !_0x4cfaee && \"object\" == typeof self,\n                _0x54d876 = !_0x1702f9.JS_MD5_NO_NODE_JS && \"object\" == typeof process && process.versions && process.versions.node,\n                _0x185caf;\n            _0x54d876 ? _0x1702f9 = _0x397dc7 : _0x5ccbb3 && (_0x1702f9 = self);\n            var _0x17dcbf = !_0x1702f9.JS_MD5_NO_COMMON_JS && _0x770f81.exports,\n                _0x554fed = !1,\n                _0x2de28f = !_0x1702f9.JS_MD5_NO_ARRAY_BUFFER && \"undefined\" != typeof ArrayBuffer,\n                _0x3a9a1b = [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"a\", \"b\", \"c\", \"d\", \"e\", \"f\"],\n                _0x465562 = [128, 32768, 8388608, -2147483648],\n                _0x20b37e = [0, 8, 16, 24],\n                _0x323604 = [\"hex\", \"array\", \"digest\", \"buffer\", \"arrayBuffer\", \"base64\"],\n                _0x2c185e = [\"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\", \"I\", \"J\", \"K\", \"L\", \"M\", \"N\", \"O\", \"P\", \"Q\", \"R\", \"S\", \"T\", \"U\", \"V\", \"W\", \"X\", \"Y\", \"Z\", \"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\", \"h\", \"i\", \"j\", \"k\", \"l\", \"m\", \"n\", \"o\", \"p\", \"q\", \"r\", \"s\", \"t\", \"u\", \"v\", \"w\", \"x\", \"y\", \"z\", \"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"+\", \"/\"],\n                _0x4b59e0 = [];\n            if (_0x2de28f) {\n                var _0x395837 = new ArrayBuffer(68);\n                _0x185caf = new Uint8Array(_0x395837), _0x4b59e0 = new Uint32Array(_0x395837)\n            }!_0x1702f9.JS_MD5_NO_NODE_JS && Array.isArray || (Array.isArray = function(e) {\n                return \"[object Array]\" === Object.prototype.toString.call(e)\n            }), _0x2de28f && (_0x1702f9.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView) && (ArrayBuffer.isView = function(e) {\n                return \"object\" == typeof e && e.buffer && e.buffer.constructor === ArrayBuffer\n            });\n            var _0x4e9930 = function(e) {\n                    return function(b) {\n                        return new _0x5887c8(!0).update(b)[e]()\n                    }\n                },\n                _0x38ba77 = function() {\n                    var e = _0x4e9930(\"hex\");\n                    _0x54d876 && (e = _0x474989(e)), e.create = function() {\n                        return new _0x5887c8\n                    }, e.update = function(b) {\n                        return e.create().update(b)\n                    };\n                    for (var b = 0; b < _0x323604.length; ++b) {\n                        var a = _0x323604[b];\n                        e[a] = _0x4e9930(a)\n                    }\n                    return e\n                },\n                _0x474989 = function(_0x57eeaa) {\n                    var _0x114910, _0x226465 = eval(\"require('crypto');\"),\n                        _0x1f6ae0 = eval(\"require('buffer')['Buffer'];\");\n                    return function(e) {\n                        if (\"string\" == typeof e) return _0x226465.createHash(\"md5\").update(e, \"utf8\").digest(\"hex\");\n                        if (null == e) throw _0x250d36;\n                        return e.constructor === ArrayBuffer && (e = new Uint8Array(e)), Array.isArray(e) || ArrayBuffer.isView(e) || e.constructor === _0x1f6ae0 ? _0x226465.createHash(\"md5\").update(new _0x1f6ae0.from(e)).digest(\"hex\") : _0x57eeaa(e)\n                    }\n                };\n\n            function _0x5887c8(e) {\n                if (e) _0x4b59e0[0] = _0x4b59e0[16] = _0x4b59e0[1] = _0x4b59e0[2] = _0x4b59e0[3] = _0x4b59e0[4] = _0x4b59e0[5] = _0x4b59e0[6] = _0x4b59e0[7] = _0x4b59e0[8] = _0x4b59e0[9] = _0x4b59e0[10] = _0x4b59e0[11] = _0x4b59e0[12] = _0x4b59e0[13] = _0x4b59e0[14] = _0x4b59e0[15] = 0, this.blocks = _0x4b59e0, this.buffer8 = _0x185caf;\n                else if (_0x2de28f) {\n                    var b = new ArrayBuffer(68);\n                    this.buffer8 = new Uint8Array(b), this.blocks = new Uint32Array(b)\n                } else this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];\n                this.h0 = this.h1 = this.h2 = this.h3 = this.start = this.bytes = this.hBytes = 0, this.finalized = this.hashed = !1, this.first = !0\n            }\n            _0x5887c8.prototype.update = function(e) {\n                if (!this.finalized) {\n                    var b, a = typeof e;\n                    if (\"string\" !== a) {\n                        if (\"object\" !== a || null === e) throw _0x250d36;\n                        if (_0x2de28f && e.constructor === ArrayBuffer) e = new Uint8Array(e);\n                        else if (!(Array.isArray(e) || _0x2de28f && ArrayBuffer.isView(e))) throw _0x250d36;\n                        b = !0\n                    }\n                    for (var f, c, r = 0, t = e.length, d = this.blocks, i = this.buffer8; r < t;) {\n                        if (this.hashed && (this.hashed = !1, d[0] = d[16], d[16] = d[1] = d[2] = d[3] = d[4] = d[5] = d[6] = d[7] = d[8] = d[9] = d[10] = d[11] = d[12] = d[13] = d[14] = d[15] = 0), b)\n                            if (_0x2de28f)\n                                for (c = this.start; r < t && c < 64; ++r) i[c++] = e[r];\n                            else\n                                for (c = this.start; r < t && c < 64; ++r) d[c >> 2] |= e[r] << _0x20b37e[3 & c++];\n                        else if (_0x2de28f)\n                            for (c = this.start; r < t && c < 64; ++r)(f = e.charCodeAt(r)) < 128 ? i[c++] = f : f < 2048 ? (i[c++] = 192 | f >> 6, i[c++] = 128 | 63 & f) : f < 55296 || f >= 57344 ? (i[c++] = 224 | f >> 12, i[c++] = 128 | f >> 6 & 63, i[c++] = 128 | 63 & f) : (f = 65536 + ((1023 & f) << 10 | 1023 & e.charCodeAt(++r)), i[c++] = 240 | f >> 18, i[c++] = 128 | f >> 12 & 63, i[c++] = 128 | f >> 6 & 63, i[c++] = 128 | 63 & f);\n                        else\n                            for (c = this.start; r < t && c < 64; ++r)(f = e.charCodeAt(r)) < 128 ? d[c >> 2] |= f << _0x20b37e[3 & c++] : f < 2048 ? (d[c >> 2] |= (192 | f >> 6) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | 63 & f) << _0x20b37e[3 & c++]) : f < 55296 || f >= 57344 ? (d[c >> 2] |= (224 | f >> 12) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | f >> 6 & 63) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | 63 & f) << _0x20b37e[3 & c++]) : (f = 65536 + ((1023 & f) << 10 | 1023 & e.charCodeAt(++r)), d[c >> 2] |= (240 | f >> 18) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | f >> 12 & 63) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | f >> 6 & 63) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | 63 & f) << _0x20b37e[3 & c++]);\n                        this.lastByteIndex = c, this.bytes += c - this.start, c >= 64 ? (this.start = c - 64, this.hash(), this.hashed = !0) : this.start = c\n                    }\n                    return this.bytes > 4294967295 && (this.hBytes += this.bytes / 4294967296 << 0, this.bytes = this.bytes % 4294967296), this\n                }\n            }, _0x5887c8.prototype.finalize = function() {\n                if (!this.finalized) {\n                    this.finalized = !0;\n                    var e = this.blocks,\n                        b = this.lastByteIndex;\n                    e[b >> 2] |= _0x465562[3 & b], b >= 56 && (this.hashed || this.hash(), e[0] = e[16], e[16] = e[1] = e[2] = e[3] = e[4] = e[5] = e[6] = e[7] = e[8] = e[9] = e[10] = e[11] = e[12] = e[13] = e[14] = e[15] = 0), e[14] = this.bytes << 3, e[15] = this.hBytes << 3 | this.bytes >>> 29, this.hash()\n                }\n            }, _0x5887c8.prototype.hash = function() {\n                var e, b, a, f, c, r, t = this.blocks;\n                this.first ? b = ((b = ((e = ((e = t[0] - 680876937) << 7 | e >>> 25) - 271733879 << 0) ^ (a = ((a = (-271733879 ^ (f = ((f = (-1732584194 ^ 2004318071 & e) + t[1] - 117830708) << 12 | f >>> 20) + e << 0) & (-271733879 ^ e)) + t[2] - 1126478375) << 17 | a >>> 15) + f << 0) & (f ^ e)) + t[3] - 1316259209) << 22 | b >>> 10) + a << 0 : (e = this.h0, b = this.h1, a = this.h2, b = ((b += ((e = ((e += ((f = this.h3) ^ b & (a ^ f)) + t[0] - 680876936) << 7 | e >>> 25) + b << 0) ^ (a = ((a += (b ^ (f = ((f += (a ^ e & (b ^ a)) + t[1] - 389564586) << 12 | f >>> 20) + e << 0) & (e ^ b)) + t[2] + 606105819) << 17 | a >>> 15) + f << 0) & (f ^ e)) + t[3] - 1044525330) << 22 | b >>> 10) + a << 0), b = ((b += ((e = ((e += (f ^ b & (a ^ f)) + t[4] - 176418897) << 7 | e >>> 25) + b << 0) ^ (a = ((a += (b ^ (f = ((f += (a ^ e & (b ^ a)) + t[5] + 1200080426) << 12 | f >>> 20) + e << 0) & (e ^ b)) + t[6] - 1473231341) << 17 | a >>> 15) + f << 0) & (f ^ e)) + t[7] - 45705983) << 22 | b >>> 10) + a << 0, b = ((b += ((e = ((e += (f ^ b & (a ^ f)) + t[8] + 1770035416) << 7 | e >>> 25) + b << 0) ^ (a = ((a += (b ^ (f = ((f += (a ^ e & (b ^ a)) + t[9] - 1958414417) << 12 | f >>> 20) + e << 0) & (e ^ b)) + t[10] - 42063) << 17 | a >>> 15) + f << 0) & (f ^ e)) + t[11] - 1990404162) << 22 | b >>> 10) + a << 0, b = ((b += ((e = ((e += (f ^ b & (a ^ f)) + t[12] + 1804603682) << 7 | e >>> 25) + b << 0) ^ (a = ((a += (b ^ (f = ((f += (a ^ e & (b ^ a)) + t[13] - 40341101) << 12 | f >>> 20) + e << 0) & (e ^ b)) + t[14] - 1502002290) << 17 | a >>> 15) + f << 0) & (f ^ e)) + t[15] + 1236535329) << 22 | b >>> 10) + a << 0, b = ((b += ((f = ((f += (b ^ a & ((e = ((e += (a ^ f & (b ^ a)) + t[1] - 165796510) << 5 | e >>> 27) + b << 0) ^ b)) + t[6] - 1069501632) << 9 | f >>> 23) + e << 0) ^ e & ((a = ((a += (e ^ b & (f ^ e)) + t[11] + 643717713) << 14 | a >>> 18) + f << 0) ^ f)) + t[0] - 373897302) << 20 | b >>> 12) + a << 0, b = ((b += ((f = ((f += (b ^ a & ((e = ((e += (a ^ f & (b ^ a)) + t[5] - 701558691) << 5 | e >>> 27) + b << 0) ^ b)) + t[10] + 38016083) << 9 | f >>> 23) + e << 0) ^ e & ((a = ((a += (e ^ b & (f ^ e)) + t[15] - 660478335) << 14 | a >>> 18) + f << 0) ^ f)) + t[4] - 405537848) << 20 | b >>> 12) + a << 0, b = ((b += ((f = ((f += (b ^ a & ((e = ((e += (a ^ f & (b ^ a)) + t[9] + 568446438) << 5 | e >>> 27) + b << 0) ^ b)) + t[14] - 1019803690) << 9 | f >>> 23) + e << 0) ^ e & ((a = ((a += (e ^ b & (f ^ e)) + t[3] - 187363961) << 14 | a >>> 18) + f << 0) ^ f)) + t[8] + 1163531501) << 20 | b >>> 12) + a << 0, b = ((b += ((f = ((f += (b ^ a & ((e = ((e += (a ^ f & (b ^ a)) + t[13] - 1444681467) << 5 | e >>> 27) + b << 0) ^ b)) + t[2] - 51403784) << 9 | f >>> 23) + e << 0) ^ e & ((a = ((a += (e ^ b & (f ^ e)) + t[7] + 1735328473) << 14 | a >>> 18) + f << 0) ^ f)) + t[12] - 1926607734) << 20 | b >>> 12) + a << 0, b = ((b += ((r = (f = ((f += ((c = b ^ a) ^ (e = ((e += (c ^ f) + t[5] - 378558) << 4 | e >>> 28) + b << 0)) + t[8] - 2022574463) << 11 | f >>> 21) + e << 0) ^ e) ^ (a = ((a += (r ^ b) + t[11] + 1839030562) << 16 | a >>> 16) + f << 0)) + t[14] - 35309556) << 23 | b >>> 9) + a << 0, b = ((b += ((r = (f = ((f += ((c = b ^ a) ^ (e = ((e += (c ^ f) + t[1] - 1530992060) << 4 | e >>> 28) + b << 0)) + t[4] + 1272893353) << 11 | f >>> 21) + e << 0) ^ e) ^ (a = ((a += (r ^ b) + t[7] - 155497632) << 16 | a >>> 16) + f << 0)) + t[10] - 1094730640) << 23 | b >>> 9) + a << 0, b = ((b += ((r = (f = ((f += ((c = b ^ a) ^ (e = ((e += (c ^ f) + t[13] + 681279174) << 4 | e >>> 28) + b << 0)) + t[0] - 358537222) << 11 | f >>> 21) + e << 0) ^ e) ^ (a = ((a += (r ^ b) + t[3] - 722521979) << 16 | a >>> 16) + f << 0)) + t[6] + 76029189) << 23 | b >>> 9) + a << 0, b = ((b += ((r = (f = ((f += ((c = b ^ a) ^ (e = ((e += (c ^ f) + t[9] - 640364487) << 4 | e >>> 28) + b << 0)) + t[12] - 421815835) << 11 | f >>> 21) + e << 0) ^ e) ^ (a = ((a += (r ^ b) + t[15] + 530742520) << 16 | a >>> 16) + f << 0)) + t[2] - 995338651) << 23 | b >>> 9) + a << 0, b = ((b += ((f = ((f += (b ^ ((e = ((e += (a ^ (b | ~f)) + t[0] - 198630844) << 6 | e >>> 26) + b << 0) | ~a)) + t[7] + 1126891415) << 10 | f >>> 22) + e << 0) ^ ((a = ((a += (e ^ (f | ~b)) + t[14] - 1416354905) << 15 | a >>> 17) + f << 0) | ~e)) + t[5] - 57434055) << 21 | b >>> 11) + a << 0, b = ((b += ((f = ((f += (b ^ ((e = ((e += (a ^ (b | ~f)) + t[12] + 1700485571) << 6 | e >>> 26) + b << 0) | ~a)) + t[3] - 1894986606) << 10 | f >>> 22) + e << 0) ^ ((a = ((a += (e ^ (f | ~b)) + t[10] - 1051523) << 15 | a >>> 17) + f << 0) | ~e)) + t[1] - 2054922799) << 21 | b >>> 11) + a << 0, b = ((b += ((f = ((f += (b ^ ((e = ((e += (a ^ (b | ~f)) + t[8] + 1873313359) << 6 | e >>> 26) + b << 0) | ~a)) + t[15] - 30611744) << 10 | f >>> 22) + e << 0) ^ ((a = ((a += (e ^ (f | ~b)) + t[6] - 1560198380) << 15 | a >>> 17) + f << 0) | ~e)) + t[13] + 1309151649) << 21 | b >>> 11) + a << 0, b = ((b += ((f = ((f += (b ^ ((e = ((e += (a ^ (b | ~f)) + t[4] - 145523070) << 6 | e >>> 26) + b << 0) | ~a)) + t[11] - 1120210379) << 10 | f >>> 22) + e << 0) ^ ((a = ((a += (e ^ (f | ~b)) + t[2] + 718787259) << 15 | a >>> 17) + f << 0) | ~e)) + t[9] - 343485551) << 21 | b >>> 11) + a << 0, this.first ? (this.h0 = e + 1732584193 << 0, this.h1 = b - 271733879 << 0, this.h2 = a - 1732584194 << 0, this.h3 = f + 271733878 << 0, this.first = !1) : (this.h0 = this.h0 + e << 0, this.h1 = this.h1 + b << 0, this.h2 = this.h2 + a << 0, this.h3 = this.h3 + f << 0)\n            }, _0x5887c8.prototype.hex = function() {\n                this.finalize();\n                var e = this.h0,\n                    b = this.h1,\n                    a = this.h2,\n                    f = this.h3;\n                return _0x3a9a1b[e >> 4 & 15] + _0x3a9a1b[15 & e] + _0x3a9a1b[e >> 12 & 15] + _0x3a9a1b[e >> 8 & 15] + _0x3a9a1b[e >> 20 & 15] + _0x3a9a1b[e >> 16 & 15] + _0x3a9a1b[e >> 28 & 15] + _0x3a9a1b[e >> 24 & 15] + _0x3a9a1b[b >> 4 & 15] + _0x3a9a1b[15 & b] + _0x3a9a1b[b >> 12 & 15] + _0x3a9a1b[b >> 8 & 15] + _0x3a9a1b[b >> 20 & 15] + _0x3a9a1b[b >> 16 & 15] + _0x3a9a1b[b >> 28 & 15] + _0x3a9a1b[b >> 24 & 15] + _0x3a9a1b[a >> 4 & 15] + _0x3a9a1b[15 & a] + _0x3a9a1b[a >> 12 & 15] + _0x3a9a1b[a >> 8 & 15] + _0x3a9a1b[a >> 20 & 15] + _0x3a9a1b[a >> 16 & 15] + _0x3a9a1b[a >> 28 & 15] + _0x3a9a1b[a >> 24 & 15] + _0x3a9a1b[f >> 4 & 15] + _0x3a9a1b[15 & f] + _0x3a9a1b[f >> 12 & 15] + _0x3a9a1b[f >> 8 & 15] + _0x3a9a1b[f >> 20 & 15] + _0x3a9a1b[f >> 16 & 15] + _0x3a9a1b[f >> 28 & 15] + _0x3a9a1b[f >> 24 & 15]\n            }, _0x5887c8.prototype.toString = _0x5887c8.prototype.hex, _0x5887c8.prototype.digest = function() {\n                this.finalize();\n                var e = this.h0,\n                    b = this.h1,\n                    a = this.h2,\n                    f = this.h3;\n                return [255 & e, e >> 8 & 255, e >> 16 & 255, e >> 24 & 255, 255 & b, b >> 8 & 255, b >> 16 & 255, b >> 24 & 255, 255 & a, a >> 8 & 255, a >> 16 & 255, a >> 24 & 255, 255 & f, f >> 8 & 255, f >> 16 & 255, f >> 24 & 255]\n            }, _0x5887c8.prototype.array = _0x5887c8.prototype.digest, _0x5887c8.prototype.arrayBuffer = function() {\n                this.finalize();\n                var e = new ArrayBuffer(16),\n                    b = new Uint32Array(e);\n                return b[0] = this.h0, b[1] = this.h1, b[2] = this.h2, b[3] = this.h3, e\n            }, _0x5887c8.prototype.buffer = _0x5887c8.prototype.arrayBuffer, _0x5887c8.prototype.base64 = function() {\n                for (var e, b, a, f = \"\", c = this.array(), r = 0; r < 15;) e = c[r++], b = c[r++], a = c[r++], f += _0x2c185e[e >>> 2] + _0x2c185e[63 & (e << 4 | b >>> 4)] + _0x2c185e[63 & (b << 2 | a >>> 6)] + _0x2c185e[63 & a];\n                return f + (_0x2c185e[(e = c[r]) >>> 2] + _0x2c185e[e << 4 & 63] + \"==\")\n            };\n            var _0x4dd781 = _0x38ba77();\n            _0x17dcbf ? _0x770f81.exports = _0x4dd781 : (_0x1702f9.md5 = _0x4dd781, _0x554fed && (void 0)(function() {\n                return _0x4dd781\n            }))\n        }()\n    });\n\nfunction _0x178cef(e) {\n    return jsvmp(\"484e4f4a403f52430038001eab0015840e8ee21a00000000000000621b000200001d000146000306000e271f001b000200021d00010500121b001b000b021b000b04041d0001071b000b0500000003000126207575757575757575757575757575757575757575757575757575757575757575\", [, , void 0 !== _0x124d1a ? _0x124d1a : void 0, _0x178cef, e])\n}\nfor (var _0xb55f3e = {\n        boe: !1,\n        aid: 0,\n        dfp: !1,\n        sdi: !1,\n        enablePathList: [],\n        _enablePathListRegex: [],\n        urlRewriteRules: [],\n        _urlRewriteRules: [],\n        initialized: !1,\n        enableTrack: !1,\n        track: {\n            unitTime: 0,\n            unitAmount: 0,\n            fre: 0\n        },\n        triggerUnload: !1,\n        region: \"\",\n        regionConf: {},\n        umode: 0,\n        v: !1,\n        perf: !1,\n        xxbg: !0\n    }, _0x3eaf64 = {\n        debug: function(e, b) {\n            let a = !1;\n            a = !1\n        }\n    }, _0x233455 = [\"0\", \"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\", \"9\", \"a\", \"b\", \"c\", \"d\", \"e\", \"f\"], _0x2e9f6d = [], _0x511f86 = [], _0x3d35de = 0; _0x3d35de < 256; _0x3d35de++) _0x2e9f6d[_0x3d35de] = _0x233455[_0x3d35de >> 4 & 15] + _0x233455[15 & _0x3d35de], _0x3d35de < 16 && (_0x3d35de < 10 ? _0x511f86[48 + _0x3d35de] = _0x3d35de : _0x511f86[87 + _0x3d35de] = _0x3d35de);\nvar _0x2ce54d = function(e) {\n        for (var b = e.length, a = \"\", f = 0; f < b;) a += _0x2e9f6d[e[f++]];\n        return a\n    },\n    _0x5960a2 = function(e) {\n        for (var b = e.length >> 1, a = b << 1, f = new Uint8Array(b), c = 0, r = 0; r < a;) f[c++] = _0x511f86[e.charCodeAt(r++)] << 4 | _0x511f86[e.charCodeAt(r++)];\n        return f\n    },\n    _0x4e46b6 = {\n        encode: _0x2ce54d,\n        decode: _0x5960a2\n    };\n\nfunction sign(e, b) {\n    return jsvmp(\"484e4f4a403f5243001f240fbf2031ccf317480300000000000007181b0002012e1d00921b000b171b000b02402217000a1c1b000b1726402217000c1c1b000b170200004017002646000306000e271f001b000200021d00920500121b001b000b031b000b17041d0092071b000b041e012f17000d1b000b05260a0000101c1b000b06260a0000101c1b001b000b071e01301d00931b001b000b081e00081d00941b0048021d00951b001b000b1b1d00961b0048401d009e1b001b000b031b000b16041d009f1b001b000b09221e0131241b000b031b000b09221e0131241b000b1e0a000110040a0001101d00d51b001b000b09221e0131241b000b031b000b09221e0131241b000b180a000110040a0001101d00d71b001b000b0a1e00101d00d91b001b000b0b261b000b1a1b000b190a0002101d00db1b001b000b0c261b000b221b000b210a0002101d00dc1b001b000b0d261b000b230200200a0002101d00dd1b001b000b09221e0131241b000b031b000b24040a0001101d00df1b001b000b0e1a00221e00de240a0000104903e82b1d00e31b001b000b0f260a0000101d00e41b001b000b1d1d00e71b001b000b1a4901002b1d00e81b001b000b1a4901002c1d00ea1b001b000b191d00f21b001b000b1f480e191d00f81b001b000b1f480f191d00f91b001b000b20480e191d00fb1b001b000b20480f191d00fe1b001b000b25480e191d01001b001b000b25480f191d01011b001b000b264818344900ff2f1d01031b001b000b264810344900ff2f1d01321b001b000b264808344900ff2f1d01331b001b000b264800344900ff2f1d01341b001b000b274818344900ff2f1d01351b001b000b274810344900ff2f1d01361b001b000b274808344900ff2f1d01371b001b000b274800344900ff2f1d01381b001b000b281b000b29311b000b2a311b000b2b311b000b2c311b000b2d311b000b2e311b000b2f311b000b30311b000b31311b000b32311b000b33311b000b34311b000b35311b000b36311b000b37311b000b38311b000b39311d01391b004900ff1d013a1b001b000b10261b000b281b000b2a1b000b2c1b000b2e1b000b301b000b321b000b341b000b361b000b381b000b3a1b000b291b000b2b1b000b2d1b000b2f1b000b311b000b331b000b351b000b371b000b390a0013101d013b1b001b000b0c261b000b111b000b3b041b000b3c0a0002101d013c1b001b000b12261b000b1c1b000b3b1b000b3d0a0003101d013d1b001b000b13261b000b3e0200240a0002101d013e1b000b3f0000013f000126207575757575757575757575757575757575757575757575757575757575757575012b0e7776757a7d7643617c637661676a027a77065c717976706708777671667474766107767d65707c77760374766707707c7d607c7f7607757a61767166740a7c66677661447a77677b0a7a7d7d7661447a77677b0b7c666776615b767a747b670b7a7d7d76615b767a747b6709666076615274767d670b677c5f7c64766150726076077a7d77766b5c7508767f767067617c7d09667d7776757a7d76770963617c677c676a637608677c4067617a7d740470727f7f0763617c7076606010487c71797670673363617c707660604e067c717976706705677a677f76047d7c7776012e0125012402602341525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a383c2e0260224157787763747b2749586042512b233c5e75656420254b5a22412126384446527f567a245d5f717c624a475c4366697e5579597d616a6b2a5b45547072406750762e0260214157787763747b2749586042512b233c5e75656420254b5a224121263e4446527f567a245d5f717c624a475c4366697e5579597d616a6b2a5b45547072406750762e02602041525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a3e4c2e012a022222067f767d74677b0a707b7261507c7776526702222306707b726152670f487c717976706733447a7d777c644e08577c70667e767d6712487c7179767067335d72657a7472677c614e057960777c7e10487c7179767067335b7a60677c616a4e07637f66747a7d60084c637b727d677c7e0b70727f7f437b727d677c7e0b4c4c7d7a747b677e726176055266777a7c1850727d65726041767d7776617a7d74507c7d67766b6721570964767177617a657661137476675c647d43617c637661676a5d727e7660097f727d74667274766006707b617c7e760761667d677a7e7607707c7d7d767067144c4c64767177617a6576614c7665727f66726776134c4c60767f767d7a667e4c7665727f667267761b4c4c64767177617a6576614c6070617a63674c75667d70677a7c7d174c4c64767177617a6576614c6070617a63674c75667d70154c4c64767177617a6576614c6070617a63674c757d134c4c756b77617a6576614c7665727f66726776124c4c77617a6576614c667d64617263637677154c4c64767177617a6576614c667d64617263637677114c4c77617a6576614c7665727f66726776144c4c60767f767d7a667e4c667d64617263637677144c4c756b77617a6576614c667d64617263637677094c60767f767d7a667e0c70727f7f40767f767d7a667e164c40767f767d7a667e4c5a57564c4176707c6177766108777c70667e767d670478766a60057e7267707b06417674566b630a4f3748723e694e77704c067072707b764c04607c7e7608707675407b72616308507675407b72616305767c72637a16767c44767151617c64607661577a60637267707b76610f717a7d775c717976706752606a7d700e7a60565c44767151617c646076610120047c63767d0467766067097a7d707c747d7a677c077c7d7661617c6104707c77761242465c47524c564b5056565756574c5641410e607660607a7c7d40677c61727476076076675a67767e10607c7e7658766a5b766176516a6776770a61767e7c65765a67767e097a7d77766b767757510c437c7a7d6776615665767d670e5e40437c7a7d6776615665767d670d706176726776567f767e767d670670727d65726009677c5772677246415f076176637f727076034f603901740a7d72677a6576707c777614487c717976706733437f66747a7d526161726a4e4a4d7b676763602c294f3c4f3c3b48233e2a4e68223f206e3b4f3d48233e2a4e68223f206e3a68206e6f48723e75233e2a4e68223f276e3b2948723e75233e2a4e68223f276e3a68246e3a0127087f7c7072677a7c7d047b61767504757a7f76107b676763293c3c7f7c70727f7b7c606708637f7267757c617e02222102222007647a7d777c646002222703647a7d02222607727d77617c7a77022225057f7a7d666b022224067a637b7c7d7602222b047a63727702222a047a637c77022123037e7270022122097e72707a7d677c607b0c7e72704c637c64766163703a0470617c60036b22220570617a7c6005756b7a7c6004637a787602212102212002212702212602212502212402212b08757a6176757c6b3c067c637661723c05337c63613c05337c63673c07707b617c7e763c0867617a77767d673c047e607a7602212a0220230665767d777c6106547c7c747f760e4c637261727e40647a67707b5c7d0a777a61767067407a747d0a707c7d607a6067767d670660647a67707b03777c7e07637b727d677c7e047b7c7c7840525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a3e3d03727a77017d01750161096067726167477a7e7601670972717a7f7a677a76600a677a7e766067727e6322137b72617764726176507c7d70666161767d706a0c7776657a70765e767e7c616a087f727d74667274760a6176607c7f66677a7c7d0f7265727a7f4176607c7f66677a7c7d0960706176767d477c630a60706176767d5f767567107776657a7076437a6b767f4172677a7c0a63617c77667067406671077172676776616a016309677c66707b5a7d757c08677a7e76697c7d760a677a7e766067727e6321077463665a7d757c0b7960557c7d67605f7a60670b637f66747a7d605f7a60670a677a7e766067727e63200a76657661507c7c787a760767674c60707a77017e0b606a7d67726b5661617c610c7d72677a65765f767d74677b056167705a43097563457661607a7c7d0b4c4c657661607a7c7d4c4c08707f7a767d675a770a677a7e766067727e63270b766b67767d77557a767f77046366607b03727f7f04677b767d097172607625274c707b0c75617c7e507b7261507c7776067125274c2023022022087172607625274c23022021087172607625274c22022020087172607625274c2102202702202602202507747667477a7e760220240b777c7e5d7c6745727f7a77096066716067617a7d740863617c677c707c7f02202b02202a01230e222323232323232322222323232302272302272207757c616176727f02272104717c776a096067617a7d747a756a02686e0b717c776a45727f216067610a717c776a4c7b72607b2e01350366617f02272005626676616a0a72607c7f774c607a747d096372677b7d727e762e0967674c6476717a772e063566667a772e0227270227260e4c716a6776774c6076704c777a770227250a27212a272a2524212a25097576457661607a7c7d0227240e4c232151274925647c232323232202272b02272a05607f7a7076022623074056505a5d555c037d7c6409677a7e766067727e6305757f7c7c610661727d777c7e0f7476674747447671507c7c787a7660056767647a770867674c6476717a770767674476715a770b67674c6476717a774c65210967674476717a7745210761667d7d7a7d7405757f66607b087e7c65765f7a60670660637f7a70760671765e7c657609707f7a70785f7a6067077176507f7a70780c78766a717c7261775f7a60670a717658766a717c7261770b7270677a657640677267760b647a7d777c6440677267760360477e05676172707808667d7a67477a7e76037270700a667d7a67527e7c667d670871767b72657a7c61077e6074476a637603645a5707727a775f7a60670b63617a6572706a5e7c777606706660677c7e067260607a747d0f4456514c5756455a50564c5a5d555c0479607c7d0a6176747a7c7d507c7d75096176637c616746617f04766b7a67094b3e5e403e404746510c4b3e5e403e43524a5f5c525720232323232323232323232323232323232323232323232323232323232323232320772722772b70772a2b75232371212327762a2b23232a2a2b7670752b272124760165066671707c7776067776707c777602262202262102262002262702262602262502262402262b02262a022523022522022521022520\", [, , void 0, void 0 !== _0x178cef ? _0x178cef : void 0, {\n        boe: !1,\n        aid: 0,\n        dfp: !1,\n        sdi: !1,\n        enablePathList: [],\n        _enablePathListRegex: [/\\/web\\/report/],\n        urlRewriteRules: [],\n        _urlRewriteRules: [],\n        initialized: !1,\n        enableTrack: !1,\n        track: {\n            unitTime: 0,\n            unitAmount: 0,\n            fre: 0\n        },\n        triggerUnload: !1,\n        region: \"\",\n        regionConf: {},\n        umode: 0,\n        v: !1,\n        perf: !1,\n        xxbg: !0\n    }, () => 0, () => \"03v\", {\n        ubcode: 0\n    }, {\n        bogusIndex: 0,\n        msNewTokenList: [],\n        moveList: [],\n        clickList: [],\n        keyboardList: [],\n        activeState: [],\n        aidList: [],\n        envcode: 0,\n        msToken: \"\",\n        msStatus: 0,\n        __ac_testid: \"\",\n        ttwid: \"\",\n        tt_webid: \"\",\n        tt_webid_v2: \"\"\n    }, void 0 !== _0x4e46b6 ? _0x4e46b6 : void 0, {\n        userAgent: b\n    }, (e, b) => {\n        let a = new Uint8Array(3);\n        return a[0] = e / 256, a[1] = e % 256, a[2] = b % 256, String.fromCharCode.apply(null, a)\n    }, (e, b) => {\n        let a, f = [],\n            c = 0,\n            r = \"\";\n        for (let e = 0; e < 256; e++) f[e] = e;\n        for (let b = 0; b < 256; b++) c = (c + f[b] + e.charCodeAt(b % e.length)) % 256, a = f[b], f[b] = f[c], f[c] = a;\n        let t = 0;\n        c = 0;\n        for (let e = 0; e < b.length; e++) c = (c + f[t = (t + 1) % 256]) % 256, a = f[t], f[t] = f[c], f[c] = a, r += String.fromCharCode(b.charCodeAt(e) ^ f[(f[t] + f[c]) % 256]);\n        return r\n    }, (e, b) => jsvmp(\"484e4f4a403f524300281018f7b851f02d296e5b00000000000004a21b0002001d1d001e1b00131e00061a001d001f1b000b070200200200210d1b000b070200220200230d1b000b070200240200250d1b000b070200260200270d1b001b000b071b000b05191d00031b000200001d00281b0048001d00291b000b041e002a1b000b0b4803283b1700f11b001b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f4810331b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f480833301b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f301d002c1b00220b091b000b08221e002d241b000b0a4a00fc00002f4812340a000110281d00281b00220b091b000b08221e002d241b000b0a4a0003f0002f480c340a000110281d00281b00220b091b000b08221e002d241b000b0a490fc02f4806340a000110281d00281b00220b091b000b08221e002d241b000b0a483f2f0a000110281d002816ff031b000b041e002a1b000b0b294800391700e01b001b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f4810331b000b041e002a1b000b0b3917001e1b000b04221e002b241b000b0b0a0001104900ff2f4808331600054800301d002c1b00220b091b000b08221e002d241b000b0a4a00fc00002f4812340a000110281d00281b00220b091b000b08221e002d241b000b0a4a0003f0002f480c340a000110281d00281b00220b091b000b041e002a1b000b0b3917001e1b000b08221e002d241b000b0a490fc02f4806340a0001101600071b000b06281d00281b00220b091b000b06281d00281b000b090000002e000126207575757575757575757575757575757575757575757575757575757575757575012b0e7776757a7d7643617c637661676a027a77065c717976706708777671667474766107767d65707c77760374766707707c7d607c7f7607757a61767166740a7c66677661447a77677b0a7a7d7d7661447a77677b0b7c666776615b767a747b670b7a7d7d76615b767a747b6709666076615274767d670b677c5f7c64766150726076077a7d77766b5c7508767f767067617c7d09667d7776757a7d76770963617c677c676a637608677c4067617a7d740470727f7f0763617c7076606010487c71797670673363617c707660604e067c717976706705677a677f76047d7c7776012e0125012402602341525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a383c2e0260224157787763747b2749586042512b233c5e75656420254b5a22412126384446527f567a245d5f717c624a475c4366697e5579597d616a6b2a5b45547072406750762e0260214157787763747b2749586042512b233c5e75656420254b5a224121263e4446527f567a245d5f717c624a475c4366697e5579597d616a6b2a5b45547072406750762e02602041525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a3e4c2e012a022222067f767d74677b0a707b7261507c7776526702222306707b72615267\", [, , , , e, b]), \"undefined\" != typeof Date ? Date : void 0, () => 0, (e, b, a, f, c, r, t, d, i, n, s, o, l, _, x, u, h, p, y) => {\n        let v = new Uint8Array(19);\n        return v[0] = e, v[1] = s, v[2] = b, v[3] = o, v[4] = a, v[5] = l, v[6] = f, v[7] = _, v[8] = c, v[9] = x, v[10] = r, v[11] = u, v[12] = t, v[13] = h, v[14] = d, v[15] = p, v[16] = i, v[17] = y, v[18] = n, String.fromCharCode.apply(null, v)\n    }, e => String.fromCharCode(e), (e, b, a) => String.fromCharCode(e) + String.fromCharCode(b) + a, (e, b) => jsvmp(\"484e4f4a403f524300281018f7b851f02d296e5b00000000000004a21b0002001d1d001e1b00131e00061a001d001f1b000b070200200200210d1b000b070200220200230d1b000b070200240200250d1b000b070200260200270d1b001b000b071b000b05191d00031b000200001d00281b0048001d00291b000b041e002a1b000b0b4803283b1700f11b001b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f4810331b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f480833301b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f301d002c1b00220b091b000b08221e002d241b000b0a4a00fc00002f4812340a000110281d00281b00220b091b000b08221e002d241b000b0a4a0003f0002f480c340a000110281d00281b00220b091b000b08221e002d241b000b0a490fc02f4806340a000110281d00281b00220b091b000b08221e002d241b000b0a483f2f0a000110281d002816ff031b000b041e002a1b000b0b294800391700e01b001b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f4810331b000b041e002a1b000b0b3917001e1b000b04221e002b241b000b0b0a0001104900ff2f4808331600054800301d002c1b00220b091b000b08221e002d241b000b0a4a00fc00002f4812340a000110281d00281b00220b091b000b08221e002d241b000b0a4a0003f0002f480c340a000110281d00281b00220b091b000b041e002a1b000b0b3917001e1b000b08221e002d241b000b0a490fc02f4806340a0001101600071b000b06281d00281b00220b091b000b06281d00281b000b090000002e000126207575757575757575757575757575757575757575757575757575757575757575012b0e7776757a7d7643617c637661676a027a77065c717976706708777671667474766107767d65707c77760374766707707c7d607c7f7607757a61767166740a7c66677661447a77677b0a7a7d7d7661447a77677b0b7c666776615b767a747b670b7a7d7d76615b767a747b6709666076615274767d670b677c5f7c64766150726076077a7d77766b5c7508767f767067617c7d09667d7776757a7d76770963617c677c676a637608677c4067617a7d740470727f7f0763617c7076606010487c71797670673363617c707660604e067c717976706705677a677f76047d7c7776012e0125012402602341525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a383c2e0260224157787763747b2749586042512b233c5e75656420254b5a22412126384446527f567a245d5f717c624a475c4366697e5579597d616a6b2a5b45547072406750762e0260214157787763747b2749586042512b233c5e75656420254b5a224121263e4446527f567a245d5f717c624a475c4366697e5579597d616a6b2a5b45547072406750762e02602041525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a3e4c2e012a022222067f767d74677b0a707b7261507c7776526702222306707b72615267\", [, , , , e, b]), , sign, e, void 0])\n}\n\nmodule.exports = {\n    sign\n  };"
  },
  {
    "path": "src/logger.py",
    "content": "# -*- coding: utf-8 -*-\n\nimport os\nimport sys\nfrom loguru import logger\n\nlogger.remove()\n\ncustom_format = \"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> - <level>{message}</level>\"\n\nlogger.add(\n    sink=sys.stderr,\n    format=custom_format,\n    level=\"DEBUG\",\n    colorize=True,\n    enqueue=True\n)\n\nscript_path = os.path.split(os.path.realpath(sys.argv[0]))[0]\n\nlogger.add(\n    f\"{script_path}/logs/streamget.log\",\n    level=\"DEBUG\",\n    format=\"{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}\",\n    filter=lambda i: i[\"level\"].name != \"INFO\",\n    serialize=False,\n    enqueue=True,\n    retention=1,\n    rotation=\"300 KB\",\n    encoding='utf-8'\n)\n\nlogger.add(\n    f\"{script_path}/logs/PlayURL.log\",\n    level=\"INFO\",\n    format=\"{time:YYYY-MM-DD HH:mm:ss.SSS} | {message}\",\n    filter=lambda i: i[\"level\"].name == \"INFO\",\n    serialize=False,\n    enqueue=True,\n    retention=1,\n    rotation=\"300 KB\",\n    encoding='utf-8'\n)\n"
  },
  {
    "path": "src/proxy.py",
    "content": "import os\nimport sys\nfrom enum import Enum, auto\nfrom dataclasses import dataclass, field\nfrom .utils import logger\n\n\nclass ProxyType(Enum):\n    HTTP = auto()\n    HTTPS = auto()\n    SOCKS = auto()\n\n\n@dataclass(frozen=True)\nclass ProxyInfo:\n    ip: str = field(default=\"\", repr=True)\n    port: str = field(default=\"\", repr=True)\n\n    def __post_init__(self):\n        if (self.ip and not self.port) or (not self.ip and self.port):\n            raise ValueError(\"IP or port cannot be empty\")\n\n        if (self.ip and self.port) and (not self.port.isdigit() or not (1 <= int(self.port) <= 65535)):\n            raise ValueError(\"Port must be a digit between 1 and 65535\")\n\n\nclass ProxyDetector:\n    def __init__(self):\n        if sys.platform.startswith('win'):\n            import winreg\n            self.winreg = winreg\n            self.__path = r'Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings'\n            with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as key_user:\n                self.__INTERNET_SETTINGS = winreg.OpenKeyEx(key_user, self.__path, 0, winreg.KEY_ALL_ACCESS)\n        else:\n            self.__is_windows = False\n\n    def get_proxy_info(self) -> ProxyInfo:\n        if sys.platform.startswith('win'):\n            ip, port = self._get_proxy_info_windows()\n        else:\n            ip, port = self._get_proxy_info_linux()\n        return ProxyInfo(ip, port)\n\n    def is_proxy_enabled(self) -> bool:\n        if sys.platform.startswith('win'):\n            return self._is_proxy_enabled_windows()\n        else:\n            return self._is_proxy_enabled_linux()\n\n    def _get_proxy_info_windows(self) -> tuple[str, str]:\n        ip, port = \"\", \"\"\n        if self._is_proxy_enabled_windows():\n            try:\n                ip_port = self.winreg.QueryValueEx(self.__INTERNET_SETTINGS, \"ProxyServer\")[0]\n                if ip_port:\n                    ip, port = ip_port.split(\":\")\n            except FileNotFoundError as err:\n                logger.warning(\"No proxy information found: \" + str(err))\n            except Exception as err:\n                logger.error(\"An error occurred: \" + str(err))\n        else:\n            logger.debug(\"No proxy is enabled on the system\")\n        return ip, port\n\n    def _is_proxy_enabled_windows(self) -> bool:\n        try:\n            if self.winreg.QueryValueEx(self.__INTERNET_SETTINGS, \"ProxyEnable\")[0] == 1:\n                return True\n        except FileNotFoundError as err:\n            print(\"No proxy information found: \" + str(err))\n        except Exception as err:\n            print(\"An error occurred: \" + str(err))\n        return False\n\n    @staticmethod\n    def _get_proxy_info_linux() -> tuple[str, str]:\n        proxies = {\n            'http': os.getenv('http_proxy'),\n            'https': os.getenv('https_proxy'),\n            'ftp': os.getenv('ftp_proxy')\n        }\n        ip = port = \"\"\n        for proto, proxy in proxies.items():\n            if proxy:\n                ip, port = proxy.split(':')\n                break\n        return ip, port\n\n    def _is_proxy_enabled_linux(self) -> bool:\n        proxies = self._get_proxy_info_linux()\n        return any(proxy != '' for proxy in proxies)\n"
  },
  {
    "path": "src/room.py",
    "content": "# -*- encoding: utf-8 -*-\n\n\"\"\"\nAuthor: Hmily\nGitHub:https://github.com/ihmily\nDate: 2023-07-17 23:52:05\nUpdate: 2025-02-04 04:57:00\nCopyright (c) 2023 by Hmily, All Rights Reserved.\n\"\"\"\nimport re\nimport urllib.parse\nimport execjs\nimport httpx\nimport urllib.request\nfrom . import JS_SCRIPT_PATH, utils\n\nno_proxy_handler = urllib.request.ProxyHandler({})\nopener = urllib.request.build_opener(no_proxy_handler)\n\n\nclass UnsupportedUrlError(Exception):\n    pass\n\n\nHEADERS = {\n    'User-Agent': 'Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) '\n                  'SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36',\n    'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n    'Cookie': 's_v_web_id=verify_lk07kv74_QZYCUApD_xhiB_405x_Ax51_GYO9bUIyZQVf'\n}\n\nHEADERS_PC = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\n                      'Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0',\n        'Cookie': 'sessionid=7494ae59ae06784454373ce25761e864; __ac_nonce=0670497840077ee4c9eb2; '\n                  '__ac_signature=_02B4Z6wo00f012DZczQAAIDCJJBb3EjnINdg-XeAAL8-db;  '\n                  's_v_web_id=verify_m1ztgtjj_vuHnMLZD_iwZ9_4YO4_BdN1_7wLP3pyqXsf2; '\n    }\n\n\n# X-bogus算法\nasync def get_xbogus(url: str, headers: dict | None = None) -> str:\n    if not headers or 'user-agent' not in (k.lower() for k in headers):\n        headers = HEADERS\n    query = urllib.parse.urlparse(url).query\n    xbogus = execjs.compile(open(f'{JS_SCRIPT_PATH}/x-bogus.js').read()).call(\n        'sign', query, headers.get(\"User-Agent\", \"user-agent\"))\n    return xbogus\n\n\n# 获取房间ID和用户secID\nasync def get_sec_user_id(url: str, proxy_addr: str | None = None, headers: dict | None = None) -> tuple | None:\n    if not headers or all(k.lower() not in ['user-agent', 'cookie'] for k in headers):\n        headers = HEADERS\n\n    try:\n        proxy_addr = utils.handle_proxy_addr(proxy_addr)\n        async with httpx.AsyncClient(proxy=proxy_addr, timeout=15) as client:\n            response = await client.get(url, headers=headers, follow_redirects=True)\n            redirect_url = response.url\n            if 'reflow/' in str(redirect_url):\n                match = re.search(r'sec_user_id=([\\w_\\-]+)&', str(redirect_url))\n                if match:\n                    sec_user_id = match.group(1)\n                    room_id = str(redirect_url).split('?')[0].rsplit('/', maxsplit=1)[1]\n                    return room_id, sec_user_id\n                else:\n                    raise RuntimeError(\"Could not find sec_user_id in the URL.\")\n            else:\n                raise UnsupportedUrlError(\"The redirect URL does not contain 'reflow/'.\")\n    except UnsupportedUrlError as e:\n        raise e\n    except Exception as e:\n        raise RuntimeError(f\"An error occurred: {e}\")\n\n\n# 获取抖音号\nasync def get_unique_id(url: str, proxy_addr: str | None = None, headers: dict | None = None) -> str | None:\n    if not headers or all(k.lower() not in ['user-agent', 'cookie'] for k in headers):\n        headers = HEADERS\n\n    try:\n        proxy_addr = utils.handle_proxy_addr(proxy_addr)\n        async with httpx.AsyncClient(proxy=proxy_addr, timeout=15) as client:\n            response = await client.get(url, headers=headers, follow_redirects=True)\n            redirect_url = str(response.url)\n            if 'reflow/' in str(redirect_url):\n                raise UnsupportedUrlError(\"Unsupported URL\")\n            sec_user_id = redirect_url.split('?')[0].rsplit('/', maxsplit=1)[1]\n            headers['Cookie'] = ('ttwid=1%7C4ejCkU2bKY76IySQENJwvGhg1IQZrgGEupSyTKKfuyk%7C1740470403%7Cbc9a'\n                                 'd2ee341f1a162f9e27f4641778030d1ae91e31f9df6553a8f2efa3bdb7b4; __ac_nonce=06'\n                                 '83e59f3009cc48fbab0; __ac_signature=_02B4Z6wo00f01mG6waQAAIDB9JUCzFb6.TZhmsU'\n                                 'AAPBf34; __ac_referer=__ac_blank')\n            user_page_response = await client.get(f'https://www.iesdouyin.com/share/user/{sec_user_id}',\n                                                headers=headers, follow_redirects=True)\n            matches = re.findall(r'unique_id\":\"(.*?)\",\"verification_type', user_page_response.text)\n            if matches:\n                unique_id = matches[-1]\n                return unique_id\n            else:\n                raise RuntimeError(\"Could not find unique_id in the response.\")\n    except UnsupportedUrlError as e:\n        raise e\n    except Exception as e:\n        raise RuntimeError(f\"An error occurred: {e}\")\n\n\n# 获取直播间webID\nasync def get_live_room_id(room_id: str, sec_user_id: str, proxy_addr: str | None = None, params: dict | None = None,\n                           headers: dict | None = None) -> str:\n    if not headers or all(k.lower() not in ['user-agent', 'cookie'] for k in headers):\n        headers = HEADERS\n\n    if not params:\n        params = {\n            \"verifyFp\": \"verify_lk07kv74_QZYCUApD_xhiB_405x_Ax51_GYO9bUIyZQVf\",\n            \"type_id\": \"0\",\n            \"live_id\": \"1\",\n            \"room_id\": room_id,\n            \"sec_user_id\": sec_user_id,\n            \"app_id\": \"1128\",\n            \"msToken\": \"wrqzbEaTlsxt52-vxyZo_mIoL0RjNi1ZdDe7gzEGMUTVh_HvmbLLkQrA_1HKVOa2C6gkxb6IiY6TY2z8enAkPEwGq--gM\"\n                       \"-me3Yudck2ailla5Q4osnYIHxd9dI4WtQ==\",\n        }\n\n    api = f'https://webcast.amemv.com/webcast/room/reflow/info/?{urllib.parse.urlencode(params)}'\n    xbogus = await get_xbogus(api)\n    api = api + \"&X-Bogus=\" + xbogus\n\n    try:\n        proxy_addr = utils.handle_proxy_addr(proxy_addr)\n        async with httpx.AsyncClient(proxy=proxy_addr,\n                                     timeout=15) as client:\n            response = await client.get(api, headers=headers)\n            response.raise_for_status()\n            json_data = response.json()\n            return json_data['data']['room']['owner']['web_rid']\n    except httpx.HTTPStatusError as e:\n        print(f\"HTTP status error occurred: {e.response.status_code}\")\n        raise\n    except Exception as e:\n        print(f\"An exception occurred during get_live_room_id: {e}\")\n        raise\n\n\nif __name__ == '__main__':\n    room_url = \"https://v.douyin.com/iQLgKSj/\"\n    _room_id, sec_uid = get_sec_user_id(room_url)\n    web_rid = get_live_room_id(_room_id, sec_uid)\n    print(\"return web_rid:\", web_rid)\n"
  },
  {
    "path": "src/spider.py",
    "content": "# -*- encoding: utf-8 -*-\n\n\"\"\"\nAuthor: Hmily\nGitHub: https://github.com/ihmily\nDate: 2023-07-15 23:15:00\nUpdate: 2025-10-23 18:28:00\nCopyright (c) 2023-2025 by Hmily, All Rights Reserved.\nFunction: Get live stream data.\n\"\"\"\n\nimport hashlib\nimport random\nimport subprocess\nimport time\nimport uuid\nfrom operator import itemgetter\nimport urllib.parse\nimport urllib.error\nfrom typing import List\nimport httpx\nimport ssl\nimport re\nimport json\nimport execjs\nimport urllib.request\nfrom . import JS_SCRIPT_PATH, utils\nfrom .utils import trace_error_decorator, generate_random_string\nfrom .logger import script_path\nfrom .room import get_sec_user_id, get_unique_id, UnsupportedUrlError\nfrom .http_clients.async_http import async_req\nfrom .ab_sign import ab_sign\n\n\nssl_context = ssl.create_default_context()\nssl_context.check_hostname = False\nssl_context.verify_mode = ssl.CERT_NONE\nOptionalStr = str | None\nOptionalDict = dict | None\n\n\ndef get_params(url: str, params: str) -> OptionalStr:\n    parsed_url = urllib.parse.urlparse(url)\n    query_params = urllib.parse.parse_qs(parsed_url.query)\n\n    if params in query_params:\n        return query_params[params][0]\n\n\nasync def get_play_url_list(m3u8: str, proxy: OptionalStr = None, header: OptionalDict = None,\n                            abroad: bool = False) -> List[str]:\n    resp = await async_req(url=m3u8, proxy_addr=proxy, headers=header, abroad=abroad)\n    play_url_list = []\n    for i in resp.split('\\n'):\n        if i.startswith('https://'):\n            play_url_list.append(i.strip())\n    if not play_url_list:\n        for i in resp.split('\\n'):\n            if i.strip().endswith('m3u8'):\n                play_url_list.append(i.strip())\n    bandwidth_pattern = re.compile(r'BANDWIDTH=(\\d+)')\n    bandwidth_list = bandwidth_pattern.findall(resp)\n    url_to_bandwidth = {url: int(bandwidth) for bandwidth, url in zip(bandwidth_list, play_url_list)}\n    play_url_list = sorted(play_url_list, key=lambda url: url_to_bandwidth[url], reverse=True)\n    return play_url_list\n\n\nasync def get_douyin_web_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None):\n    headers = {\n        'cookie': 'ttwid=1%7C2iDIYVmjzMcpZ20fcaFde0VghXAA3NaNXE_SLR68IyE%7C1761045455'\n                  '%7Cab35197d5cfb21df6cbb2fa7ef1c9262206b062c315b9d04da746d0b37dfbc7d',\n        'referer': 'https://live.douyin.com/335354047186',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) '\n                      'Chrome/116.0.5845.97 Safari/537.36 Core/1.116.567.400 QQBrowser/19.7.6764.400',\n    }\n    if cookies:\n        headers['cookie'] = cookies\n\n    try:\n        web_rid = url.split('?')[0].split('live.douyin.com/')[-1]\n        params = {\n            \"aid\": \"6383\",\n            \"app_name\": \"douyin_web\",\n            \"live_id\": \"1\",\n            \"device_platform\": \"web\",\n            \"language\": \"zh-CN\",\n            \"browser_language\": \"zh-CN\",\n            \"browser_platform\": \"Win32\",\n            \"browser_name\": \"Chrome\",\n            \"browser_version\": \"116.0.0.0\",\n            \"web_rid\": web_rid,\n            'msToken': '',\n        }\n\n        api = f'https://live.douyin.com/webcast/room/web/enter/?{urllib.parse.urlencode(params)}'\n        a_bogus = ab_sign(urllib.parse.urlparse(api).query, headers['user-agent'])\n        api += \"&a_bogus=\" + a_bogus\n        try:\n            json_str = await async_req(url=api, proxy_addr=proxy_addr, headers=headers)\n            if not json_str:\n                raise Exception(\"it triggered risk control\")\n            json_data = json.loads(json_str)['data']\n            if not json_data['data']:\n                raise Exception(f\"{url} VR live is not supported\")\n            room_data = json_data['data'][0]\n            room_data['anchor_name'] = json_data['user']['nickname']\n        except Exception as e:\n            raise Exception(f\"Douyin web data fetch error, because {e}.\")\n\n        if room_data['status'] == 2:\n            if 'stream_url' not in room_data:\n                raise RuntimeError(\n                    \"The live streaming type or gameplay is not supported on the computer side yet, please use the \"\n                    \"app to share the link for recording.\"\n                )\n            live_core_sdk_data = room_data['stream_url']['live_core_sdk_data']\n            pull_datas = room_data['stream_url']['pull_datas']\n            if live_core_sdk_data:\n                if pull_datas:\n                    key = list(pull_datas.keys())[0]\n                    json_str = pull_datas[key]['stream_data']\n                else:\n                    json_str = live_core_sdk_data['pull_data']['stream_data']\n                json_data = json.loads(json_str)\n                if 'origin' in json_data['data']:\n                    stream_data = live_core_sdk_data['pull_data']['stream_data']\n                    origin_data = json.loads(stream_data)['data']['origin']['main']\n                    sdk_params = json.loads(origin_data['sdk_params'])\n                    origin_hls_codec = sdk_params.get('VCodec') or ''\n\n                    origin_url_list = json_data['data']['origin']['main']\n                    origin_m3u8 = {'ORIGIN': origin_url_list[\"hls\"] + '&codec=' + origin_hls_codec}\n                    origin_flv = {'ORIGIN': origin_url_list[\"flv\"] + '&codec=' + origin_hls_codec}\n                    hls_pull_url_map = room_data['stream_url']['hls_pull_url_map']\n                    flv_pull_url = room_data['stream_url']['flv_pull_url']\n                    room_data['stream_url']['hls_pull_url_map'] = {**origin_m3u8, **hls_pull_url_map}\n                    room_data['stream_url']['flv_pull_url'] = {**origin_flv, **flv_pull_url}\n    except Exception as e:\n        print(f\"Error message: {e} Error line: {e.__traceback__.tb_lineno}\")\n        room_data = {'anchor_name': \"\"}\n    return room_data\n\n\n@trace_error_decorator\nasync def get_douyin_app_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\n                      'Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0',\n        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n        'Referer': 'https://live.douyin.com/',\n        'Cookie': 'ttwid=1%7CB1qls3GdnZhUov9o2NxOMxxYS2ff6OSvEWbv0ytbES4%7C1680522049%7C280d802d6d478e3e78d0c807f7c487e7ffec0ae4e5fdd6a0fe74c3c6af149511; my_rd=1; passport_csrf_token=3ab34460fa656183fccfb904b16ff742; passport_csrf_token_default=3ab34460fa656183fccfb904b16ff742; d_ticket=9f562383ac0547d0b561904513229d76c9c21; n_mh=hvnJEQ4Q5eiH74-84kTFUyv4VK8xtSrpRZG1AhCeFNI; store-region=cn-fj; store-region-src=uid; LOGIN_STATUS=1; __security_server_data_status=1; FORCE_LOGIN=%7B%22videoConsumedRemainSeconds%22%3A180%7D; pwa2=%223%7C0%7C3%7C0%22; download_guide=%223%2F20230729%2F0%22; volume_info=%7B%22isUserMute%22%3Afalse%2C%22isMute%22%3Afalse%2C%22volume%22%3A0.6%7D; strategyABtestKey=%221690824679.923%22; stream_recommend_feed_params=%22%7B%5C%22cookie_enabled%5C%22%3Atrue%2C%5C%22screen_width%5C%22%3A1536%2C%5C%22screen_height%5C%22%3A864%2C%5C%22browser_online%5C%22%3Atrue%2C%5C%22cpu_core_num%5C%22%3A8%2C%5C%22device_memory%5C%22%3A8%2C%5C%22downlink%5C%22%3A10%2C%5C%22effective_type%5C%22%3A%5C%224g%5C%22%2C%5C%22round_trip_time%5C%22%3A150%7D%22; VIDEO_FILTER_MEMO_SELECT=%7B%22expireTime%22%3A1691443863751%2C%22type%22%3Anull%7D; home_can_add_dy_2_desktop=%221%22; __live_version__=%221.1.1.2169%22; device_web_cpu_core=8; device_web_memory_size=8; xgplayer_user_id=346045893336; csrf_session_id=2e00356b5cd8544d17a0e66484946f28; odin_tt=724eb4dd23bc6ffaed9a1571ac4c757ef597768a70c75fef695b95845b7ffcd8b1524278c2ac31c2587996d058e03414595f0a4e856c53bd0d5e5f56dc6d82e24004dc77773e6b83ced6f80f1bb70627; __ac_nonce=064caded4009deafd8b89; __ac_signature=_02B4Z6wo00f01HLUuwwAAIDBh6tRkVLvBQBy9L-AAHiHf7; ttcid=2e9619ebbb8449eaa3d5a42d8ce88ec835; webcast_leading_last_show_time=1691016922379; webcast_leading_total_show_times=1; webcast_local_quality=sd; live_can_add_dy_2_desktop=%221%22; msToken=1JDHnVPw_9yTvzIrwb7cQj8dCMNOoesXbA_IooV8cezcOdpe4pzusZE7NB7tZn9TBXPr0ylxmv-KMs5rqbNUBHP4P7VBFUu0ZAht_BEylqrLpzgt3y5ne_38hXDOX8o=; msToken=jV_yeN1IQKUd9PlNtpL7k5vthGKcHo0dEh_QPUQhr8G3cuYv-Jbb4NnIxGDmhVOkZOCSihNpA2kvYtHiTW25XNNX_yrsv5FN8O6zm3qmCIXcEe0LywLn7oBO2gITEeg=; tt_scid=mYfqpfbDjqXrIGJuQ7q-DlQJfUSG51qG.KUdzztuGP83OjuVLXnQHjsz-BRHRJu4e986'\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    async def get_app_data(room_id: str, sec_uid: str) -> dict:\n        app_params = {\n            \"verifyFp\": \"verify_hwj52020_7szNlAB7_pxNY_48Vh_ALKF_GA1Uf3yteoOY\",\n            \"type_id\": \"0\",\n            \"live_id\": \"1\",\n            \"room_id\": room_id,\n            \"sec_user_id\": sec_uid,\n            \"version_code\": \"99.99.99\",\n            \"app_id\": \"1128\"\n        }\n        api2 = f'https://webcast.amemv.com/webcast/room/reflow/info/?{urllib.parse.urlencode(app_params)}'\n        a_bogus = ab_sign(urllib.parse.urlparse(api2).query, headers['User-Agent'])\n        api2 += \"&a_bogus=\" + a_bogus\n        try:\n            json_str2 = await async_req(url=api2, proxy_addr=proxy_addr, headers=headers)\n            if not json_str2:\n                raise Exception(\"it triggered risk control\")\n            json_data2 = json.loads(json_str2)['data']\n            if not json_data2.get('room'):\n                raise Exception(f\"{url} VR live is not supported\")\n            room_data2 = json_data2['room']\n            room_data2['anchor_name'] = room_data2['owner']['nickname']\n            return room_data2\n        except Exception as e:\n            raise Exception(f\"Douyin app data fetch error, because {e}.\")\n\n    try:\n        web_rid = url.split('?')[0].split('live.douyin.com/')\n        if len(web_rid) > 1:\n            return await get_douyin_web_stream_data(url, proxy_addr, cookies)\n        else:\n            try:\n                data = await get_sec_user_id(url, proxy_addr=proxy_addr)\n                _room_id, _sec_uid = data\n                room_data = await get_app_data(_room_id, _sec_uid)\n            except UnsupportedUrlError:\n                unique_id = await get_unique_id(url, proxy_addr=proxy_addr)\n                return await get_douyin_stream_data(f'https://live.douyin.com/{unique_id}')\n\n        if room_data['status'] == 2:\n            if 'stream_url' not in room_data:\n                raise RuntimeError(\n                    \"The live streaming type or gameplay is not supported on the computer side yet, please use the \"\n                    \"app to share the link for recording.\"\n                )\n            live_core_sdk_data = room_data['stream_url']['live_core_sdk_data']\n            pull_datas = room_data['stream_url']['pull_datas']\n            if live_core_sdk_data:\n                if pull_datas:\n                    key = list(pull_datas.keys())[0]\n                    json_str = pull_datas[key]['stream_data']\n                else:\n                    json_str = live_core_sdk_data['pull_data']['stream_data']\n                json_data = json.loads(json_str)\n                if 'origin' in json_data['data']:\n                    stream_data = live_core_sdk_data['pull_data']['stream_data']\n                    origin_data = json.loads(stream_data)['data']['origin']['main']\n                    sdk_params = json.loads(origin_data['sdk_params'])\n                    origin_hls_codec = sdk_params.get('VCodec') or ''\n\n                    origin_url_list = json_data['data']['origin']['main']\n                    origin_m3u8 = {'ORIGIN': origin_url_list[\"hls\"] + '&codec=' + origin_hls_codec}\n                    origin_flv = {'ORIGIN': origin_url_list[\"flv\"] + '&codec=' + origin_hls_codec}\n                    hls_pull_url_map = room_data['stream_url']['hls_pull_url_map']\n                    flv_pull_url = room_data['stream_url']['flv_pull_url']\n                    room_data['stream_url']['hls_pull_url_map'] = {**origin_m3u8, **hls_pull_url_map}\n                    room_data['stream_url']['flv_pull_url'] = {**origin_flv, **flv_pull_url}\n    except Exception as e:\n        print(f\"Error message: {e} Error line: {e.__traceback__.tb_lineno}\")\n        room_data = {'anchor_name': \"\"}\n    return room_data\n\n\n@trace_error_decorator\nasync def get_douyin_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',\n        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n        'Referer': 'https://live.douyin.com/',\n        'Cookie': 'ttwid=1%7CB1qls3GdnZhUov9o2NxOMxxYS2ff6OSvEWbv0ytbES4%7C1680522049%7C280d802d6d478e3e78d0c807f7c487e7ffec0ae4e5fdd6a0fe74c3c6af149511; my_rd=1; passport_csrf_token=3ab34460fa656183fccfb904b16ff742; passport_csrf_token_default=3ab34460fa656183fccfb904b16ff742; d_ticket=9f562383ac0547d0b561904513229d76c9c21; n_mh=hvnJEQ4Q5eiH74-84kTFUyv4VK8xtSrpRZG1AhCeFNI; store-region=cn-fj; store-region-src=uid; LOGIN_STATUS=1; __security_server_data_status=1; FORCE_LOGIN=%7B%22videoConsumedRemainSeconds%22%3A180%7D; pwa2=%223%7C0%7C3%7C0%22; download_guide=%223%2F20230729%2F0%22; volume_info=%7B%22isUserMute%22%3Afalse%2C%22isMute%22%3Afalse%2C%22volume%22%3A0.6%7D; strategyABtestKey=%221690824679.923%22; stream_recommend_feed_params=%22%7B%5C%22cookie_enabled%5C%22%3Atrue%2C%5C%22screen_width%5C%22%3A1536%2C%5C%22screen_height%5C%22%3A864%2C%5C%22browser_online%5C%22%3Atrue%2C%5C%22cpu_core_num%5C%22%3A8%2C%5C%22device_memory%5C%22%3A8%2C%5C%22downlink%5C%22%3A10%2C%5C%22effective_type%5C%22%3A%5C%224g%5C%22%2C%5C%22round_trip_time%5C%22%3A150%7D%22; VIDEO_FILTER_MEMO_SELECT=%7B%22expireTime%22%3A1691443863751%2C%22type%22%3Anull%7D; home_can_add_dy_2_desktop=%221%22; __live_version__=%221.1.1.2169%22; device_web_cpu_core=8; device_web_memory_size=8; xgplayer_user_id=346045893336; csrf_session_id=2e00356b5cd8544d17a0e66484946f28; odin_tt=724eb4dd23bc6ffaed9a1571ac4c757ef597768a70c75fef695b95845b7ffcd8b1524278c2ac31c2587996d058e03414595f0a4e856c53bd0d5e5f56dc6d82e24004dc77773e6b83ced6f80f1bb70627; __ac_nonce=064caded4009deafd8b89; __ac_signature=_02B4Z6wo00f01HLUuwwAAIDBh6tRkVLvBQBy9L-AAHiHf7; ttcid=2e9619ebbb8449eaa3d5a42d8ce88ec835; webcast_leading_last_show_time=1691016922379; webcast_leading_total_show_times=1; webcast_local_quality=sd; live_can_add_dy_2_desktop=%221%22; msToken=1JDHnVPw_9yTvzIrwb7cQj8dCMNOoesXbA_IooV8cezcOdpe4pzusZE7NB7tZn9TBXPr0ylxmv-KMs5rqbNUBHP4P7VBFUu0ZAht_BEylqrLpzgt3y5ne_38hXDOX8o=; msToken=jV_yeN1IQKUd9PlNtpL7k5vthGKcHo0dEh_QPUQhr8G3cuYv-Jbb4NnIxGDmhVOkZOCSihNpA2kvYtHiTW25XNNX_yrsv5FN8O6zm3qmCIXcEe0LywLn7oBO2gITEeg=; tt_scid=mYfqpfbDjqXrIGJuQ7q-DlQJfUSG51qG.KUdzztuGP83OjuVLXnQHjsz-BRHRJu4e986'\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    try:\n        origin_url_list = None\n        html_str = await async_req(url=url, proxy_addr=proxy_addr, headers=headers)\n        match_json_str = re.search(r'(\\{\\\\\"state\\\\\":.*?)]\\\\n\"]\\)', html_str)\n        if not match_json_str:\n            match_json_str = re.search(r'(\\{\\\\\"common\\\\\":.*?)]\\\\n\"]\\)</script><div hidden', html_str)\n        json_str = match_json_str.group(1)\n        cleaned_string = json_str.replace('\\\\', '').replace(r'u0026', r'&')\n        room_store = re.search('\"roomStore\":(.*?),\"linkmicStore\"', cleaned_string, re.DOTALL).group(1)\n        anchor_name = re.search('\"nickname\":\"(.*?)\",\"avatar_thumb', room_store, re.DOTALL).group(1)\n        room_store = room_store.split(',\"has_commerce_goods\"')[0] + '}}}'\n        json_data = json.loads(room_store)['roomInfo']['room']\n        json_data['anchor_name'] = anchor_name\n        if 'status' in json_data and json_data['status'] == 4:\n            return json_data\n        stream_orientation = json_data['stream_url']['stream_orientation']\n        match_json_str2 = re.findall(r'\"(\\{\\\\\"common\\\\\":.*?)\"]\\)</script><script nonce=', html_str)\n        if match_json_str2:\n            json_str = match_json_str2[0] if stream_orientation == 1 else match_json_str2[1]\n            json_data2 = json.loads(\n                json_str.replace('\\\\', '').replace('\"{', '{').replace('}\"', '}').replace('u0026', '&'))\n            if 'origin' in json_data2['data']:\n                origin_url_list = json_data2['data']['origin']['main']\n\n        else:\n            html_str = html_str.replace('\\\\', '').replace('u0026', '&')\n            match_json_str3 = re.search('\"origin\":\\\\{\"main\":(.*?),\"dash\"', html_str, re.DOTALL)\n            if match_json_str3:\n                origin_url_list = json.loads(match_json_str3.group(1) + '}')\n\n        if origin_url_list:\n            origin_hls_codec = origin_url_list['sdk_params'].get('VCodec') or ''\n            origin_m3u8 = {'ORIGIN': origin_url_list[\"hls\"] + '&codec=' + origin_hls_codec}\n            origin_flv = {'ORIGIN': origin_url_list[\"flv\"] + '&codec=' + origin_hls_codec}\n            hls_pull_url_map = json_data['stream_url']['hls_pull_url_map']\n            flv_pull_url = json_data['stream_url']['flv_pull_url']\n            json_data['stream_url']['hls_pull_url_map'] = {**origin_m3u8, **hls_pull_url_map}\n            json_data['stream_url']['flv_pull_url'] = {**origin_flv, **flv_pull_url}\n        return json_data\n\n    except Exception as e:\n        print(f\"First data retrieval failed: {url} Preparing to switch parsing methods due to {e}\")\n        return await get_douyin_app_stream_data(url=url, proxy_addr=proxy_addr, cookies=cookies)\n\n\n@trace_error_decorator\nasync def get_tiktok_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict | None:\n    headers = {\n        'referer': 'https://www.tiktok.com/',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\n                      'Chrome/141.0.0.0 Safari/537.36',\n        'cookie': cookies or '1%7Cz7FKki38aKyy7i-BC9rEDwcrVvjcLcFEL6QIeqldoy4%7C1761302831%7C6c1461e9f1f980cbe0404c5190'\n                             '5177d5d53bbd822e1bf66128887d942c9c3e2f'\n    }\n\n    for i in range(3):\n        html_str = await async_req(url=url, proxy_addr=proxy_addr, headers=headers, abroad=True, http2=False)\n        time.sleep(1)\n        if \"We regret to inform you that we have discontinued operating TikTok\" in html_str:\n            msg = re.search('<p>\\n\\\\s+(We regret to inform you that we have discontinu.*?)\\\\.\\n\\\\s+</p>', html_str)\n            raise ConnectionError(\n                \"Your proxy node's regional network is blocked from accessing TikTok; please switch to a node in \"\n                f\"another region to access. {msg.group(1) if msg else ''}\"\n            )\n        if 'UNEXPECTED_EOF_WHILE_READING' not in html_str:\n            try:\n                json_str = re.findall(\n                    '<script id=\"SIGI_STATE\" type=\"application/json\">(.*?)</script>',\n                    html_str, re.DOTALL)[0]\n            except Exception:\n                raise ConnectionError(\"Please check if your network can access the TikTok website normally\")\n            json_data = json.loads(json_str)\n            return json_data\n\n\n@trace_error_decorator\nasync def get_kuaishou_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',\n        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n    try:\n        html_str = await async_req(url=url, proxy_addr=proxy_addr, headers=headers)\n    except Exception as e:\n        print(f\"Failed to fetch data from {url}.{e}\")\n        return {\"type\": 1, \"is_live\": False}\n\n    try:\n        json_str = re.search('<script>window.__INITIAL_STATE__=(.*?);\\\\(function\\\\(\\\\)\\\\{var s;', html_str).group(1)\n        play_list = re.findall('(\\\\{\"liveStream\".*?),\"gameInfo', json_str)[0] + \"}\"\n        play_list = json.loads(play_list)\n    except (AttributeError, IndexError, json.JSONDecodeError) as e:\n        print(f\"Failed to parse JSON data from {url}. Error: {e}\")\n        return {\"type\": 1, \"is_live\": False}\n\n    result = {\"type\": 2, \"is_live\": False}\n\n    if 'errorType' in play_list or 'liveStream' not in play_list:\n        error_msg = play_list['errorType']['title'] + play_list['errorType']['content']\n        print(f\"Failed URL: {url} Error message: {error_msg}\")\n        return result\n\n    if not play_list.get('liveStream'):\n        print(\"IP banned. Please change device or network.\")\n        return result\n\n    anchor_name = play_list['author'].get('name', '')\n    result.update({\"anchor_name\": anchor_name})\n\n    if play_list['liveStream'].get(\"playUrls\"):\n        if 'h264' in play_list['liveStream']['playUrls']:\n            if 'adaptationSet' not in play_list['liveStream']['playUrls']['h264']:\n                return result\n            play_url_list = play_list['liveStream']['playUrls']['h264']['adaptationSet']['representation']\n        else:\n            # TODO: Old version which not working at 20241128, could be removed if not working confirmed\n            play_url_list = play_list['liveStream']['playUrls'][0]['adaptationSet']['representation']\n        result.update({\"flv_url_list\": play_url_list, \"is_live\": True})\n\n    return result\n\n\n@trace_error_decorator\nasync def get_kuaishou_stream_data2(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict | None:\n    headers = {\n        'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n        'Referer': \"https://www.kuaishou.com/short-video/3x224rwabjmuc9y?fid=1712760877&cc=share_copylink&followRefer=151&shareMethod=TOKEN&docId=9&kpn=KUAISHOU&subBiz=BROWSE_SLIDE_PHOTO&photoId=3x224rwabjmuc9y&shareId=17144298796566&shareToken=X-6FTMeYTsY97qYL&shareResourceType=PHOTO_OTHER&userId=3xtnuitaz2982eg&shareType=1&et=1_i/2000048330179867715_h3052&shareMode=APP&originShareId=17144298796566&appType=21&shareObjectId=5230086626478274600&shareUrlOpened=0&timestamp=1663833792288&utm_source=app_share&utm_medium=app_share&utm_campaign=app_share&location=app_share\",\n        'content-type': 'application/json',\n        'Cookie': 'did=web_e988652e11b545469633396abe85a89f; didv=1796004001000',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n    try:\n        eid = url.split('/u/')[1].strip()\n        data = {\"source\": 5, \"eid\": eid, \"shareMethod\": \"card\", \"clientType\": \"WEB_OUTSIDE_SHARE_H5\"}\n        app_api = 'https://livev.m.chenzhongtech.com/rest/k/live/byUser?kpn=GAME_ZONE&captchaToken='\n        json_str = await async_req(url=app_api, proxy_addr=proxy_addr, headers=headers, data=data)\n        json_data = json.loads(json_str)\n        live_stream = json_data['liveStream']\n        anchor_name = live_stream['user']['user_name']\n        result = {\n            \"type\": 2,\n            \"anchor_name\": anchor_name,\n            \"is_live\": False,\n        }\n        live_status = live_stream['living']\n        if live_status:\n            result['is_live'] = True\n            backup_m3u8_url = live_stream['hlsPlayUrl']\n            backup_flv_url = live_stream['playUrls'][0]['url']\n            if 'multiResolutionHlsPlayUrls' in live_stream:\n                m3u8_url_list = live_stream['multiResolutionHlsPlayUrls'][0]['urls']\n                result['m3u8_url_list'] = m3u8_url_list\n            if 'multiResolutionPlayUrls' in live_stream:\n                flv_url_list = live_stream['multiResolutionPlayUrls'][0]['urls']\n                result['flv_url_list'] = flv_url_list\n            result['backup'] = {'m3u8_url': backup_m3u8_url, 'flv_url': backup_flv_url}\n        if result['anchor_name']:\n            return result\n    except Exception as e:\n        print(f\"{e}, Failed URL: {url}, preparing to switch to a backup plan for re-parsing.\")\n    return await get_kuaishou_stream_data(url, cookies=cookies, proxy_addr=proxy_addr)\n\n\n@trace_error_decorator\nasync def get_huya_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:123.0) Gecko/20100101 Firefox/123.0',\n        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',\n        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n        'Cookie': 'huya_ua=webh5&0.1.0&websocket; game_did=zXyXVqV1NF4ZeNWg7QaOFbpIEWqcsrxkoVy; alphaValue=0.80; isInLiveRoom=; guid=0a7df378828609654d01a205a305fb52; __yamid_tt1=0.8936157401010706; __yamid_new=CA715E8BC9400001E5A313E028F618DE; udb_guiddata=4657813d32ce43d381ea8ff8d416a3c2; udb_deviceid=w_756598227007868928; sdid=0UnHUgv0_qmfD4KAKlwzhqQB32nywGZJYLZl_9RLv0Lbi5CGYYNiBGLrvNZVszz4FEo_unffNsxk9BdvXKO_PkvC5cOwCJ13goOiNYGClLirWVkn9LtfFJw_Qo4kgKr8OZHDqNnuwg612sGyflFn1draukOt03gk2m3pwGbiKsB143MJhMxcI458jIjiX0MYq; Hm_lvt_51700b6c722f5bb4cf39906a596ea41f=1708583696; SoundValue=0.50; sdidtest=0UnHUgv0_qmfD4KAKlwzhqQB32nywGZJYLZl_9RLv0Lbi5CGYYNiBGLrvNZVszz4FEo_unffNsxk9BdvXKO_PkvC5cOwCJ13goOiNYGClLirWVkn9LtfFJw_Qo4kgKr8OZHDqNnuwg612sGyflFn1draukOt03gk2m3pwGbiKsB143MJhMxcI458jIjiX0MYq; sdidshorttest=test; __yasmid=0.8936157401010706; _yasids=__rootsid^%^3DCAA3838C53600001F4EE863017406250; huyawap_rep_cnt=4; udb_passdata=3; huya_web_rep_cnt=89; huya_flash_rep_cnt=20; Hm_lpvt_51700b6c722f5bb4cf39906a596ea41f=1709548534; _rep_cnt=3; PHPSESSID=r0klm0vccf08q1das65bnd8co1; guid=0a7df378828609654d01a205a305fb52; huya_hd_rep_cnt=8',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    html_str = await async_req(url=url, proxy_addr=proxy_addr, headers=headers)\n    json_str = re.findall('stream: (\\\\{\"data\".*?),\"iWebDefaultBitRate\"', html_str)[0]\n    json_data = json.loads(json_str + '}')\n    return json_data\n\n\n@trace_error_decorator\nasync def get_huya_app_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n        'xweb_xhr': '1',\n        'referer': 'https://servicewechat.com/wx74767bf0b684f7d3/301/page-frame.html',\n        'accept-language': 'zh-CN,zh;q=0.9',\n    }\n\n    if cookies:\n        headers['Cookie'] = cookies\n    room_id = url.split('?')[0].rsplit('/', maxsplit=1)[-1]\n\n    if any(char.isalpha() for char in room_id):\n        html_str = await async_req(url, proxy_addr=proxy_addr, headers=headers)\n        room_id = re.search('ProfileRoom\":(.*?),\"sPrivateHost', html_str)\n        if room_id:\n            room_id = room_id.group(1)\n        else:\n            raise Exception('Please use \"https://www.huya.com/+room_number\" for recording')\n\n    params = {\n        'm': 'Live',\n        'do': 'profileRoom',\n        'roomid': room_id,\n        'showSecret': '1',\n    }\n    wx_app_api = f'https://mp.huya.com/cache.php?{urllib.parse.urlencode(params)}'\n    json_str = await async_req(url=wx_app_api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    anchor_name = json_data['data']['profileInfo']['nick']\n    live_status = json_data['data']['realLiveStatus']\n    live_title = json_data['data']['liveData']['introduction']\n    if live_status != 'ON':\n        return {'anchor_name': anchor_name, 'is_live': False}\n    else:\n        base_steam_info_list = json_data['data']['stream']['baseSteamInfoList']\n        play_url_list = []\n        for i in base_steam_info_list:\n            cdn_type = i['sCdnType']\n            stream_name = i['sStreamName']\n            s_flv_url = i['sFlvUrl']\n            flv_anti_code = i['sFlvAntiCode']\n            s_hls_url = i['sHlsUrl']\n            hls_anti_code = i['sHlsAntiCode']\n            m3u8_url = f'{s_hls_url}/{stream_name}.m3u8?{hls_anti_code}'\n            flv_url = f'{s_flv_url}/{stream_name}.flv?{flv_anti_code}'\n            play_url_list.append(\n                {\n                    'cdn_type': cdn_type,\n                    'm3u8_url': m3u8_url,\n                    'flv_url': flv_url,\n                }\n            )\n        #print(json.dumps(play_url_list, indent=4, ensure_ascii=False))\n        # flv_url = 'https://' + play_url_list[0]['flv_url'].split('://')[1]\n        # record_url = flv_url\n\n        # 设定优先级，优先选择 TX,2025/03/14时AL不可用\n        priority_order = [\"TX\", \"HW\", \"HS\", \"AL\"]\n\n        # 查找优先的 flv_url\n        selected_flv_url = None\n        selected_cdn_type = None\n\n        for cdn in priority_order:\n            for item in play_url_list:\n                if item[\"cdn_type\"] == cdn:\n                    selected_flv_url = item[\"flv_url\"]\n                    selected_cdn_type = cdn\n                    break\n            if selected_flv_url:\n                break\n\n        # 处理 flv_url，确保使用 https\n        if selected_flv_url:\n            flv_url = 'https://' + selected_flv_url.split('://')[1]\n\n            # 如果选择的是 TX，执行额外的字符串替换\n            if selected_cdn_type == \"TX\":\n                flv_url = flv_url.replace(\"&ctype=tars_mp\", \"&ctype=huya_webh5\").replace(\"&fs=bhct\", \"&fs=bgct\")\n\n            record_url = flv_url\n        else:\n            record_url = None\n\n        return {\n            'anchor_name': anchor_name,\n            'is_live': True,\n            'm3u8_url': play_url_list[0]['m3u8_url'],\n            'flv_url': play_url_list[0]['flv_url'],\n            'record_url': record_url,\n            'title': live_title\n        }\n\n\ndef md5(data) -> str:\n    return hashlib.md5(data.encode('utf-8')).hexdigest()\n\n\nasync def get_token_js(rid: str, did: str, proxy_addr: OptionalStr = None) -> List[str]:\n\n    url = f'https://www.douyu.com/{rid}'\n    html_str = await async_req(url=url, proxy_addr=proxy_addr)\n    result = re.search(r'(vdwdae325w_64we[\\s\\S]*function ub98484234[\\s\\S]*?)function', html_str).group(1)\n    func_ub9 = re.sub(r'eval.*?;}', 'strc;}', result)\n    js = execjs.compile(func_ub9)\n    res = js.call('ub98484234')\n\n    t10 = str(int(time.time()))\n    v = re.search(r'v=(\\d+)', res).group(1)\n    rb = md5(rid + did + t10 + v)\n\n    func_sign = re.sub(r'return rt;}\\);?', 'return rt;}', res)\n    func_sign = func_sign.replace('(function (', 'function sign(')\n    func_sign = func_sign.replace('CryptoJS.MD5(cb).toString()', '\"' + rb + '\"')\n\n    js = execjs.compile(func_sign)\n    params = js.call('sign', rid, did, t10)\n    params_list = re.findall('=(.*?)(?=&|$)', params)\n    return params_list\n\n\n@trace_error_decorator\nasync def get_douyu_info_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n        'Referer': 'https://m.douyu.com/3125893?rid=3125893&dyshid=0-96003918aa5365bc6dcb4933000316p1&dyshci=181',\n        'Cookie': 'dy_did=413b835d2ae00270f0c69f6400031601; acf_did=413b835d2ae00270f0c69f6400031601; Hm_lvt_e99aee90ec1b2106afe7ec3b199020a7=1692068308,1694003758; m_did=96003918aa5365bc6dcb4933000316p1; dy_teen_mode=%7B%22uid%22%3A%22472647365%22%2C%22status%22%3A0%2C%22birthday%22%3A%22%22%2C%22password%22%3A%22%22%7D; PHPSESSID=td59qi2fu2gepngb8mlehbeme3; acf_auth=94fc9s%2FeNj%2BKlpU%2Br8tZC3Jo9sZ0wz9ClcHQ1akL2Nhb6ZyCmfjVWSlR3LFFPuePWHRAMo0dt9vPSCoezkFPOeNy4mYcdVOM1a8CbW0ZAee4ipyNB%2Bflr58; dy_auth=bec5yzM8bUFYe%2FnVAjmUAljyrsX%2FcwRW%2FyMHaoArYb5qi8FS9tWR%2B96iCzSnmAryLOjB3Qbeu%2BBD42clnI7CR9vNAo9mva5HyyL41HGsbksx1tEYFOEwxSI; wan_auth37wan=5fd69ed5b27fGM%2FGoswWwDo%2BL%2FRMtnEa4Ix9a%2FsH26qF0sR4iddKMqfnPIhgfHZUqkAk%2FA1d8TX%2B6F7SNp7l6buIxAVf3t9YxmSso8bvHY0%2Fa6RUiv8; acf_uid=472647365; acf_username=472647365; acf_nickname=%E7%94%A8%E6%88%B776576662; acf_own_room=0; acf_groupid=1; acf_phonestatus=1; acf_avatar=https%3A%2F%2Fapic.douyucdn.cn%2Fupload%2Favatar%2Fdefault%2F24_; acf_ct=0; acf_ltkid=25305099; acf_biz=1; acf_stk=90754f8ed18f0c24; Hm_lpvt_e99aee90ec1b2106afe7ec3b199020a7=1694003778'\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    match_rid = re.search('rid=(.*?)(?=&|$)', url)\n    if match_rid:\n        rid = match_rid.group(1)\n    else:\n        rid = re.search('douyu.com/(.*?)(?=\\\\?|$)', url).group(1)\n        html_str = await async_req(url=f'https://m.douyu.com/{rid}', proxy_addr=proxy_addr, headers=headers)\n        json_str = re.findall('<script id=\"vike_pageContext\" type=\"application/json\">(.*?)</script>', html_str)[0]\n        json_data = json.loads(json_str)\n        rid = json_data['pageProps']['room']['roomInfo']['roomInfo']['rid']\n\n    headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0'\n    url2 = f'https://www.douyu.com/betard/{rid}'\n    json_str = await async_req(url=url2, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    result = {\n        \"anchor_name\": json_data['room']['nickname'],\n        \"is_live\": False\n    }\n    if json_data['room']['videoLoop'] == 0 and json_data['room']['show_status'] == 1:\n        result[\"title\"] = json_data['room']['room_name'].replace('&nbsp;', '')\n        result[\"is_live\"] = True\n        result[\"room_id\"] = json_data['room']['room_id']\n    return result\n\n\n@trace_error_decorator\nasync def get_douyu_stream_data(rid: str, rate: str = '-1', proxy_addr: OptionalStr = None,\n                          cookies: OptionalStr = None) -> dict:\n    did = '10000000000000000000000000003306'\n    params_list = await get_token_js(rid, did, proxy_addr=proxy_addr)\n    headers = {\n        'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n        'Referer': 'https://m.douyu.com/3125893?rid=3125893&dyshid=0-96003918aa5365bc6dcb4933000316p1&dyshci=181',\n        'Cookie': 'dy_did=413b835d2ae00270f0c69f6400031601; acf_did=413b835d2ae00270f0c69f6400031601; Hm_lvt_e99aee90ec1b2106afe7ec3b199020a7=1692068308,1694003758; m_did=96003918aa5365bc6dcb4933000316p1; dy_teen_mode=%7B%22uid%22%3A%22472647365%22%2C%22status%22%3A0%2C%22birthday%22%3A%22%22%2C%22password%22%3A%22%22%7D; PHPSESSID=td59qi2fu2gepngb8mlehbeme3; acf_auth=94fc9s%2FeNj%2BKlpU%2Br8tZC3Jo9sZ0wz9ClcHQ1akL2Nhb6ZyCmfjVWSlR3LFFPuePWHRAMo0dt9vPSCoezkFPOeNy4mYcdVOM1a8CbW0ZAee4ipyNB%2Bflr58; dy_auth=bec5yzM8bUFYe%2FnVAjmUAljyrsX%2FcwRW%2FyMHaoArYb5qi8FS9tWR%2B96iCzSnmAryLOjB3Qbeu%2BBD42clnI7CR9vNAo9mva5HyyL41HGsbksx1tEYFOEwxSI; wan_auth37wan=5fd69ed5b27fGM%2FGoswWwDo%2BL%2FRMtnEa4Ix9a%2FsH26qF0sR4iddKMqfnPIhgfHZUqkAk%2FA1d8TX%2B6F7SNp7l6buIxAVf3t9YxmSso8bvHY0%2Fa6RUiv8; acf_uid=472647365; acf_username=472647365; acf_nickname=%E7%94%A8%E6%88%B776576662; acf_own_room=0; acf_groupid=1; acf_phonestatus=1; acf_avatar=https%3A%2F%2Fapic.douyucdn.cn%2Fupload%2Favatar%2Fdefault%2F24_; acf_ct=0; acf_ltkid=25305099; acf_biz=1; acf_stk=90754f8ed18f0c24; Hm_lpvt_e99aee90ec1b2106afe7ec3b199020a7=1694003778'\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    data = {\n        'v': params_list[0],\n        'did': params_list[1],\n        'tt': params_list[2],\n        'sign': params_list[3],  # 10分钟有效期\n        'ver': '22011191',\n        'rid': rid,\n        'rate': rate,  # 0蓝光、3超清、2高清、-1默认\n    }\n\n    # app_api = 'https://m.douyu.com/hgapi/livenc/room/getStreamUrl'\n    app_api = f'https://www.douyu.com/lapi/live/getH5Play/{rid}'\n    json_str = await async_req(url=app_api, proxy_addr=proxy_addr, headers=headers, data=data)\n    json_data = json.loads(json_str)\n    return json_data\n\n\n@trace_error_decorator\nasync def get_yy_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0',\n        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n        'Referer': 'https://www.yy.com/',\n        'Cookie': 'hd_newui=0.2103068903976506; hdjs_session_id=0.4929014850884579; hdjs_session_time=1694004002636; hiido_ui=0.923076230899782'\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    html_str = await async_req(url=url, proxy_addr=proxy_addr, headers=headers)\n    anchor_name = re.search('nick: \"(.*?)\",\\n\\\\s+logo', html_str).group(1)\n    cid = re.search('sid : \"(.*?)\",\\n\\\\s+ssid', html_str, re.DOTALL).group(1)\n\n    data = '{\"head\":{\"seq\":1701869217590,\"appidstr\":\"0\",\"bidstr\":\"121\",\"cidstr\":\"' + cid + '\",\"sidstr\":\"' + cid + '\",\"uid64\":0,\"client_type\":108,\"client_ver\":\"5.17.0\",\"stream_sys_ver\":1,\"app\":\"yylive_web\",\"playersdk_ver\":\"5.17.0\",\"thundersdk_ver\":\"0\",\"streamsdk_ver\":\"5.17.0\"},\"client_attribute\":{\"client\":\"web\",\"model\":\"web0\",\"cpu\":\"\",\"graphics_card\":\"\",\"os\":\"chrome\",\"osversion\":\"0\",\"vsdk_version\":\"\",\"app_identify\":\"\",\"app_version\":\"\",\"business\":\"\",\"width\":\"1920\",\"height\":\"1080\",\"scale\":\"\",\"client_type\":8,\"h265\":0},\"avp_parameter\":{\"version\":1,\"client_type\":8,\"service_type\":0,\"imsi\":0,\"send_time\":1701869217,\"line_seq\":-1,\"gear\":4,\"ssl\":1,\"stream_format\":0}}'\n    data_bytes = data.encode('utf-8')\n    params = {\n        \"uid\": \"0\",\n        \"cid\": cid,\n        \"sid\": cid,\n        \"appid\": \"0\",\n        \"sequence\": \"1701869217590\",\n        \"encode\": \"json\"\n    }\n    url2 = f'https://stream-manager.yy.com/v3/channel/streams?{urllib.parse.urlencode(params)}'\n    json_str = await async_req(url=url2, data=data_bytes, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    json_data['anchor_name'] = anchor_name\n\n    params = {\n        'uid': '',\n        'sid': cid,\n        'ssid': cid,\n        '_': int(time.time() * 1000),\n    }\n    detail_api = f'https://www.yy.com/live/detail?{urllib.parse.urlencode(params)}'\n    json_str2 = await async_req(detail_api, proxy_addr=proxy_addr, headers=headers)\n    json_data2 = json.loads(json_str2)\n    json_data['title'] = json_data2['data']['roomName']\n    return json_data\n\n\n@trace_error_decorator\nasync def get_bilibili_room_info_h5(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> str:\n    headers = {\n        'user-agent': 'Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) '\n                      'SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36',\n        'accept-language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n        'cookie': '',\n        'origin': 'https://live.bilibili.com',\n        'referer': 'https://live.bilibili.com/26066074',\n    }\n    if cookies:\n        headers['cookie'] = cookies\n\n    room_id = url.split('?')[0].rsplit('/', maxsplit=1)[1]\n    api = f'https://api.live.bilibili.com/xlive/web-room/v1/index/getH5InfoByRoom?room_id={room_id}'\n    json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers)\n    room_info = json.loads(json_str)\n    title = room_info['data']['room_info'].get('title') if room_info.get('data') else ''\n    return title\n\n\n@trace_error_decorator\nasync def get_bilibili_room_info(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0',\n        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',\n        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    try:\n        room_id = url.split('?')[0].rsplit('/', maxsplit=1)[1]\n        json_str = await async_req(f'https://api.live.bilibili.com/room/v1/Room/room_init?id={room_id}',\n                           proxy_addr=proxy_addr, headers=headers)\n        room_info = json.loads(json_str)\n        uid = room_info['data']['uid']\n        live_status = True if room_info['data']['live_status'] == 1 else False\n\n        api = f'https://api.live.bilibili.com/live_user/v1/Master/info?uid={uid}'\n        json_str2 = await async_req(url=api, proxy_addr=proxy_addr, headers=headers)\n        anchor_info = json.loads(json_str2)\n        anchor_name = anchor_info['data']['info']['uname']\n\n        title = await get_bilibili_room_info_h5(url, proxy_addr, cookies)\n        return {\"anchor_name\": anchor_name, \"live_status\": live_status, \"room_url\": url, \"title\": title}\n    except Exception as e:\n        print(e)\n        return {\"anchor_name\": '', \"live_status\": False, \"room_url\": url}\n\n\n@trace_error_decorator\nasync def get_bilibili_stream_data(url: str, qn: str = '10000', platform: str = 'web', proxy_addr: OptionalStr = None,\n                             cookies: OptionalStr = None) -> OptionalStr:\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0',\n        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n        'origin': 'https://live.bilibili.com',\n        'referer': 'https://live.bilibili.com/26066074',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    room_id = url.split('?')[0].rsplit('/', maxsplit=1)[1]\n    params = {\n        'cid': room_id,\n        'qn': qn,\n        'platform': platform,\n    }\n    play_api = f'https://api.live.bilibili.com/room/v1/Room/playUrl?{urllib.parse.urlencode(params)}'\n    json_str = await async_req(play_api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    if json_data and json_data['code'] == 0:\n        for i in json_data['data']['durl']:\n            if 'd1--cn-gotcha' in i['url']:\n                return i['url']\n        return json_data['data']['durl'][-1]['url']\n    else:\n        params = {\n            \"room_id\": room_id,\n            \"protocol\": \"0,1\",\n            \"format\": \"0,1,2\",\n            \"codec\": \"0,1,2\",\n            \"qn\": qn,\n            \"platform\": \"web\",\n            \"ptype\": \"8\",\n            \"dolby\": \"5\",\n            \"panorama\": \"1\",\n            \"hdr_type\": \"0,1\"\n        }\n\n        # 此接口因网页上有限制, 需要配置登录后的cookie才能获取最高画质\n        api = f'https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo?{urllib.parse.urlencode(params)}'\n        json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers)\n        json_data = json.loads(json_str)\n        if json_data['data']['live_status'] == 0:\n            print(\"The anchor did not start broadcasting.\")\n            return\n        playurl_info = json_data['data']['playurl_info']\n        format_list = playurl_info['playurl']['stream'][0]['format']\n        stream_data_list = format_list[0]['codec']\n        sorted_stream_list = sorted(stream_data_list, key=itemgetter(\"current_qn\"), reverse=True)\n        # qn: 30000=杜比 20000=4K 10000=原画 400=蓝光 250=超清 150=高清 80=流畅\n        video_quality_options = {'10000': 0, '400': 1, '250': 2, '150': 3, '80': 4}\n        qn_count = len(sorted_stream_list)\n        select_stream_index = min(video_quality_options[qn], qn_count - 1)\n        stream_data: dict = sorted_stream_list[select_stream_index]\n        base_url = stream_data['base_url']\n        host = stream_data['url_info'][0]['host']\n        extra = stream_data['url_info'][0]['extra']\n        m3u8_url = host + base_url + extra\n        return m3u8_url\n\n@trace_error_decorator\nasync def get_xhs_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n        'xy-common-params': 'platform=iOS&sid=session.1722166379345546829388',\n        'referer': 'https://app.xhs.cn/',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    if \"xhslink.com\" in url:\n        url = await async_req(url, proxy_addr=proxy_addr, headers=headers, redirect_url=True)\n\n    host_id = get_params(url, \"host_id\")\n    user_id = re.search(\"/user/profile/(.*?)(?=/|\\\\?|$)\", url)\n    user_id = user_id.group(1) if user_id else host_id\n    result = {\"anchor_name\": '', \"is_live\": False}\n    html_str = await async_req(url, proxy_addr=proxy_addr, headers=headers)\n    match_data = re.search(\"<script>window.__INITIAL_STATE__=(.*?)</script>\", html_str)\n\n    if match_data:\n        json_str = match_data.group(1).replace(\"undefined\", \"null\")\n        json_data = json.loads(json_str)\n\n        if json_data.get(\"liveStream\"):\n            stream_data = json_data[\"liveStream\"]\n            if stream_data.get(\"liveStatus\") == \"success\":\n                room_info = stream_data[\"roomData\"][\"roomInfo\"]\n                title = room_info.get(\"roomTitle\")\n                if title and \"回放\" not in title:\n                    live_link = room_info[\"deeplink\"]\n                    anchor_name = get_params(live_link, \"host_nickname\")\n                    flv_url = get_params(live_link, \"flvUrl\")\n                    room_id = flv_url.split('live/')[1].split('.')[0]\n                    flv_url = f\"http://live-source-play.xhscdn.com/live/{room_id}.flv\"\n                    m3u8_url = flv_url.replace('.flv', '.m3u8')\n                    result |= {\n                        \"anchor_name\": anchor_name,\n                        \"is_live\": True,\n                        \"title\": title,\n                        \"flv_url\": flv_url,\n                        \"m3u8_url\": m3u8_url,\n                        'record_url': flv_url\n                    }\n                    return result\n\n    profile_url = f\"https://www.xiaohongshu.com/user/profile/{user_id}\"\n    html_str = await async_req(profile_url, proxy_addr=proxy_addr, headers=headers)\n    anchor_name = re.search(\"<title>@(.*?) 的个人主页</title>\", html_str)\n    if anchor_name:\n        result[\"anchor_name\"] = anchor_name.group(1)\n\n    return result\n\n\n@trace_error_decorator\nasync def get_bigo_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0',\n        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',\n        'Referer': 'https://www.bigo.tv/',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    if 'bigo.tv' not in url:\n        html_str = await async_req(url, proxy_addr=proxy_addr, headers=headers)\n        web_url = re.search(\n            '<meta data-n-head=\"ssr\" data-hid=\"al:web:url\" property=\"al:web:url\" content=\"(.*?)\">',\n            html_str).group(1)\n        room_id = web_url.split('&amp;h=')[-1]\n    else:\n        if '&h=' in url:\n            room_id = url.split('&h=')[-1]\n        else:\n            room_id = url.split(\"?\")[0].rsplit(\"/\", maxsplit=1)[-1]\n\n    data = {'siteId': room_id}  # roomId\n    url2 = 'https://ta.bigo.tv/official_website/studio/getInternalStudioInfo'\n    json_str = await async_req(url=url2, proxy_addr=proxy_addr, headers=headers, data=data)\n    json_data = json.loads(json_str)\n    anchor_name = json_data['data']['nick_name']\n    live_status = json_data['data']['alive']\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n\n    if live_status == 1:\n        live_title = json_data['data']['roomTopic']\n        m3u8_url = json_data['data']['hls_src']\n        result['m3u8_url'] = m3u8_url\n        result['record_url'] = m3u8_url\n        result |= {\"title\": live_title, \"is_live\": True, \"m3u8_url\": m3u8_url, 'record_url': m3u8_url}\n    elif result['anchor_name'] == '':\n        html_str = await async_req(url=f'https://www.bigo.tv/{url.split(\"/\")[3]}/{room_id}',\n                                   proxy_addr=proxy_addr, headers=headers)\n        match_anchor_name = re.search('<title>欢迎来到(.*?)的直播间</title>', html_str, re.DOTALL)\n        if match_anchor_name:\n            anchor_name = match_anchor_name.group(1)\n        else:\n            match_anchor_name = re.search('<meta data-n-head=\"ssr\" data-hid=\"og:title\" property=\"og:title\" '\n                                          'content=\"(.*?) - BIGO LIVE\">', html_str, re.DOTALL)\n            anchor_name = match_anchor_name.group(1)\n        result['anchor_name'] = anchor_name\n\n    return result\n\n\n@trace_error_decorator\nasync def get_blued_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',\n        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    html_str = await async_req(url=url, proxy_addr=proxy_addr, headers=headers)\n    json_str = re.search('decodeURIComponent\\\\(\\\"(.*?)\\\"\\\\)\\\\),window\\\\.Promise', html_str, re.DOTALL).group(1)\n    json_str = urllib.parse.unquote(json_str)\n    json_data = json.loads(json_str)\n    anchor_name = json_data['userInfo']['name']\n    live_status = json_data['userInfo']['onLive']\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n\n    if live_status:\n        m3u8_url = json_data['liveInfo']['liveUrl']\n        result |= {\"is_live\": True, \"m3u8_url\": m3u8_url, 'record_url': m3u8_url}\n    return result\n\n\n@trace_error_decorator\nasync def login_sooplive(username: str, password: str, proxy_addr: OptionalStr = None) -> OptionalStr:\n    if len(username) < 6 or len(password) < 10:\n        raise RuntimeError(\"sooplive login failed! Please enter the correct account and password for the sooplive \"\n                           \"platform in the config.ini file.\")\n\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0',\n        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',\n        'Origin': 'https://play.sooplive.co.kr',\n        'Referer': 'https://play.sooplive.co.kr/superbsw123/277837074',\n    }\n\n    data = {\n        'szWork': 'login',\n        'szType': 'json',\n        'szUid': username,\n        'szPassword': password,\n        'isSaveId': 'true',\n        'isSavePw': 'true',\n        'isSaveJoin': 'true',\n        'isLoginRetain': 'Y',\n    }\n\n    url = 'https://login.sooplive.co.kr/app/LoginAction.php'\n\n    try:\n        cookie_dict = await async_req(url, proxy_addr=proxy_addr, headers=headers,\n                                      data=data, return_cookies=True, timeout=20)\n        cookie_str = '; '.join([f\"{k}={v}\" for k, v in cookie_dict.items()])\n        return cookie_str\n    except Exception as e:\n        print(f\"An error occurred during login: {e}\")\n        raise Exception(\n            \"sooplive login failed, please check if the account password in the configuration file is correct.\"\n        )\n\n\n@trace_error_decorator\nasync def get_sooplive_cdn_url(broad_no: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0',\n        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n        'Origin': 'https://play.sooplive.co.kr',\n        'Referer': 'https://play.sooplive.co.kr/oul282/249469582',\n        'Content-Type': 'application/x-www-form-urlencoded',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    params = {\n        'return_type': 'gcp_cdn',\n        'use_cors': 'false',\n        'cors_origin_url': 'play.sooplive.co.kr',\n        'broad_key': f'{broad_no}-common-master-hls',\n        'time': '8361.086329376785',\n    }\n\n    url2 = 'http://livestream-manager.sooplive.co.kr/broad_stream_assign.html?' + urllib.parse.urlencode(params)\n    json_str = await async_req(url=url2, proxy_addr=proxy_addr, headers=headers, abroad=True)\n    json_data = json.loads(json_str)\n\n    return json_data\n\n\n@trace_error_decorator\nasync def get_sooplive_tk(url: str, rtype: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> str | tuple:\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0',\n        'Origin': 'https://play.sooplive.co.kr',\n        'Referer': 'https://play.sooplive.co.kr/secretx/250989857',\n        'Content-Type': 'application/x-www-form-urlencoded',\n    }\n\n    if cookies:\n        headers['Cookie'] = cookies\n\n    split_url = url.split('/')\n    bj_id = split_url[3] if len(split_url) < 6 else split_url[5]\n    room_password = get_params(url, \"pwd\")\n    if not room_password:\n        room_password = ''\n    data = {\n        'bid': bj_id,\n        'bno': '',\n        'type': rtype,\n        'pwd': room_password,\n        'player_type': 'html5',\n        'stream_type': 'common',\n        'quality': 'master',\n        'mode': 'landing',\n        'from_api': '0',\n        'is_revive': 'false',\n    }\n\n    url2 = f'https://live.sooplive.co.kr/afreeca/player_live_api.php?bjid={bj_id}'\n    json_str = await async_req(url=url2, proxy_addr=proxy_addr, headers=headers, data=data, abroad=True)\n    json_data = json.loads(json_str)\n\n    if rtype == 'aid':\n        token = json_data[\"CHANNEL\"][\"AID\"]\n        return token\n    else:\n        bj_name = json_data['CHANNEL']['BJNICK']\n        bj_id = json_data['CHANNEL']['BJID']\n        return f\"{bj_name}-{bj_id}\", json_data['CHANNEL']['BNO']\n\n\ndef get_soop_headers(cookies):\n    headers = {\n        'client-id': str(uuid.uuid4()),\n        'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, '\n                      'like Gecko) Version/18.5 Mobile/15E148 Safari/604.1 Edg/141.0.0.0',\n    }\n    if cookies:\n        headers['cookie'] = cookies\n    return headers\n\n\nasync def _get_soop_channel_info_global(bj_id, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> str:\n    headers = get_soop_headers(cookies)\n    api = 'https://api.sooplive.com/v2/channel/info/' + str(bj_id)\n    json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    nickname = json_data['data']['streamerChannelInfo']['nickname']\n    channelId = json_data['data']['streamerChannelInfo']['channelId']\n    anchor_name = f\"{nickname}-{channelId}\"\n    return anchor_name\n\n\nasync def _get_soop_stream_info_global(bj_id, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> tuple:\n    headers = get_soop_headers(cookies)\n    api = 'https://api.sooplive.com/v2/stream/info/' + str(bj_id)\n    json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    status = json_data['data']['isStream']\n    title = json_data['data']['title']\n    return status, title\n\n\nasync def _fetch_web_stream_data_global(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    split_url = url.split('/')\n    bj_id = split_url[3] if len(split_url) < 6 else split_url[5]\n    anchor_name = await _get_soop_channel_info_global(bj_id)\n    result = {\"anchor_name\": anchor_name or '', \"is_live\": False, \"live_url\": url}\n    status, title = await _get_soop_stream_info_global(bj_id)\n    if not status:\n        return result\n    else:\n        async def _get_url_list(m3u8: str) -> list[str]:\n            headers = {\n                'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\n                              'Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0',\n            }\n            if cookies:\n                headers['cookie'] = cookies\n            resp = await async_req(url=m3u8, proxy_addr=proxy_addr, headers=headers)\n            play_url_list = []\n            url_prefix = '/'.join(m3u8.split('/')[0:3])\n            for i in resp.split('\\n'):\n                if not i.startswith('#') and i.strip():\n                    play_url_list.append(url_prefix + i.strip())\n            bandwidth_pattern = re.compile(r'BANDWIDTH=(\\d+)')\n            bandwidth_list = bandwidth_pattern.findall(resp)\n            url_to_bandwidth = {purl: int(bandwidth) for bandwidth, purl in zip(bandwidth_list, play_url_list)}\n            play_url_list = sorted(play_url_list, key=lambda purl: url_to_bandwidth[purl], reverse=True)\n            return play_url_list\n\n        m3u8_url = 'https://global-media.sooplive.com/live/' + str(bj_id) + '/master.m3u8'\n        result |= {\n            'is_live': True,\n            'title': title,\n            'm3u8_url': m3u8_url,\n            'play_url_list': await _get_url_list(m3u8_url)\n        }\n    return result\n\n\n@trace_error_decorator\nasync def get_sooplive_stream_data(\n        url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None,\n        username: OptionalStr = None, password: OptionalStr = None\n) -> dict:\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0',\n        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n        'Referer': 'https://m.sooplive.co.kr/',\n        'Content-Type': 'application/x-www-form-urlencoded',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    if \"sooplive.com\" in url:\n        return await _fetch_web_stream_data_global(url, proxy_addr, cookies)\n\n    split_url = url.split('/')\n    bj_id = split_url[3] if len(split_url) < 6 else split_url[5]\n\n    data = {\n        'bj_id': bj_id,\n        'broad_no': '',\n        'agent': 'web',\n        'confirm_adult': 'true',\n        'player_type': 'webm',\n        'mode': 'live',\n    }\n\n    url2 = 'http://api.m.sooplive.co.kr/broad/a/watch'\n\n    json_str = await async_req(url=url2, proxy_addr=proxy_addr, headers=headers, data=data, abroad=True)\n    json_data = json.loads(json_str)\n\n    if 'user_nick' in json_data['data']:\n        anchor_name = json_data['data']['user_nick']\n        if \"bj_id\" in json_data['data']:\n            anchor_name = f\"{anchor_name}-{json_data['data']['bj_id']}\"\n    else:\n        anchor_name = ''\n\n    result = {\"anchor_name\": anchor_name or '' ,\"is_live\": False}\n\n    async def get_url_list(m3u8: str) -> List[str]:\n        resp = await async_req(url=m3u8, proxy_addr=proxy_addr, headers=headers, abroad=True)\n        play_url_list = []\n        url_prefix = m3u8.rsplit('/', maxsplit=1)[0] + '/'\n        for i in resp.split('\\n'):\n            if i.startswith('auth_playlist'):\n                play_url_list.append(url_prefix + i.strip())\n        bandwidth_pattern = re.compile(r'BANDWIDTH=(\\d+)')\n        bandwidth_list = bandwidth_pattern.findall(resp)\n        url_to_bandwidth = {purl: int(bandwidth) for bandwidth, purl in zip(bandwidth_list, play_url_list)}\n        play_url_list = sorted(play_url_list, key=lambda purl: url_to_bandwidth[purl], reverse=True)\n        return play_url_list\n\n    if not anchor_name:\n        async def handle_login() -> OptionalStr:\n            cookie = await login_sooplive(username, password, proxy_addr=proxy_addr)\n            if 'AuthTicket=' in cookie:\n                print(\"sooplive platform login successful! Starting to fetch live streaming data...\")\n                return cookie\n\n        async def fetch_data(cookie, _result) -> dict:\n            aid_token = await get_sooplive_tk(url, rtype='aid', proxy_addr=proxy_addr, cookies=cookie)\n            _anchor_name, _broad_no = await get_sooplive_tk(url, rtype='info', proxy_addr=proxy_addr, cookies=cookie)\n            _view_url_data = await get_sooplive_cdn_url(_broad_no, proxy_addr=proxy_addr)\n            _view_url = _view_url_data['view_url']\n            _m3u8_url = _view_url + '?aid=' + aid_token\n            _result |= {\n                \"anchor_name\": _anchor_name,\n                \"is_live\": True,\n                \"m3u8_url\": _m3u8_url,\n                'play_url_list': await get_url_list(_m3u8_url),\n                'new_cookies': cookie\n            }\n            return _result\n\n        if json_data['data']['code'] == -3001:\n            print(\"sooplive live stream failed to retrieve, the live stream just ended.\")\n            return result\n\n        elif json_data['data']['code'] == -3002:\n            print(\"sooplive live stream retrieval failed, the live needs 19+, you are not logged in.\")\n            print(\"Attempting to log in to the sooplive live streaming platform with your account and password, \"\n                  \"please ensure it is configured.\")\n            new_cookie = await handle_login()\n            if new_cookie and len(new_cookie) > 0:\n                return await fetch_data(new_cookie, result)\n            raise RuntimeError(\"sooplive login failed, please check if the account and password are correct\")\n\n        elif json_data['data']['code'] == -3004:\n            if cookies and len(cookies) > 0:\n                return await fetch_data(cookies, result)\n            else:\n                raise RuntimeError(\"sooplive login failed, please check if the account and password are correct\")\n        elif json_data['data']['code'] == -6001:\n            print(\"error message：Please check if the input sooplive live room address \"\n                  \"is correct.\")\n            return result\n    if json_data['result'] == 1 and anchor_name:\n        broad_no = json_data['data']['broad_no']\n        hls_authentication_key = json_data['data']['hls_authentication_key']\n        view_url_data = await get_sooplive_cdn_url(broad_no, proxy_addr=proxy_addr)\n        view_url = view_url_data['view_url']\n        m3u8_url = view_url + '?aid=' + hls_authentication_key\n        result |= {'is_live': True, 'm3u8_url': m3u8_url, 'play_url_list': await get_url_list(m3u8_url)}\n    result['new_cookies'] = None\n    return result\n\n\n@trace_error_decorator\nasync def get_netease_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'accept': 'application/json, text/plain, */*',\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'referer': 'https://cc.163.com/',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n    url = url + '/' if url[-1] != '/' else url\n\n    html_str = await async_req(url=url, proxy_addr=proxy_addr, headers=headers)\n    json_str = re.search('<script id=\"__NEXT_DATA__\" .* crossorigin=\"anonymous\">(.*?)</script></body>',\n                         html_str, re.DOTALL).group(1)\n    json_data = json.loads(json_str)\n    room_data = json_data['props']['pageProps']['roomInfoInitData']\n    live_data = room_data['live']\n    result = {\"is_live\": False}\n    live_status = live_data.get('status') == 1\n    result[\"anchor_name\"] = live_data.get('nickname', room_data.get('nickname'))\n    if live_status:\n        result |= {\n            'is_live': True,\n            'title': live_data['title'],\n            'stream_list': live_data.get('quickplay'),\n            'm3u8_url': live_data.get('sharefile')\n        }\n    return result\n\n\n@trace_error_decorator\nasync def get_qiandurebo_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,'\n                  'application/signed-exchange;v=b3;q=0.7',\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'referer': 'https://qiandurebo.com/web/index.php',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    html_str = await async_req(url=url, proxy_addr=proxy_addr, headers=headers)\n    data = re.search('var user = (.*?)\\r\\n\\\\s+user\\\\.play_url', html_str, re.DOTALL).group(1)\n    anchor_name = re.findall('\"zb_nickname\": \"(.*?)\",\\r\\n', data)\n\n    result = {\"anchor_name\": \"\", \"is_live\": False}\n    if len(anchor_name) > 0:\n        result['anchor_name'] = anchor_name[0]\n        play_url = re.findall('\"play_url\": \"(.*?)\",\\r\\n', data)\n\n        if len(play_url) > 0 and 'common-text-center\" style=\"display:block' not in html_str:\n            result |= {\n                'anchor_name': anchor_name[0],\n                'is_live': True,\n                'flv_url': play_url[0],\n                'record_url': play_url[0]\n            }\n    return result\n\n\n@trace_error_decorator\nasync def get_pandatv_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'origin': 'https://www.pandalive.co.kr',\n        'referer': 'https://www.pandalive.co.kr/',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    user_id = url.split('?')[0].rsplit('/', maxsplit=1)[1]\n    url2 = 'https://api.pandalive.co.kr/v1/live/play'\n    data = {\n        'userId': user_id,\n        'info': 'media fanGrade',\n    }\n    room_password = get_params(url, \"pwd\")\n    if not room_password:\n        room_password = ''\n    data2 = {\n        'action': 'watch',\n        'userId': user_id,\n        'password': room_password,\n        'shareLinkType': '',\n    }\n\n    result = {\"anchor_name\": \"\", \"is_live\": False}\n    json_str = await async_req('https://api.pandalive.co.kr/v1/member/bj',\n                       proxy_addr=proxy_addr, headers=headers, data=data, abroad=True)\n    json_data = json.loads(json_str)\n    if \"bjInfo\" not in json_data:\n        raise RuntimeError(json_data.get(\"message\", 'Unknown error'))\n    anchor_id = json_data['bjInfo']['id']\n    anchor_name = f\"{json_data['bjInfo']['nick']}-{anchor_id}\"\n    result['anchor_name'] = anchor_name\n    live_status = 'media' in json_data\n\n    if live_status:\n        json_str = await async_req(url2, proxy_addr=proxy_addr, headers=headers, data=data2, abroad=True)\n        json_data = json.loads(json_str)\n        if 'errorData' in json_data:\n            if json_data['errorData']['code'] == 'needAdult':\n                raise RuntimeError(f\"{url} The live room requires login and is only accessible to adults. Please \"\n                                   f\"correctly fill in the login cookie in the configuration file.\")\n            else:\n                raise RuntimeError(json_data['errorData']['code'], json_data['message'])\n        play_url = json_data['PlayList']['hls'][0]['url']\n        play_url_list = await get_play_url_list(m3u8=play_url, proxy=proxy_addr, header=headers, abroad=True)\n        result |= {'is_live': True, 'm3u8_url': play_url, 'play_url_list': play_url_list}\n    return result\n\n\n@trace_error_decorator\nasync def get_maoerfm_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'accept': 'application/json, text/plain, */*',\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'referer': 'https://fm.missevan.com/live/868895007',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    room_id = url.split('?')[0].rsplit('/', maxsplit=1)[1]\n    url2 = f'https://fm.missevan.com/api/v2/live/{room_id}'\n\n    json_str = await async_req(url=url2, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n\n    anchor_name = json_data['info']['creator']['username']\n    live_status = False\n    if 'room' in json_data['info']:\n        live_status = json_data['info']['room']['status']['broadcasting']\n\n    result = {\"anchor_name\": anchor_name, \"is_live\": live_status}\n    if live_status:\n        stream_list = json_data['info']['room']['channel']\n        m3u8_url = stream_list['hls_pull_url']\n        flv_url = stream_list['flv_pull_url']\n        title = json_data['info']['room']['name']\n        result |= {'is_live': True, 'title': title, 'm3u8_url': m3u8_url, 'flv_url': flv_url, 'record_url': flv_url}\n    return result\n\n\n@trace_error_decorator\nasync def get_winktv_bj_info(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> tuple:\n    headers = {\n        'accept': 'application/json, text/plain, */*',\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'content-type': 'application/x-www-form-urlencoded',\n        'referer': 'https://www.winktv.co.kr/',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n    user_id = url.split('?')[0].rsplit('/', maxsplit=1)[-1]\n    data = {\n        'userId': user_id,\n        'info': 'media',\n    }\n\n    info_api = 'https://api.winktv.co.kr/v1/member/bj'\n    json_str = await async_req(url=info_api, proxy_addr=proxy_addr, headers=headers, data=data, abroad=True)\n    json_data = json.loads(json_str)\n    live_status = 'media' in json_data\n    anchor_id = json_data['bjInfo']['id']\n    anchor_name = f\"{json_data['bjInfo']['nick']}-{anchor_id}\"\n    return anchor_name, live_status\n\n\n@trace_error_decorator\nasync def get_winktv_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'accept': 'application/json, text/plain, */*',\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'content-type': 'application/x-www-form-urlencoded',\n        'referer': 'https://www.winktv.co.kr',\n        'origin': 'https://www.winktv.co.kr',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',\n\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n    user_id = url.split('?')[0].rsplit('/', maxsplit=1)[-1]\n    room_password = get_params(url, \"pwd\")\n    if not room_password:\n        room_password = ''\n    data = {\n        'action': 'watch',\n        'userId': user_id,\n        'password': room_password,\n        'shareLinkType': '',\n    }\n\n    anchor_name, live_status = await get_winktv_bj_info(url=url, proxy_addr=proxy_addr, cookies=cookies)\n    result = {\"anchor_name\": anchor_name, \"is_live\": live_status}\n    if live_status:\n        play_api = 'https://api.winktv.co.kr/v1/live/play'\n        json_str = await async_req(url=play_api, proxy_addr=proxy_addr, headers=headers, data=data, abroad=True)\n        if '403: Forbidden' in json_str:\n            raise ConnectionError(f\"Your network has been banned from accessing WinkTV ({json_str})\")\n        json_data = json.loads(json_str)\n        if 'errorData' in json_data:\n            if json_data['errorData']['code'] == 'needAdult':\n                raise RuntimeError(f\"{url} The live stream is only accessible to logged-in adults. Please ensure that \"\n                                   f\"the cookie is correctly filled in the configuration file after logging in.\")\n            else:\n                raise RuntimeError(json_data['errorData']['code'], json_data['message'])\n        m3u8_url = json_data['PlayList']['hls'][0]['url']\n        play_url_list = await get_play_url_list(m3u8=m3u8_url, proxy=proxy_addr, header=headers, abroad=True)\n        result['m3u8_url'] = m3u8_url\n        result['play_url_list'] = play_url_list\n    return result\n\n\n@trace_error_decorator\nasync def login_flextv(username: str, password: str, proxy_addr: OptionalStr = None) -> OptionalStr:\n    headers = {\n        'accept': 'application/json, text/plain, */*',\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'content-type': 'application/json;charset=UTF-8',\n        'referer': 'https://www.ttinglive.com/',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',\n    }\n\n    data = {\n        'loginId': username,\n        'password': password,\n        'loginKeep': True,\n        'saveId': True,\n        'device': 'PCWEB',\n    }\n\n    url = 'https://www.ttinglive.com/v2/api/auth/signin'\n\n    try:\n        print(\"Logging into FlexTV platform...\")\n        cookie_dict = await async_req(url, proxy_addr=proxy_addr, headers=headers, json_data=data,\n                                      return_cookies=True, timeout=20)\n\n        if cookie_dict and 'flx_oauth_access' in cookie_dict:\n            cookie_str = '; '.join([f\"{k}={v}\" for k, v in cookie_dict.items()])\n            return cookie_str\n        else:\n            print(\"Please check if the FlexTV account and password in the configuration file are correct.\")\n            return None\n\n    except Exception as e:\n        print(f\"FlexTV login request exception: {e}\")\n        raise Exception(\n            \"FlexTV login failed, please check if the account and password in the configuration file are correct.\"\n        )\n\n\nasync def get_flextv_stream_url(\n        url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None\n) -> str:\n    async def fetch_data(cookie) -> dict:\n        headers = {\n            'accept': 'application/json, text/plain, */*',\n            'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n            'referer': 'https://www.ttinglive.com/',\n            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',\n        }\n        user_id = url.split('/live')[0].rsplit('/', maxsplit=1)[-1]\n        if cookie:\n            headers['Cookie'] = cookie\n        play_api = f'https://www.ttinglive.com/api/channels/{user_id}/stream?option=all'\n        json_str = await async_req(play_api, proxy_addr=proxy_addr, headers=headers, abroad=True)\n        if 'HTTP Error 400: Bad Request' in json_str:\n            raise ConnectionError(\n                \"Failed to retrieve FlexTV live streaming data, please switch to a different proxy and try again.\"\n            )\n        return json.loads(json_str)\n\n    json_data = await fetch_data(cookies)\n    if 'sources' in json_data and len(json_data['sources']) > 0:\n        play_url = json_data['sources'][0]['url']\n        return play_url\n\n\n@trace_error_decorator\nasync def get_flextv_stream_data(\n        url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None,\n        username: OptionalStr = None, password: OptionalStr = None\n) -> dict:\n    headers = {\n        'accept': 'application/json, text/plain, */*',\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'referer': 'https://www.ttinglive.com/',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n    user_id = url.split('/live')[0].rsplit('/', maxsplit=1)[-1]\n    result = {\"anchor_name\": '', \"is_live\": False}\n    new_cookies = None\n    try:\n        url2 = f'https://www.ttinglive.com/channels/{user_id}/live'\n        html_str = await async_req(url2, proxy_addr=proxy_addr, headers=headers, abroad=True)\n        json_str = re.search('<script id=\"__NEXT_DATA__\" type=\".*\">(.*?)</script>', html_str).group(1)\n        json_data = json.loads(json_str)\n        channel_data = json_data['props']['pageProps']['channel']\n        login_need = 'message' in channel_data and '로그인후 이용이 가능합니다.' in channel_data.get('message')\n        if login_need:\n            print(\"FlexTV live stream retrieval failed [not logged in]: 19+ live streams are only available for \"\n                  \"logged-in adults.\")\n            print(\"Attempting to log in to the FlexTV live streaming platform, please ensure your account and \"\n                  \"password are correctly filled in the configuration file.\")\n            if len(username) < 6 or len(password) < 8:\n                raise RuntimeError(\"FlexTV登录失败！请在config.ini配置文件中填写正确的FlexTV平台的账号和密码\")\n            new_cookies = await login_flextv(username, password, proxy_addr=proxy_addr)\n            if new_cookies:\n                print(\"Logged into FlexTV platform successfully! Starting to fetch live streaming data...\")\n            else:\n                raise RuntimeError(\"FlexTV login failed\")\n            cookies = new_cookies if new_cookies else cookies\n            headers['Cookie'] = cookies\n            html_str = await async_req(url2, proxy_addr=proxy_addr, headers=headers, abroad=True)\n            json_str = re.search('<script id=\"__NEXT_DATA__\" type=\".*\">(.*?)</script>', html_str).group(1)\n            json_data = json.loads(json_str)\n            channel_data = json_data['props']['pageProps']['channel']\n\n        live_status = 'message' not in channel_data\n        if live_status:\n            anchor_id = channel_data['owner']['loginId']\n            anchor_name = f\"{channel_data['owner']['nickname']}-{anchor_id}\"\n            result[\"anchor_name\"] = anchor_name\n            play_url = await get_flextv_stream_url(url=url, proxy_addr=proxy_addr, cookies=cookies)\n            if play_url:\n                result['is_live'] = True\n                if '.m3u8' in play_url:\n                    play_url_list = await get_play_url_list(m3u8=play_url, proxy=proxy_addr, header=headers, abroad=True)\n                    if play_url_list:\n                        result['m3u8_url'] = play_url\n                        result['play_url_list'] = play_url_list\n                else:\n                    result['flv_url'] = play_url\n                    result['record_url'] = play_url\n        else:\n            url2 = f'https://www.ttinglive.com/channels/{user_id}'\n            html_str = await async_req(url2, proxy_addr=proxy_addr, headers=headers, abroad=True)\n            anchor_name = re.search('<meta name=\"twitter:title\" content=\"(.*?)의', html_str).group(1)\n            result[\"anchor_name\"] = anchor_name\n    except Exception as e:\n        print(\"Failed to retrieve data from FlexTV live room\", e)\n    result['new_cookies'] = new_cookies\n    return result\n\n\ndef get_looklive_secret_data(text) -> tuple:\n    # 本算法参考项目：https://github.com/785415581/MusicBox/blob/b8f716d43d/doc/analysis/analyze_captured_data.md\n\n    modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee' \\\n              '341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe487' \\\n              '5d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'\n    nonce = b'0CoJUm6Qyw8W8jud'\n    public_key = '010001'\n    from Crypto.Cipher import AES\n    from Crypto.Util.Padding import pad\n    import base64\n    import binascii\n    import secrets\n\n    def create_secret_key(size: int) -> bytes:\n        charset = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=[]{}|;:,.<>?'\n        return ''.join(secrets.choice(charset) for _ in range(size)).encode('utf-8')\n\n    def aes_encrypt(_text: str | bytes, _sec_key: str | bytes) -> bytes:\n        if isinstance(_text, str):\n            _text = _text.encode('utf-8')\n        if isinstance(_sec_key, str):\n            _sec_key = _sec_key.encode('utf-8')\n        _sec_key = _sec_key[:16]  # 16 (AES-128), 24 (AES-192), or 32 (AES-256) bytes\n        iv = bytes('0102030405060708', 'utf-8')\n        encryptor = AES.new(_sec_key, AES.MODE_CBC, iv)\n        padded_text = pad(_text, AES.block_size)\n        ciphertext = encryptor.encrypt(padded_text)\n        encoded_ciphertext = base64.b64encode(ciphertext)\n        return encoded_ciphertext\n\n    def rsa_encrypt(_text: str | bytes, pub_key: str, mod: str) -> str:\n        if isinstance(_text, str):\n            _text = _text.encode('utf-8')\n        text_reversed = _text[::-1]\n        text_int = int(binascii.hexlify(text_reversed), 16)\n        encrypted_int = pow(text_int, int(pub_key, 16), int(mod, 16))\n        return format(encrypted_int, 'x').zfill(256)\n\n    sec_key = create_secret_key(16)\n    enc_text = aes_encrypt(aes_encrypt(json.dumps(text), nonce), sec_key)\n    enc_sec_key = rsa_encrypt(sec_key, public_key, modulus)\n    return enc_text.decode(), enc_sec_key\n\n\nasync def get_looklive_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    \"\"\"\n    通过PC网页端的接口获取完整直播源，只有params和encSecKey这两个加密请求参数。\n    params: 由两次AES加密完成\n    ncSecKey: 由一次自写的加密函数完成，值可固定\n    \"\"\"\n\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0',\n        'Accept': 'application/json, text/javascript',\n        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n        'Content-Type': 'application/x-www-form-urlencoded',\n        'Referer': 'https://look.163.com/',\n    }\n\n    if cookies:\n        headers['Cookie'] = cookies\n\n    room_id = re.search('live\\\\?id=(.*?)&', url).group(1)\n    params, secretkey = get_looklive_secret_data({\"liveRoomNo\": room_id})\n    request_data = {'params': params, 'encSecKey': secretkey}\n    api = 'https://api.look.163.com/weapi/livestream/room/get/v3'\n    json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers, data=request_data)\n    json_data = json.loads(json_str)\n    anchor_name = json_data['data']['anchor']['nickName']\n    live_status = json_data['data']['liveStatus']\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if live_status == 1:\n        result[\"is_live\"] = True\n        if json_data['data']['roomInfo']['liveType'] == 1:\n            print(\"Look live currently only supports audio live streaming, not video live streaming!\")\n        else:\n            play_url_list = json_data['data']['roomInfo']['liveUrl']\n            live_title = json_data['data']['roomInfo']['title']\n            result |= {\n                \"title\": live_title,\n                \"flv_url\": play_url_list['httpPullUrl'],\n                \"m3u8_url\": play_url_list['hlsPullUrl'],\n                \"record_url\": play_url_list['hlsPullUrl'],\n            }\n    return result\n\n\n@trace_error_decorator\nasync def login_popkontv(\n        username: str, password: str, proxy_addr: OptionalStr = None, code: OptionalStr = 'P-00001'\n) -> tuple:\n    headers = {\n        'Accept': 'application/json, text/plain, */*',\n        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'Authorization': 'Basic FpAhe6mh8Qtz116OENBmRddbYVirNKasktdXQiuHfm88zRaFydTsFy63tzkdZY0u',\n        'Content-Type': 'application/json',\n        'Origin': 'https://www.popkontv.com',\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',\n    }\n\n    data = {\n        'partnerCode': code,\n        'signId': username,\n        'signPwd': password,\n    }\n\n    url = 'https://www.popkontv.com/api/proxy/member/v1/login'\n\n    try:\n        proxy_addr = utils.handle_proxy_addr(proxy_addr)\n        async with httpx.AsyncClient(proxy=proxy_addr, timeout=20, verify=False) as client:\n            response = await client.post(url, json=data, headers=headers)\n            response.raise_for_status()\n\n            json_data = response.json()\n            login_status_code = json_data.get(\"statusCd\")\n\n            if login_status_code == 'E4010':\n                raise Exception(\"popkontv login failed, please reconfigure the correct login account or password!\")\n            elif login_status_code == 'S2000':\n                token = json_data['data'].get(\"token\")\n                partner_code = json_data['data'].get(\"partnerCode\")\n                return token, partner_code\n            else:\n                raise Exception(f\"popkontv login failed, {json_data.get('statusMsg', 'unknown error')}\")\n    except httpx.HTTPStatusError as e:\n        print(f\"HTTP status error occurred during login: {e.response.status_code}\")\n        raise\n    except Exception as e:\n        print(f\"An exception occurred during popkontv login: {e}\")\n        raise\n\n\n@trace_error_decorator\nasync def get_popkontv_stream_data(\n        url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None,\n        username: OptionalStr = None, code: OptionalStr = 'P-00001'\n) -> tuple:\n    headers = {\n        'Accept': 'application/json, text/plain, */*',\n        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'Content-Type': 'application/json',\n        'Origin': 'https://www.popkontv.com',\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n    if 'mcid' in url:\n        anchor_id = re.search('mcid=(.*?)&', url).group(1)\n    else:\n        anchor_id = re.search('castId=(.*?)(?=&|$)', url).group(1)\n\n    data = {\n        'partnerCode': code,\n        'searchKeyword': anchor_id,\n        'signId': username,\n    }\n\n    api = 'https://www.popkontv.com/api/proxy/broadcast/v1/search/all'\n    json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers, json_data=data, abroad=True)\n    json_data = json.loads(json_str)\n\n    partner_code = ''\n    anchor_name = 'Unknown'\n    for item in json_data['data']['broadCastList']:\n        if item['mcSignId'] == anchor_id:\n            mc_name = item['nickName']\n            anchor_name = f\"{mc_name}-{anchor_id}\"\n            partner_code = item['mcPartnerCode']\n            break\n\n    if not partner_code:\n        if 'mcPartnerCode' in url:\n            regex_result = re.search('mcPartnerCode=(P-\\\\d+)', url)\n        else:\n            regex_result = re.search('partnerCode=(P-\\\\d+)', url)\n        partner_code = regex_result.group(1) if regex_result else code\n        notices_url = f'https://www.popkontv.com/channel/notices?mcid={anchor_id}&mcPartnerCode={partner_code}'\n        notices_response = await async_req(notices_url, proxy_addr=proxy_addr, headers=headers, abroad=True)\n        mc_name_match = re.search(r'\"mcNickName\":\"([^\"]+)\"', notices_response)\n        mc_name = mc_name_match.group(1) if mc_name_match else 'Unknown'\n        anchor_name = f\"{anchor_id}-{mc_name}\"\n\n    live_url = f\"https://www.popkontv.com/live/view?castId={anchor_id}&partnerCode={partner_code}\"\n    html_str2 = await async_req(live_url, proxy_addr=proxy_addr, headers=headers, abroad=True)\n    json_str2 = re.search('<script id=\"__NEXT_DATA__\" type=\"application/json\">(.*?)</script>', html_str2).group(1)\n    json_data2 = json.loads(json_str2)\n    if 'mcData' in json_data2['props']['pageProps']:\n        room_data = json_data2['props']['pageProps']['mcData']['data']\n        is_private = room_data['mc_isPrivate']\n        cast_start_date_code = room_data['mc_castStartDate']\n        mc_sign_id = room_data['mc_signId']\n        cast_type = room_data['castType']\n        return anchor_name, [cast_start_date_code, partner_code, mc_sign_id, cast_type, is_private]\n    else:\n        return anchor_name, None\n\n\n@trace_error_decorator\nasync def get_popkontv_stream_url(\n        url: str,\n        proxy_addr: OptionalStr = None,\n        access_token: OptionalStr = None,\n        username: OptionalStr = None,\n        password: OptionalStr = None,\n        partner_code: OptionalStr = 'P-00001'\n) -> dict:\n    headers = {\n        'Accept': 'application/json, text/plain, */*',\n        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'ClientKey': 'Client FpAhe6mh8Qtz116OENBmRddbYVirNKasktdXQiuHfm88zRaFydTsFy63tzkdZY0u',\n        'Content-Type': 'application/json',\n        'Origin': 'https://www.popkontv.com',\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',\n    }\n\n    if access_token:\n        headers['Authorization'] = f'Bearer {access_token}'\n\n    anchor_name, room_info = await get_popkontv_stream_data(\n        url, proxy_addr=proxy_addr, code=partner_code, username=username)\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    new_token = None\n    if room_info:\n        cast_start_date_code, cast_partner_code, mc_sign_id, cast_type, is_private = room_info\n        result[\"is_live\"] = True\n        room_password = get_params(url, \"pwd\")\n        if int(is_private) != 0 and not room_password:\n            raise RuntimeError(f\"Failed to retrieve live room data because {anchor_name}'s room is a private room. \"\n                               f\"Please configure the room password and try again.\")\n\n        async def fetch_data(header: dict = None, code: str = None) -> str:\n            data = {\n                'androidStore': 0,\n                'castCode': f'{mc_sign_id}-{cast_start_date_code}',\n                'castPartnerCode': cast_partner_code,\n                'castSignId': mc_sign_id,\n                'castType': cast_type,\n                'commandType': 0,\n                'exePath': 5,\n                'isSecret': is_private,\n                'partnerCode': code,\n                'password': room_password,\n                'signId': username,\n                'version': '4.6.2',\n            }\n            play_api = 'https://www.popkontv.com/api/proxy/broadcast/v1/castwatchonoffguest'\n            return await async_req(play_api, proxy_addr=proxy_addr, json_data=data, headers=header, abroad=True)\n\n        json_str = await fetch_data(headers, partner_code)\n\n        if 'HTTP Error 400' in json_str or 'statusCd\":\"E5000' in json_str:\n            print(\"Failed to retrieve popkontv live stream [token does not exist or has expired]: Please log in to \"\n                  \"watch.\")\n            print(\"Attempting to log in to the popkontv live streaming platform, please ensure your account \"\n                  \"and password are correctly filled in the configuration file.\")\n            if len(username) < 4 or len(password) < 10:\n                raise RuntimeError(\"popkontv login failed! Please enter the correct account and password for the \"\n                                   \"popkontv platform in the config.ini file.\")\n            print(\"Logging into popkontv platform...\")\n            new_access_token, new_partner_code = await login_popkontv(\n                username=username, password=password, proxy_addr=proxy_addr, code=partner_code\n            )\n            if new_access_token and len(new_access_token) == 640:\n                print(\"Logged into popkontv platform successfully! Starting to fetch live streaming data...\")\n                headers['Authorization'] = f'Bearer {new_access_token}'\n                new_token = f'Bearer {new_access_token}'\n                json_str = await fetch_data(headers, new_partner_code)\n            else:\n                raise RuntimeError(\"popkontv login failed, please check if the account and password are correct\")\n        json_data = json.loads(json_str)\n        status_msg = json_data[\"statusMsg\"]\n        if json_data['statusCd'] == \"L000A\":\n            print(\"Failed to retrieve live stream source,\", status_msg)\n            raise RuntimeError(\"You are an unverified member. After logging into the popkontv official website, \"\n                               \"please verify your mobile phone at the bottom of the 'My Page' > 'Edit My \"\n                               \"Information' to use the service.\")\n        elif json_data['statusCd'] == \"L0001\":\n            cast_start_date_code = int(cast_start_date_code) - 1\n            json_str = await fetch_data(headers, partner_code)\n            json_data = json.loads(json_str)\n            m3u8_url = json_data['data']['castHlsUrl']\n            result |= {\"m3u8_url\": m3u8_url, \"record_url\": m3u8_url}\n        elif json_data['statusCd'] == \"L0000\":\n            m3u8_url = json_data['data']['castHlsUrl']\n            result |= {\"m3u8_url\": m3u8_url, \"record_url\": m3u8_url}\n        else:\n            raise RuntimeError(\"Failed to retrieve live stream source,\", status_msg)\n    result['new_token'] = new_token\n    return result\n\n\n@trace_error_decorator\nasync def login_twitcasting(\n        account_type: str, username: str, password: str, proxy_addr: OptionalStr = None,\n        cookies: OptionalStr = None\n) -> OptionalStr:\n    headers = {\n        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',\n        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'Content-Type': 'application/x-www-form-urlencoded',\n        'Referer': 'https://twitcasting.tv/indexcaslogin.php?redir=%2Findexloginwindow.php%3Fnext%3D%252F&keep=1',\n        'Cookie': 'hl=zh; did=04fb08f1b15d248644f1dfa82816d323; _ga=GA1.1.1021187740.1709706998; keep=1; mfadid=yrQiEB26ruRg7mlMavABMBZWdOddzojW; _ga_X8R46Y30YM=GS1.1.1709706998.1.1.1709712274.0.0.0',\n        'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n    }\n\n    if cookies:\n        headers['Cookie'] = cookies\n\n    if account_type == \"twitter\":\n        login_url = 'https://twitcasting.tv/indexpasswordlogin.php'\n        login_api = 'https://twitcasting.tv/indexpasswordlogin.php?redir=/indexloginwindow.php?next=%2F&keep=1'\n    else:\n        login_url = 'https://twitcasting.tv/indexcaslogin.php?redir=%2F&keep=1'\n        login_api = 'https://twitcasting.tv/indexcaslogin.php?redir=/indexloginwindow.php?next=%2F&keep=1'\n\n    html_str = await async_req(login_url, proxy_addr=proxy_addr, headers=headers)\n    cs_session_id = re.search('<input type=\"hidden\" name=\"cs_session_id\" value=\"(.*?)\">', html_str).group(1)\n\n    data = {\n        'username': username,\n        'password': password,\n        'action': 'login',\n        'cs_session_id': cs_session_id,\n    }\n    try:\n        cookie_dict = await async_req(login_api, proxy_addr=proxy_addr, headers=headers,\n                                      data=data, return_cookies=True, timeout=20)\n        if 'tc_ss' in cookie_dict:\n            cookie = utils.dict_to_cookie_str(cookie_dict)\n            return cookie\n    except Exception as e:\n        print(\"TwitCasting login error,\", e)\n\n\n@trace_error_decorator\nasync def get_twitcasting_stream_url(\n        url: str,\n        proxy_addr: OptionalStr = None,\n        cookies: OptionalStr = None,\n        account_type: OptionalStr = None,\n        username: OptionalStr = None,\n        password: OptionalStr = None,\n) -> dict:\n    headers = {\n        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',\n        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'Referer': 'https://twitcasting.tv/?ch0',\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',\n    }\n\n    anchor_id = url.split('/')[3]\n    if cookies:\n        headers['Cookie'] = cookies\n\n    async def get_data(header) -> tuple:\n        html_str = await async_req(url, proxy_addr=proxy_addr, headers=header)\n        anchor = re.search(\"<title>(.*?) \\\\(@(.*?)\\\\)  的直播 - Twit\", html_str)\n        title = re.search('<meta name=\"twitter:title\" content=\"(.*?)\">\\n\\\\s+<meta', html_str)\n        status = re.search('data-is-onlive=\"(.*?)\"\\n\\\\s+data-view-mode', html_str)\n        movie_id = re.search('data-movie-id=\"(.*?)\" data-audience-id', html_str)\n        return f'{anchor.group(1).strip()}-{anchor.group(2)}-{movie_id.group(1)}', status.group(1), title.group(1)\n\n    result = {\"anchor_name\": '', \"is_live\": False}\n    new_cookie = None\n    try:\n        to_login = get_params(url, \"login\")\n        if to_login == 'true':\n            print(\"Attempting to log in to TwitCasting...\")\n            new_cookie = await login_twitcasting(\n                account_type=account_type, username=username, password=password, proxy_addr=proxy_addr, cookies=cookies)\n            if not new_cookie:\n                raise RuntimeError(\"TwitCasting login failed, please check if the account password in the \"\n                                   \"configuration file is correct\")\n            print(\"TwitCasting login successful! Starting to fetch data...\")\n            headers['Cookie'] = new_cookie\n        anchor_name, live_status, live_title = await get_data(headers)\n    except AttributeError:\n        print(\"Failed to retrieve TwitCasting data, attempting to log in...\")\n        new_cookie = await login_twitcasting(\n            account_type=account_type, username=username, password=password, proxy_addr=proxy_addr, cookies=cookies)\n        if not new_cookie:\n            raise RuntimeError(\"TwitCasting login failed, please check if the account and password in the \"\n                               \"configuration file are correct\")\n        print(\"TwitCasting login successful! Starting to fetch data...\")\n        headers['Cookie'] = new_cookie\n        anchor_name, live_status, live_title = await get_data(headers)\n\n    result[\"anchor_name\"] = anchor_name\n    if live_status == 'true':\n        url_streamserver = f\"https://twitcasting.tv/streamserver.php?target={anchor_id}&mode=client&player=pc_web\"\n        stream_data = await async_req(url_streamserver, proxy_addr=proxy_addr, headers=headers)\n        json_data = json.loads(stream_data)\n        if not json_data.get('tc-hls') or not json_data['tc-hls'].get(\"streams\"):\n            raise RuntimeError(\"No m3u8_url,please check the url\")\n\n        stream_dict = json_data['tc-hls'][\"streams\"]\n        quality_order = {\"high\": 0, \"medium\": 1, \"low\": 2}\n        sorted_streams = sorted(stream_dict.items(), key=lambda item: quality_order[item[0]])\n        play_url_list = [url for quality, url in sorted_streams]\n        result |= {'title': live_title, 'is_live': True, \"play_url_list\": play_url_list}\n    result['new_cookies'] = new_cookie\n    return result\n\n\n@trace_error_decorator\nasync def get_baidu_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'Connection': 'keep-alive',\n        'Referer': 'https://live.baidu.com/',\n        'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    uid = random.choice([\n        'h5-683e85bdf741bf2492586f7ca39bf465',\n        'h5-c7c6dc14064a136be4215b452fab9eea',\n        'h5-4581281f80bb8968bd9a9dfba6050d3a'\n    ])\n    room_id = re.search('room_id=(.*?)&', url).group(1)\n    params = {\n        'cmd': '371',\n        'action': 'star',\n        'service': 'bdbox',\n        'osname': 'baiduboxapp',\n        'data': '{\"data\":{\"room_id\":\"' + room_id + '\",\"device_id\":\"h5-683e85bdf741bf2492586f7ca39bf465\",'\n                                                   '\"source_type\":0,\"osname\":\"baiduboxapp\"},\"replay_slice\":0,'\n                                                   '\"nid\":\"\",\"schemeParams\":{\"src_pre\":\"pc\",\"src_suf\":\"other\",'\n                                                   '\"bd_vid\":\"\",\"share_uid\":\"\",\"share_cuk\":\"\",\"share_ecid\":\"\",'\n                                                   '\"zb_tag\":\"\",\"shareTaskInfo\":\"{\\\\\"room_id\\\\\":\\\\\"9175031377\\\\\"}\",'\n                                                   '\"share_from\":\"\",\"ext_params\":\"\",\"nid\":\"\"}}',\n        'ua': '360_740_ANDROID_0',\n        'bd_vid': '',\n        'uid': uid,\n        '_': str(int(time.time() * 1000)),\n    }\n    app_api = f'https://mbd.baidu.com/searchbox?{urllib.parse.urlencode(params)}'\n    json_str = await async_req(url=app_api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    key = list(json_data['data'].keys())[0]\n    data = json_data['data'][key]\n    anchor_name = data['host']['name']\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if data['status'] == \"0\":\n        result[\"is_live\"] = True\n        live_title = data['video']['title']\n        play_url_list = data['video']['url_clarity_list']\n        url_list = []\n        prefix = 'https://hls.liveshow.bdstatic.com/live/'\n        if play_url_list:\n            for i in play_url_list:\n                url_list.append(\n                    prefix + i['urls']['flv'].rsplit('.', maxsplit=1)[0].rsplit('/', maxsplit=1)[1] + '.m3u8')\n        else:\n            play_url_list = data['video']['url_list']\n            for i in play_url_list:\n                url_list.append(prefix + i['urls'][0]['hls'].rsplit('?', maxsplit=1)[0].rsplit('/', maxsplit=1)[1])\n\n        if url_list:\n            result |= {\"is_live\": True, \"title\": live_title, 'play_url_list': url_list}\n    return result\n\n\n@trace_error_decorator\nasync def get_weibo_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'Cookie': 'XSRF-TOKEN=qAP-pIY5V4tO6blNOhA4IIOD; SUB=_2AkMRNMCwf8NxqwFRmfwWymPrbI9-zgzEieKnaDFrJRMxHRl-yT9kqmkhtRB6OrTuX5z9N_7qk9C3xxEmNR-8WLcyo2PM; SUBP=0033WrSXqPxfM72-Ws9jqgMF55529P9D9WWemwcqkukCduUO11o9sBqA; WBPSESS=Wk6CxkYDejV3DDBcnx2LOXN9V1LjdSTNQPMbBDWe4lO2HbPmXG_coMffJ30T-Avn_ccQWtEYFcq9fab1p5RR6PEI6w661JcW7-56BszujMlaiAhLX-9vT4Zjboy1yf2l',\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',\n        'Referer': 'https://weibo.com/u/5885340893'\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    room_id = ''\n    if 'show/' in url:\n        room_id = url.split('?')[0].split('show/')[1]\n    else:\n        uid = url.split('?')[0].rsplit('/u/', maxsplit=1)[1]\n        web_api = f'https://weibo.com/ajax/statuses/mymblog?uid={uid}&page=1&feature=0'\n        json_str = await async_req(web_api, proxy_addr=proxy_addr, headers=headers)\n        json_data = json.loads(json_str)\n        for i in json_data['data']['list']:\n            if 'page_info' in i and i['page_info']['object_type'] == 'live':\n                room_id = i['page_info']['object_id']\n                break\n\n    result = {\"anchor_name\": '', \"is_live\": False}\n    if room_id:\n        app_api = f'https://weibo.com/l/pc/anchor/live?live_id={room_id}'\n        # app_api = f'https://weibo.com/l/!/2/wblive/room/show_pc_live.json?live_id={room_id}'\n        json_str = await async_req(url=app_api, proxy_addr=proxy_addr, headers=headers)\n        json_data = json.loads(json_str)\n        anchor_name = json_data['data']['user_info']['name']\n        result[\"anchor_name\"] = anchor_name\n        live_status = json_data['data']['item']['status']\n        if live_status == 1:\n            result[\"is_live\"] = True\n            live_title = json_data['data']['item']['desc']\n            play_url_list = json_data['data']['item']['stream_info']['pull']\n            m3u8_url = play_url_list['live_origin_hls_url']\n            flv_url = play_url_list['live_origin_flv_url']\n            result['title'] = live_title\n            result['play_url_list'] = [\n                {\"m3u8_url\": m3u8_url, \"flv_url\": flv_url},\n                {\"m3u8_url\": m3u8_url.split('_')[0] + '.m3u8', \"flv_url\": flv_url.split('_')[0] + '.flv'}\n            ]\n    return result\n\n\n@trace_error_decorator\nasync def get_kugou_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',\n        'Accept': 'application/json',\n        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n        'Referer': 'https://fanxing2.kugou.com/',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    if 'roomId' in url:\n        room_id = re.search('roomId=(\\\\d+)', url).group(1)\n    else:\n        room_id = url.split('?')[0].rsplit('/', maxsplit=1)[1]\n\n    app_api = f'https://service2.fanxing.kugou.com/roomcen/room/web/cdn/getEnterRoomInfo?roomId={room_id}'\n    json_str = await async_req(url=app_api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    anchor_name = json_data['data']['normalRoomInfo']['nickName']\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if not anchor_name:\n        raise RuntimeError(\n            \"Music channel live rooms are not supported for recording, please switch to a different live room.\"\n        )\n    live_status = json_data['data']['liveType']\n    if live_status != -1:\n        params = {\n            'std_rid': room_id,\n            'std_plat': '7',\n            'std_kid': '0',\n            'streamType': '1-2-4-5-8',\n            'ua': 'fx-flash',\n            'targetLiveTypes': '1-5-6',\n            'version': '1000',\n            'supportEncryptMode': '1',\n            'appid': '1010',\n            '_': str(int(time.time() * 1000)),\n        }\n        api = f'https://fx1.service.kugou.com/video/pc/live/pull/mutiline/streamaddr?{urllib.parse.urlencode(params)}'\n        json_str2 = await async_req(api, proxy_addr=proxy_addr, headers=headers)\n        json_data2 = json.loads(json_str2)\n        stream_data = json_data2['data']['lines']\n        if stream_data:\n            flv_url = stream_data[-1]['streamProfiles'][0]['httpsFlv'][0]\n            result |= {\"is_live\": True, \"flv_url\": flv_url, \"record_url\": flv_url}\n    return result\n\n\nasync def get_twitchtv_room_info(url: str, token: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> tuple:\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0',\n        'Accept-Language': 'zh-CN',\n        'Referer': 'https://www.twitch.tv/',\n        'Client-Id': 'kimne78kx3ncx6brgo4mv6wki5h1ko',\n        'Client-Integrity': token,\n        'Content-Type': 'text/plain;charset=UTF-8',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n    uid = url.split('?')[0].rsplit('/', maxsplit=1)[-1]\n\n    data = [\n        {\n            \"operationName\": \"ChannelShell\",\n            \"variables\": {\n                \"login\": uid\n            },\n            \"extensions\": {\n                \"persistedQuery\": {\n                    \"version\": 1,\n                    \"sha256Hash\": \"580ab410bcd0c1ad194224957ae2241e5d252b2c5173d8e0cce9d32d5bb14efe\"\n                }\n            }\n        },\n    ]\n\n    json_str = await async_req('https://gql.twitch.tv/gql', proxy_addr=proxy_addr, headers=headers,\n                               json_data=data, abroad=True)\n    json_data = json.loads(json_str)\n    user_data = json_data[0]['data']['userOrError']\n    login_name = user_data[\"login\"]\n    nickname = f\"{user_data['displayName']}-{login_name}\"\n    status = True if user_data['stream'] else False\n    return nickname, status\n\n\n@trace_error_decorator\nasync def get_twitchtv_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',\n        'Accept-Language': 'en-US',\n        'Referer': 'https://www.twitch.tv/',\n        'Client-ID': 'kimne78kx3ncx6brgo4mv6wki5h1ko',\n        'device-id': generate_random_string(16).lower(),\n    }\n\n    if cookies:\n        headers['Cookie'] = cookies\n    uid = url.split('?')[0].rsplit('/', maxsplit=1)[-1]\n\n    data = {\n        \"operationName\": \"PlaybackAccessToken_Template\",\n        \"query\": \"query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, \"\n                 \"$isVod: Boolean!, $playerType: String!) {  streamPlaybackAccessToken(channelName: $login, \"\n                 \"params: {platform: \\\"web\\\", playerBackend: \\\"mediaplayer\\\", playerType: $playerType}) @include(if: \"\n                 \"$isLive) {    value    signature   authorization { isForbidden forbiddenReasonCode }   __typename  \"\n                 \"}  videoPlaybackAccessToken(id: $vodID, params: {platform: \\\"web\\\", playerBackend: \\\"mediaplayer\\\", \"\n                 \"playerType: $playerType}) @include(if: $isVod) {    value    signature   __typename  }}\",\n        \"variables\": {\n            \"isLive\": True,\n            \"login\": uid,\n            \"isVod\": False,\n            \"vodID\": \"\",\n            \"playerType\": \"site\"\n        }\n    }\n\n    json_str = await async_req('https://gql.twitch.tv/gql', proxy_addr=proxy_addr, headers=headers,\n                               json_data=data, abroad=True)\n    json_data = json.loads(json_str)\n    token = json_data['data']['streamPlaybackAccessToken']['value']\n    sign = json_data['data']['streamPlaybackAccessToken']['signature']\n\n    anchor_name, live_status = await get_twitchtv_room_info(url=url, token=token, proxy_addr=proxy_addr, cookies=cookies)\n    result = {\"anchor_name\": anchor_name, \"is_live\": live_status}\n    if live_status:\n        play_session_id = random.choice([\"bdd22331a986c7f1073628f2fc5b19da\", \"064bc3ff1722b6f53b0b5b8c01e46ca5\"])\n        params = {\n            \"acmb\": \"e30=\",\n            \"allow_source\": \"true\",\n            \"browser_family\": \"firefox\",\n            \"browser_version\": \"124.0\",\n            \"cdm\": \"wv\",\n            \"fast_bread\": \"true\",\n            \"os_name\": \"Windows\",\n            \"os_version\": \"NT%2010.0\",\n            \"p\": \"3553732\",\n            \"platform\": \"web\",\n            \"play_session_id\": play_session_id,\n            \"player_backend\": \"mediaplayer\",\n            \"player_version\": \"1.28.0-rc.1\",\n            \"playlist_include_framerate\": \"true\",\n            \"reassignments_supported\": \"true\",\n            \"sig\": sign,\n            \"token\": token,\n            \"transcode_mode\": \"cbr_v1\"\n        }\n        access_key = urllib.parse.urlencode(params)\n        m3u8_url = f'https://usher.ttvnw.net/api/channel/hls/{uid}.m3u8?{access_key}'\n        play_url_list = await get_play_url_list(m3u8=m3u8_url, proxy=proxy_addr, header=headers, abroad=True)\n        result |= {'m3u8_url': m3u8_url, 'play_url_list': play_url_list}\n    return result\n\n\n@trace_error_decorator\nasync def get_liveme_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'origin': 'https://www.liveme.com',\n        'referer': 'https://www.liveme.com',\n        'user-agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    if 'index.html' not in url:\n        html_str = await async_req(url, proxy_addr=proxy_addr, headers=headers, abroad=True)\n        match_url = re.search('<meta property=\"og:url\" content=\"(.*?)\">', html_str)\n        if match_url:\n            url = match_url.group(1)\n\n    room_id = url.split(\"/index.html\")[0].rsplit('/', maxsplit=1)[-1]\n    sign_data = execjs.compile(open(f'{JS_SCRIPT_PATH}/liveme.js').read()).call('sign', room_id,\n                                                                                f'{JS_SCRIPT_PATH}/crypto-js.min.js')\n    lm_s_sign = sign_data.pop(\"lm_s_sign\")\n    tongdun_black_box = sign_data.pop(\"tongdun_black_box\")\n    platform = sign_data.pop(\"os\")\n    headers['lm-s-sign'] = lm_s_sign\n\n    params = {\n        'alias': 'liveme',\n        'tongdun_black_box': tongdun_black_box,\n        'os': platform,\n    }\n\n    api = f'https://live.liveme.com/live/queryinfosimple?{urllib.parse.urlencode(params)}'\n    json_str = await async_req(api, data=sign_data, proxy_addr=proxy_addr, headers=headers, abroad=True)\n    json_data = json.loads(json_str)\n    stream_data = json_data['data']['video_info']\n    anchor_name = stream_data['uname']\n    live_status = stream_data['status']\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if live_status == \"0\":\n        m3u8_url = stream_data['hlsvideosource']\n        flv_url = stream_data['videosource']\n        result |= {\n            'is_live': True,\n            'm3u8_url': m3u8_url,\n            'flv_url': flv_url,\n            'record_url': m3u8_url or flv_url\n        }\n    return result\n\n\nasync def get_huajiao_sn(url: str, cookies: OptionalStr = None, proxy_addr: OptionalStr = None) -> tuple | None:\n    headers = {\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'referer': 'https://www.huajiao.com/',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',\n    }\n\n    if cookies:\n        headers['Cookie'] = cookies\n\n    live_id = url.split('?')[0].rsplit('/', maxsplit=1)[1]\n    api = f'https://www.huajiao.com/l/{live_id}'\n    try:\n        html_str = await async_req(url=api, proxy_addr=proxy_addr, headers=headers)\n        json_str = re.search('var feed = (.*?});', html_str).group(1)\n        json_data = json.loads(json_str)\n        sn = json_data['feed']['sn']\n        uid = json_data['author']['uid']\n        nickname = json_data['author']['nickname']\n        live_id = url.split('?')[0].rsplit('/', maxsplit=1)[1]\n        return nickname, sn, uid, live_id\n    except Exception:\n        utils.replace_url(f'{script_path}/config/URL_config.ini', old=url, new='#' + url)\n        raise RuntimeError(\"Failed to retrieve live room data, the Huajiao live room address is not fixed, please use \"\n                           \"the anchor's homepage address for recording.\")\n\n\nasync def get_huajiao_user_info(url: str, cookies: OptionalStr = None, proxy_addr: OptionalStr = None) -> OptionalDict:\n    headers = {\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'referer': 'https://www.huajiao.com/',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',\n    }\n\n    if cookies:\n        headers['Cookie'] = cookies\n\n    if 'user' in url:\n        uid = url.split('?')[0].split('user/')[1]\n        params = {\n            'uid': uid,\n            'fmt': 'json',\n            '_': str(int(time.time() * 1000)),\n        }\n\n        api = f'https://webh.huajiao.com/User/getUserFeeds?{urllib.parse.urlencode(params)}'\n        json_str = await async_req(url=api, proxy_addr=proxy_addr, headers=headers)\n        json_data = json.loads(json_str)\n\n        html_str = await async_req(url=f'https://www.huajiao.com/user/{uid}', proxy_addr=proxy_addr, headers=headers)\n        anchor_name = re.search('<title>(.*?)的主页.*</title>', html_str).group(1)\n        if json_data['data'] and 'sn' in json_data['data']['feeds'][0]['feed']:\n            feed = json_data['data']['feeds'][0]['feed']\n            return {\n                \"anchor_name\": anchor_name,\n                \"title\": feed['title'],\n                \"is_live\": True,\n                \"sn\": feed['sn'],\n                \"liveid\": feed['relateid'],\n                \"uid\": uid\n            }\n        else:\n            return {\"anchor_name\": anchor_name, \"is_live\": False}\n\n\nasync def get_huajiao_stream_url_app(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> OptionalDict:\n    headers = {\n        'User-Agent': 'living/9.4.0 (com.huajiao.seeding; build:2410231746; iOS 17.0.0) Alamofire/9.4.0',\n        'accept-language': 'zh-Hans-US;q=1.0',\n        'sdk_version': '1',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n    room_id = url.rsplit('/', maxsplit=1)[1]\n    api = f'https://live.huajiao.com/feed/getFeedInfo?relateid={room_id}'\n    json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n\n    if json_data['errmsg'] or not json_data['data'].get('creatime'):\n        print(\"Failed to retrieve live room data, the Huajiao live room address is not fixed, please manually change \"\n              \"the address for recording.\")\n        return\n    data = json_data['data']\n    return {\n        \"anchor_name\": data['author']['nickname'],\n        \"title\": data['feed']['title'],\n        \"is_live\": True,\n        \"sn\": data['feed']['sn'],\n        \"liveid\": data['feed']['relateid'],\n        \"uid\": data['author']['uid']\n    }\n\n\n@trace_error_decorator\nasync def get_huajiao_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'referer': 'https://www.huajiao.com/',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    result = {\"anchor_name\": \"\", \"is_live\": False}\n\n    if 'user/' in url:\n        if not cookies:\n            return result\n        room_data = await get_huajiao_user_info(url, cookies, proxy_addr)\n    else:\n        url = await async_req(url, proxy_addr=proxy_addr, headers=headers, redirect_url=True)\n        if url.rstrip('/') == 'https://www.huajiao.com':\n            print(\n                \"Failed to retrieve live room data, the Huajiao live room address is not fixed, please manually change \"\n                \"the address for recording.\")\n            return result\n        room_data = await get_huajiao_stream_url_app(url, proxy_addr)\n\n    if room_data:\n        result[\"anchor_name\"] = room_data.pop(\"anchor_name\")\n        live_status = room_data.pop(\"is_live\")\n\n        if live_status:\n            result[\"title\"] = room_data.pop(\"title\")\n            params = {\n                \"time\": int(time.time() * 1000),\n                \"version\": \"1.0.0\",\n                **room_data,\n                \"encode\": \"h265\"\n            }\n\n            api = f'https://live.huajiao.com/live/substream?{urllib.parse.urlencode(params)}'\n            json_str = await async_req(url=api, proxy_addr=proxy_addr, headers=headers)\n            json_data = json.loads(json_str)\n            result |= {\n                'is_live': True,\n                'flv_url': json_data['data']['h264_url'],\n                'record_url': json_data['data']['h264_url'],\n            }\n    return result\n\n\n@trace_error_decorator\nasync def get_liuxing_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'Accept': 'application/json, text/plain, */*',\n        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'Referer': 'https://wap.7u66.com/198189?promoters=0',\n        'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    room_id = url.split('?')[0].rsplit('/', maxsplit=1)[1]\n    params = {\n        \"promoters\": \"0\",\n        \"roomidx\": room_id,\n        \"currentUrl\": f\"https://www.7u66.com/{room_id}?promoters=0\"\n    }\n    api = f'https://wap.7u66.com/api/ui/room/v1.0.0/live.ashx?{urllib.parse.urlencode(params)}'\n    json_str = await async_req(url=api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    room_info = json_data['data']['roomInfo']\n    anchor_name = room_info['nickname']\n    live_status = room_info[\"live_stat\"]\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if live_status == 1:\n        idx = room_info['idx']\n        live_id = room_info['liveId1']\n        flv_url = f'https://txpull1.5see.com/live/{idx}/{live_id}.flv'\n        result |= {'is_live': True, 'flv_url': flv_url, 'record_url': flv_url}\n    return result\n\n\n@trace_error_decorator\nasync def get_showroom_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    if '/room/profile' in url:\n        room_id = url.split('room_id=')[-1]\n    else:\n        html_str = await async_req(url, proxy_addr=proxy_addr, headers=headers, abroad=True)\n        room_id = re.search('href=\"/room/profile\\\\?room_id=(.*?)\"', html_str).group(1)\n    info_api = f'https://www.showroom-live.com/api/live/live_info?room_id={room_id}'\n    json_str = await async_req(info_api, proxy_addr=proxy_addr, headers=headers, abroad=True)\n    json_data = json.loads(json_str)\n    anchor_name = json_data['room_name']\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    live_status = json_data['live_status']\n    if live_status == 2:\n        result[\"is_live\"] = True\n        web_api = f'https://www.showroom-live.com/api/live/streaming_url?room_id={room_id}&abr_available=1'\n        json_str = await async_req(web_api, proxy_addr=proxy_addr, headers=headers, abroad=True)\n        if json_str:\n            json_data = json.loads(json_str)\n            streaming_url_list = json_data['streaming_url_list']\n\n            for i in streaming_url_list:\n                if i['type'] == 'hls_all':\n                    m3u8_url = i['url']\n                    result['m3u8_url'] = m3u8_url\n                    if m3u8_url:\n                        m3u8_url_list = await get_play_url_list(m3u8_url, proxy=proxy_addr, header=headers, abroad=True)\n                        if m3u8_url_list:\n                            result['play_url_list'] = [f\"{m3u8_url.rsplit('/', maxsplit=1)[0]}/{i}\" for i in\n                                                       m3u8_url_list]\n                        else:\n                            result['play_url_list'] = [m3u8_url]\n                        result['play_url_list'] = [i.replace('https://', 'http://') for i in result['play_url_list']]\n                        break\n    return result\n\n\n@trace_error_decorator\nasync def get_acfun_sign_params(proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> tuple:\n    did = f'web_{utils.generate_random_string(16)}'\n    headers = {\n        'referer': 'https://live.acfun.cn/',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',\n        'cookie': f'_did={did};',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n    data = {\n        'sid': 'acfun.api.visitor',\n    }\n    api = 'https://id.app.acfun.cn/rest/app/visitor/login'\n    json_str = await async_req(api, data=data, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    user_id = json_data[\"userId\"]\n    visitor_st = json_data[\"acfun.api.visitor_st\"]\n    return user_id, did, visitor_st\n\n\n@trace_error_decorator\nasync def get_acfun_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'referer': 'https://live.acfun.cn/live/17912421',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    author_id = url.split('?')[0].rsplit('/', maxsplit=1)[1]\n    user_info_api = f'https://live.acfun.cn/rest/pc-direct/user/userInfo?userId={author_id}'\n    json_str = await async_req(user_info_api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    anchor_name = json_data['profile']['name']\n    status = 'liveId' in json_data['profile']\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if status:\n        result[\"is_live\"] = True\n        user_id, did, visitor_st = await get_acfun_sign_params(proxy_addr=proxy_addr, cookies=cookies)\n        params = {\n            'subBiz': 'mainApp',\n            'kpn': 'ACFUN_APP',\n            'kpf': 'PC_WEB',\n            'userId': user_id,\n            'did': did,\n            'acfun.api.visitor_st': visitor_st,\n        }\n\n        data = {\n            'authorId': author_id,\n            'pullStreamType': 'FLV',\n        }\n        play_api = f'https://api.kuaishouzt.com/rest/zt/live/web/startPlay?{urllib.parse.urlencode(params)}'\n        json_str = await async_req(play_api, data=data, proxy_addr=proxy_addr, headers=headers)\n        json_data = json.loads(json_str)\n        live_title = json_data['data']['caption']\n        videoPlayRes = json_data['data']['videoPlayRes']\n        play_url_list = json.loads(videoPlayRes)['liveAdaptiveManifest'][0]['adaptationSet']['representation']\n        play_url_list = sorted(play_url_list, key=itemgetter('bitrate'), reverse=True)\n        result |= {'play_url_list': play_url_list, 'title': live_title}\n    return result\n\n\n@trace_error_decorator\nasync def get_changliao_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n        'Accept': 'application/json, text/plain, */*',\n        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n        'Referer': 'https://wap.tlclw.com/phone/15777?promoters=0',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    room_id = url.split('?')[0].rsplit('/', maxsplit=1)[-1]\n    params = {\n        'roomidx': room_id,\n        'currentUrl': f'https://wap.tlclw.com/{room_id}',\n    }\n    play_api = f'https://wap.tlclw.com/api/ui/room/v1.0.0/live.ashx?{urllib.parse.urlencode(params)}'\n    json_str = await async_req(play_api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    anchor_name = json_data['data']['roomInfo']['nickname']\n    live_status = json_data['data']['roomInfo']['live_stat']\n\n    async def get_live_domain(page_url):\n        html_str = await async_req(page_url, proxy_addr=proxy_addr, headers=headers)\n        config_json_str = re.findall(\"var config = (.*?)config.webskins\",\n                                     html_str, re.DOTALL)[0].rsplit(\";\", maxsplit=1)[0].strip()\n        config_json_data = json.loads(config_json_str)\n        stream_flv_domain = config_json_data['domainpullstream_flv']\n        stream_hls_domain = config_json_data['domainpullstream_hls']\n        return stream_flv_domain, stream_hls_domain\n\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if live_status == 1:\n        flv_domain, hls_domain = await get_live_domain(url)\n        live_id = json_data['data']['roomInfo']['liveID']\n        flv_url = f'{flv_domain}/{live_id}.flv'\n        m3u8_url = f'{hls_domain}/{live_id}.m3u8'\n        result |= {'is_live': True, 'm3u8_url': m3u8_url, 'flv_url': flv_url, 'record_url': flv_url}\n    return result\n\n\n@trace_error_decorator\nasync def get_yingke_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'Referer': 'https://www.inke.cn/',\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    parsed_url = urllib.parse.urlparse(url)\n    query_params = urllib.parse.parse_qs(parsed_url.query)\n    uid = query_params['uid'][0]\n    live_id = query_params['id'][0]\n    params = {\n        'uid': uid,\n        'id': live_id,\n        '_t': str(int(time.time())),\n    }\n\n    api = f'https://webapi.busi.inke.cn/web/live_share_pc?{urllib.parse.urlencode(params)}'\n    json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    anchor_name = json_data['data']['media_info']['nick']\n    live_status = json_data['data']['status']\n\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if live_status == 1:\n        m3u8_url = json_data['data']['live_addr'][0]['hls_stream_addr']\n        flv_url = json_data['data']['live_addr'][0]['stream_addr']\n        result |= {'is_live': True, 'm3u8_url': m3u8_url, 'flv_url': flv_url, 'record_url': m3u8_url}\n    return result\n\n\n@trace_error_decorator\nasync def get_yinbo_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n        'Accept': 'application/json, text/plain, */*',\n        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n        'Referer': 'https://live.ybw1666.com/800005143?promoters=0',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    room_id = url.split('?')[0].rsplit('/', maxsplit=1)[-1]\n    params = {\n        'roomidx': room_id,\n        'currentUrl': f'https://wap.ybw1666.com/{room_id}',\n    }\n    play_api = f'https://wap.ybw1666.com/api/ui/room/v1.0.0/live.ashx?{urllib.parse.urlencode(params)}'\n    json_str = await async_req(play_api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    room_data = json_data['data']['roomInfo']\n    anchor_name = room_data['nickname']\n    live_status = room_data['live_stat']\n\n    async def get_live_domain(page_url):\n        html_str = await async_req(page_url, proxy_addr=proxy_addr, headers=headers)\n        config_json_str = re.findall(\"var config = (.*?)config.webskins\",\n                                     html_str, re.DOTALL)[0].rsplit(\";\", maxsplit=1)[0].strip()\n        config_json_data = json.loads(config_json_str)\n        stream_flv_domain = config_json_data['domainpullstream_flv']\n        stream_hls_domain = config_json_data['domainpullstream_hls']\n        return stream_flv_domain, stream_hls_domain\n\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if live_status == 1:\n        flv_domain, hls_domain = await get_live_domain(url)\n        live_id = room_data['liveID']\n        flv_url = f'{flv_domain}/{live_id}.flv'\n        m3u8_url = f'{hls_domain}/{live_id}.m3u8'\n        result |= {'is_live': True, 'm3u8_url': m3u8_url, 'flv_url': flv_url, 'record_url': flv_url}\n    return result\n\n\n@trace_error_decorator\nasync def get_zhihu_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    if 'people/' in url:\n        user_id = url.split('people/')[1]\n        api = f'https://api.zhihu.com/people/{user_id}/profile?profile_new_version='\n        json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers)\n        json_data = json.loads(json_str)\n        live_page_url = json_data['drama']['living_theater']['theater_url']\n    else:\n        live_page_url = url\n\n    web_id = live_page_url.split('?')[0].rsplit('/', maxsplit=1)[-1]\n    html_str = await async_req(live_page_url, proxy_addr=proxy_addr, headers=headers)\n    json_str2 = re.findall('<script id=\"js-initialData\" type=\"text/json\">(.*?)</script>', html_str)[0]\n    json_data2 = json.loads(json_str2)\n    live_data = json_data2['initialState']['theater']['theaters'][web_id]\n    anchor_name = live_data['actor']['name']\n    live_status = live_data['drama']['status']\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if live_status == 1:\n        live_title = live_data['theme']\n        play_url = live_data['drama']['playInfo']\n        result |= {\n            'is_live': True,\n            'title': live_title,\n            'm3u8_url': play_url['hlsUrl'],\n            'flv_url': play_url['playUrl'],\n            'record_url': play_url['hlsUrl']\n        }\n    return result\n\n\n@trace_error_decorator\nasync def get_chzzk_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'accept': 'application/json, text/plain, */*',\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'origin': 'https://chzzk.naver.com',\n        'referer': 'https://chzzk.naver.com/live/458f6ec20b034f49e0fc6d03921646d2',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    room_id = url.split('?')[0].rsplit('/', maxsplit=1)[-1]\n    play_api = f'https://api.chzzk.naver.com/service/v3/channels/{room_id}/live-detail'\n    json_str = await async_req(play_api, proxy_addr=proxy_addr, headers=headers, abroad=True)\n    json_data = json.loads(json_str)\n    live_data = json_data['content']\n    anchor_name = live_data['channel']['channelName']\n    live_status = live_data['status']\n\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if live_status == 'OPEN':\n        play_data = json.loads(live_data['livePlaybackJson'])\n        m3u8_url = play_data['media'][0]['path']\n        m3u8_url_list = await get_play_url_list(m3u8_url, proxy=proxy_addr, header=headers, abroad=True)\n        prefix = m3u8_url.split('?')[0].rsplit('/', maxsplit=1)[0]\n        m3u8_url_list = [prefix + '/' + i for i in m3u8_url_list]\n        result |= {\"is_live\": True, \"m3u8_url\": m3u8_url, \"play_url_list\": m3u8_url_list}\n    return result\n\n\n@trace_error_decorator\nasync def get_haixiu_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'origin': 'https://www.haixiutv.com',\n        'referer': 'https://www.haixiutv.com/',\n        'user-agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n    }\n    if cookies:\n        headers['Cookie'] = cookies\n\n    room_id = url.split(\"?\")[0].rsplit('/', maxsplit=1)[-1]\n    if 'haixiutv' in url:\n        access_token = \"pLXSC%252FXJ0asc1I21tVL5FYZhNJn2Zg6d7m94umCnpgL%252BuVm31GQvyw%253D%253D\"\n    else:\n        access_token = \"s7FUbTJ%252BjILrR7kicJUg8qr025ZVjd07DAnUQd8c7g%252Fo4OH9pdSX6w%253D%253D\"\n\n    params = {\n        \"accessToken\": access_token,\n        \"tku\": \"3000006\",\n        \"c\": \"10138100100000\",\n        \"_st1\": int(time.time() * 1000)\n    }\n    ajax_data = execjs.compile(open(f'{JS_SCRIPT_PATH}/haixiu.js').read()).call('sign', params,\n                                                                                f'{JS_SCRIPT_PATH}/crypto-js.min.js')\n\n    params[\"accessToken\"] = urllib.parse.unquote(urllib.parse.unquote(access_token))\n    params['_ajaxData1'] = ajax_data\n    params['_'] = int(time.time() * 1000)\n\n    if 'haixiutv' in url:\n        api = f'https://service.haixiutv.com/v2/room/{room_id}/media/advanceInfoRoom?{urllib.parse.urlencode(params)}'\n    else:\n        headers['origin'] = 'https://www.lehaitv.com'\n        headers['referer'] = 'https://www.lehaitv.com'\n        api = f'https://service.lehaitv.com/v2/room/{room_id}/media/advanceInfoRoom?{urllib.parse.urlencode(params)}'\n\n    json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers, abroad=True)\n    json_data = json.loads(json_str)\n\n    stream_data = json_data['data']\n    anchor_name = stream_data['nickname']\n    live_status = stream_data['live_status']\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if live_status == 1:\n        flv_url = stream_data['media_url_web']\n        result |= {'is_live': True, 'flv_url': flv_url, 'record_url': flv_url}\n    return result\n\n\n@trace_error_decorator\nasync def get_vvxqiu_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n        'Access-Control-Request-Method': 'GET',\n        'Origin': 'https://h5webcdn-pro.vvxqiu.com',\n        'Referer': 'https://h5webcdn-pro.vvxqiu.com/',\n    }\n\n    if cookies:\n        headers['Cookie'] = cookies\n\n    room_id = get_params(url, \"roomId\")\n    api_1 = f'https://h5p.vvxqiu.com/activity-center/fanclub/activity/captain/banner?roomId={room_id}&product=vvstar'\n    json_str = await async_req(api_1, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    anchor_name = json_data['data']['anchorName']\n    if not anchor_name:\n        params = {\n            'sessionId': '',\n            'userId': '',\n            'product': 'vvstar',\n            'tickToken': '',\n            'roomId': room_id,\n        }\n        json_str = await async_req(\n            f'https://h5p.vvxqiu.com/activity-center/halloween2023/banner?{urllib.parse.urlencode(params)}',\n            proxy_addr=proxy_addr, headers=headers\n        )\n        json_data = json.loads(json_str)\n        anchor_name = json_data['data']['memberVO']['memberName']\n\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    m3u8_url = f'https://liveplay-pro.wasaixiu.com/live/1400442770_{room_id}_{room_id[2:]}_single.m3u8'\n    resp = await async_req(m3u8_url, proxy_addr=proxy_addr, headers=headers)\n    if 'Not Found' not in resp:\n        result |= {'is_live': True, 'm3u8_url': m3u8_url, 'record_url': m3u8_url}\n    return result\n\n\n@trace_error_decorator\nasync def get_17live_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'origin': 'https://17.live',\n        'referer': 'https://17.live/',\n        'user-agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n    }\n\n    if cookies:\n        headers['Cookie'] = cookies\n\n    room_id = url.split('?')[0].rsplit('/', maxsplit=1)[-1]\n    api_1 = f'https://wap-api.17app.co/api/v1/user/room/{room_id}'\n    json_str = await async_req(api_1, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    anchor_name = json_data[\"displayName\"]\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    json_data = {\n        'liveStreamID': room_id,\n    }\n    api_1 = f'https://wap-api.17app.co/api/v1/lives/{room_id}/viewers/alive'\n    json_str = await async_req(api_1, json_data=json_data, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    live_status = json_data.get(\"status\")\n    if live_status and live_status == 2:\n        flv_url = json_data['pullURLsInfo']['rtmpURLs'][0]['urlHighQuality']\n        result |= {'is_live': True, 'flv_url': flv_url, 'record_url': flv_url}\n    return result\n\n\n@trace_error_decorator\nasync def get_langlive_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'origin': 'https://www.lang.live',\n        'referer': 'https://www.lang.live/',\n        'user-agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n    }\n\n    if cookies:\n        headers['Cookie'] = cookies\n\n    room_id = url.split('?')[0].rsplit('/', maxsplit=1)[-1]\n    api_1 = f'https://api.lang.live/langweb/v1/room/liveinfo?room_id={room_id}'\n    json_str = await async_req(api_1, proxy_addr=proxy_addr, headers=headers, abroad=True)\n    json_data = json.loads(json_str)\n    live_info = json_data['data']['live_info']\n    anchor_name = live_info['nickname']\n    live_status = live_info['live_status']\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if live_status == 1:\n        flv_url = json_data['data']['live_info']['liveurl']\n        m3u8_url = json_data['data']['live_info']['liveurl_hls']\n        result |= {'is_live': True, 'm3u8_url': m3u8_url, 'flv_url': flv_url, 'record_url': m3u8_url}\n    return result\n\n\n@trace_error_decorator\nasync def get_pplive_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'Content-Type': 'application/json',\n        'Origin': 'https://m.pp.weimipopo.com',\n        'Referer': 'https://m.pp.weimipopo.com/',\n        'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n    }\n\n    if cookies:\n        headers['Cookie'] = cookies\n\n    room_id = get_params(url, 'anchorUid')\n    json_data = {\n        'inviteUuid': '',\n        'anchorUuid': room_id,\n    }\n\n    if 'catshow' in url:\n        api = 'https://api.catshow168.com/live/preview'\n        headers['Origin'] = 'https://h.catshow168.com'\n        headers['Referer'] = 'https://h.catshow168.com'\n    else:\n        api = 'https://api.pp.weimipopo.com/live/preview'\n    json_str = await async_req(api, json_data=json_data, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    live_info = json_data['data']\n    anchor_name = live_info['name']\n    live_status = live_info['living']\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if live_status:\n        m3u8_url = live_info['pullUrl']\n        result |= {'is_live': True, 'm3u8_url': m3u8_url, 'record_url': m3u8_url}\n    return result\n\n\n@trace_error_decorator\nasync def get_6room_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'referer': 'https://ios.6.cn/?ver=8.0.3&build=4',\n        'user-agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n    }\n\n    if cookies:\n        headers['Cookie'] = cookies\n\n    room_id = url.split('?')[0].rsplit('/', maxsplit=1)[1]\n    html_str = await async_req(f'https://v.6.cn/{room_id}', proxy_addr=proxy_addr, headers=headers)\n    room_id = re.search('rid: \\'(.*?)\\',\\n\\\\s+roomid', html_str).group(1)\n    data = {\n        'av': '3.1',\n        'encpass': '',\n        'logiuid': '',\n        'project': 'v6iphone',\n        'rate': '1',\n        'rid': '',\n        'ruid': room_id,\n    }\n    api = 'https://v.6.cn/coop/mobile/index.php?padapi=coop-mobile-inroom.php'\n    json_str = await async_req(api, data=data, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    flv_title = json_data['content']['liveinfo']['flvtitle']\n    anchor_name = json_data['content']['roominfo']['alias']\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if flv_title:\n        flv_url = f'https://wlive.6rooms.com/httpflv/{flv_title}.flv'\n        result |= {'is_live': True, 'flv_url': flv_url, 'record_url': flv_url}\n    return result\n\n\n@trace_error_decorator\nasync def get_shopee_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'accept': 'application/json, text/plain, */*',\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'referer': 'https://live.shopee.sg/share?from=live&session=802458&share_user_id=',\n        'user-agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n    }\n\n    if cookies:\n        headers['Cookie'] = cookies\n\n    result = {\"anchor_name\": \"\", \"is_live\": False}\n    is_living = False\n\n    if 'live.shopee' not in url and 'uid' not in url:\n        url = await async_req(url, proxy_addr=proxy_addr, headers=headers, redirect_url=True, abroad=True)\n\n    if 'live.shopee' in url:\n        host_suffix = url.split('/')[2].rsplit('.', maxsplit=1)[1]\n        is_living = get_params(url, 'uid') is None\n    else:\n        host_suffix = url.split('/')[2].split('.', maxsplit=1)[0]\n\n    uid = get_params(url, 'uid')\n    api_host = f'https://live.shopee.{host_suffix}'\n    session_id = get_params(url, 'session')\n    if uid:\n        json_str = await async_req(f'{api_host}/api/v1/shop_page/live/ongoing?uid={uid}',\n                           proxy_addr=proxy_addr, headers=headers, abroad=True)\n        json_data = json.loads(json_str)\n        if json_data['data']['ongoing_live']:\n            session_id = json_data['data']['ongoing_live']['session_id']\n            is_living = True\n        else:\n            json_str = await async_req(f'{api_host}/api/v1/shop_page/live/replay_list?offset=0&limit=1&uid={uid}',\n                               proxy_addr=proxy_addr, headers=headers, abroad=True)\n            json_data = json.loads(json_str)\n            if json_data['data']['replay']:\n                result['anchor_name'] = json_data['data']['replay'][0]['nick_name']\n                return result\n\n    json_str = await async_req(f'{api_host}/api/v1/session/{session_id}', proxy_addr=proxy_addr, headers=headers, abroad=True)\n    json_data = json.loads(json_str)\n    if not json_data.get('data'):\n        print(\"Fetch shopee live data failed, please update the address of the live broadcast room and try again.\")\n        return result\n    uid = json_data['data']['session']['uid']\n    anchor_name = json_data['data']['session']['nickname']\n    live_status = json_data['data']['session']['status']\n    result[\"anchor_name\"] = anchor_name\n    result['uid'] = f'uid={uid}&session={session_id}'\n    if live_status == 1 and is_living:\n        flv_url = json_data['data']['session']['play_url']\n        title = json_data['data']['session']['title']\n        result |= {'is_live': True, 'title': title, 'flv_url': flv_url, 'record_url': flv_url}\n    return result\n\n\n@trace_error_decorator\nasync def get_youtube_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',\n    }\n\n    if cookies:\n        headers['Cookie'] = cookies\n\n    html_str = await async_req(url, proxy_addr=proxy_addr, headers=headers, abroad=True)\n    json_str = re.search('var ytInitialPlayerResponse = (.*?);var meta = document\\\\.createElement', html_str).group(1)\n    json_data = json.loads(json_str)\n    result = {\"anchor_name\": \"\", \"is_live\": False}\n    if 'videoDetails' not in json_data:\n        print(\"Error: Please log in to YouTube on your device's webpage and configure cookies in the config.ini\")\n        return result\n    result['anchor_name'] = json_data['videoDetails']['author']\n    live_status = json_data['videoDetails'].get('isLive')\n    if live_status:\n        live_title = json_data['videoDetails']['title']\n        m3u8_url = json_data['streamingData'][\"hlsManifestUrl\"]\n        play_url_list = await get_play_url_list(m3u8_url, proxy=proxy_addr, header=headers, abroad=True)\n        result |= {\"is_live\": True, \"title\": live_title, \"m3u8_url\": m3u8_url, \"play_url_list\": play_url_list}\n    return result\n\n\n@trace_error_decorator\nasync def get_taobao_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'Referer': 'https://huodong.m.taobao.com/',\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',\n        'Cookie': '',\n    }\n\n    if cookies:\n        headers['Cookie'] = cookies\n\n    if '_m_h5_tk' not in headers['Cookie']:\n        print('Error: Cookies is empty! please input correct cookies')\n\n    live_id = get_params(url, 'id')\n    if not live_id:\n        html_str = await async_req(url, proxy_addr=proxy_addr, headers=headers)\n        redirect_url = re.findall(\"var url = '(.*?)';\", html_str)[0]\n        live_id = get_params(redirect_url, 'id')\n\n    params = {\n        'jsv': '2.7.0',\n        'appKey': '12574478',\n        't': '1733104933120',\n        'sign': '',\n        'AntiFlood': 'true',\n        'AntiCreep': 'true',\n        'api': 'mtop.mediaplatform.live.livedetail',\n        'v': '4.0',\n        'preventFallback': 'true',\n        'type': 'jsonp',\n        'dataType': 'jsonp',\n        'callback': 'mtopjsonp1',\n        'data': '{\"liveId\":\"' + live_id + '\",\"creatorId\":null}',\n    }\n\n    for i in range(2):\n        app_key = '12574478'\n        _m_h5_tk = re.findall('_m_h5_tk=(.*?);', headers['Cookie'])[0]\n        t13 = int(time.time() * 1000)\n        pre_sign_str = f'{_m_h5_tk.split(\"_\")[0]}&{t13}&{app_key}&' + params['data']\n        sign = execjs.compile(open(f'{JS_SCRIPT_PATH}/taobao-sign.js').read()).call('sign', pre_sign_str)\n        params |= {'sign': sign, 't': t13}\n        api = f'https://h5api.m.taobao.com/h5/mtop.mediaplatform.live.livedetail/4.0/?{urllib.parse.urlencode(params)}'\n        jsonp_str, new_cookie = await async_req(url=api, proxy_addr=proxy_addr, headers=headers, timeout=20,\n                                                return_cookies=True, include_cookies=True)\n        json_data = utils.jsonp_to_json(jsonp_str)\n\n        ret_msg = json_data['ret']\n        if ret_msg == ['SUCCESS::调用成功']:\n            anchor_name = json_data['data']['broadCaster']['accountName']\n            result = {\"anchor_name\": anchor_name, \"is_live\": False}\n            live_status = json_data['data']['streamStatus']\n            if live_status == '1':\n                live_title = json_data['data']['title']\n                play_url_list = json_data['data']['liveUrlList']\n\n                def get_sort_key(item):\n                    definition_priority = {\n                        \"lld\": 0, \"ld\": 1, \"md\": 2, \"hd\": 3, \"ud\": 4\n                    }\n                    def_value = item.get('definition') or item.get('newDefinition')\n                    priority = definition_priority.get(def_value, -1)\n                    return priority\n\n                play_url_list = sorted(play_url_list, key=get_sort_key, reverse=True)\n                result |= {\"is_live\": True, \"title\": live_title, \"play_url_list\": play_url_list, 'live_id': live_id}\n\n            return result\n        else:\n            print(f'Error: Taobao live data fetch failed, {ret_msg[0]}')\n\n        if '_m_h5_tk' in new_cookie and '_m_h5_tk_enc' in new_cookie:\n            headers['Cookie'] = re.sub('_m_h5_tk=(.*?);', new_cookie['_m_h5_tk'], headers['Cookie'])\n            headers['Cookie'] = re.sub('_m_h5_tk_enc=(.*?);', new_cookie['_m_h5_tk_enc'], headers['Cookie'])\n        else:\n            print('Error: Try to update cookie failed, please update the cookies in the configuration file')\n\n\n@trace_error_decorator\nasync def get_jd_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))',\n        'origin': 'https://lives.jd.com',\n        'referer': 'https://lives.jd.com/',\n        'x-referer-page': 'https://lives.jd.com/',\n    }\n\n    if cookies:\n        headers['Cookie'] = cookies\n\n    redirect_url = await async_req(url, proxy_addr=proxy_addr, headers=headers, redirect_url=True)\n    author_id = get_params(redirect_url, 'authorId')\n    result = {\"anchor_name\": '', \"is_live\": False}\n    if not author_id:\n        live_id = re.search('#/(.*?)\\\\?origin', redirect_url)\n        if not live_id:\n            return result\n        live_id = live_id.group(1)\n        result['anchor_name'] = f'jd_{live_id}'\n    else:\n        data = {\n            'functionId': 'talent_head_findTalentMsg',\n            'appid': 'dr_detail',\n            'body': '{\"authorId\":\"' + author_id + '\",\"monitorSource\":\"1\",\"userId\":\"\"}',\n        }\n        info_api = 'https://api.m.jd.com/talent_head_findTalentMsg'\n        json_str = await async_req(info_api, data=data, proxy_addr=proxy_addr, headers=headers)\n        json_data = json.loads(json_str)\n        anchor_name = json_data['result']['talentName']\n        result['anchor_name'] = anchor_name\n        if 'livingRoomJump' not in json_data['result']:\n            return result\n        live_id = json_data['result']['livingRoomJump']['params']['id']\n    params = {\n        \"body\": '{\"liveId\": \"' + live_id + '\"}',\n        \"functionId\": \"getImmediatePlayToM\",\n        \"appid\": \"h5-live\"\n    }\n\n    api = f'https://api.m.jd.com/client.action?{urllib.parse.urlencode(params)}'\n    # backup_api: https://api.m.jd.com/api\n    json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    live_status = json_data['data']['status']\n    if live_status == 1:\n        if author_id:\n            data = {\n                'functionId': 'jdTalentContentList',\n                'appid': 'dr_detail',\n                'body': '{\"authorId\":\"' + author_id + '\",\"type\":1,\"userId\":\"\",\"page\":1,\"offset\":\"-1\",'\n                                                      '\"monitorSource\":\"1\",\"pageSize\":1}',\n            }\n            json_str2 = await async_req('https://api.m.jd.com/jdTalentContentList', data=data,\n                                proxy_addr=proxy_addr, headers=headers)\n            json_data2 = json.loads(json_str2)\n            result['title'] = json_data2['result']['content'][0]['title']\n\n        flv_url = json_data['data']['videoUrl']\n        m3u8_url = json_data['data']['h5VideoUrl']\n        result |= {\"is_live\": True, \"m3u8_url\": m3u8_url, \"flv_url\": flv_url, \"record_url\": m3u8_url}\n    return result\n\n\n@trace_error_decorator\nasync def get_faceit_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n        'Referer': 'https://www.faceit.com/zh/players/qpjzz/stream',\n        'faceit-referer': 'web-next',\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',\n    }\n\n    if cookies:\n        headers['Cookie'] = cookies\n    nickname = re.findall('/players/(.*?)/stream', url)[0]\n    api = f'https://www.faceit.com/api/users/v1/nicknames/{nickname}'\n    json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    user_id = json_data['payload']['id']\n    api2 = f'https://www.faceit.com/api/stream/v1/streamings?userId={user_id}'\n    json_str2 = await async_req(api2, proxy_addr=proxy_addr, headers=headers)\n    json_data2 = json.loads(json_str2)\n    platform_info = json_data2['payload'][0]\n    anchor_name = platform_info.get('userNickname')\n    anchor_id = platform_info.get('platformId')\n    platform = platform_info.get('platform')\n    if platform == 'twitch':\n        result = await get_twitchtv_stream_data(f'https://www.twitch.tv/{anchor_id}')\n        result['anchor_name'] = anchor_name\n    else:\n        result = {'anchor_name': anchor_name, 'is_live': False}\n    return result\n\n\n@trace_error_decorator\nasync def get_migu_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n            'origin': 'https://www.miguvideo.com',\n            'referer': 'https://www.miguvideo.com/',\n            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\n                          'Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0',\n            'appCode': 'miguvideo_default_www',\n            'appId': 'miguvideo',\n            'channel': 'H5',\n        }\n\n    if cookies:\n        headers['Cookie'] = cookies\n\n    web_id = url.split('?')[0].rsplit('/')[-1]\n    api = f'https://vms-sc.miguvideo.com/vms-match/v6/staticcache/basic/basic-data/{web_id}/miguvideo'\n    json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n\n    anchor_name = json_data['body']['title']\n    live_title = json_data['body'].get('title') + '-' + json_data['body'].get('detailPageTitle', '')\n    room_id = json_data['body'].get('pId')\n\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if not room_id:\n        return result\n\n    params = {\n        'contId': room_id,\n        'rateType': '3',\n        'clientId': str(uuid.uuid4()),\n        'timestamp': int(time.time() * 1000),\n        'flvEnable': 'true',\n        'xh265': 'false',\n        'chip': 'mgwww',\n        'channelId': '',\n    }\n\n    api = f'https://webapi.miguvideo.com/gateway/playurl/v3/play/playurl?{urllib.parse.urlencode(params)}'\n    json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n    live_status = json_data['body']['content']['currentLive']\n    if live_status != '1':\n        return result\n    else:\n        result['title'] = live_title\n        source_url = json_data['body']['urlInfo']['url']\n\n        async def _get_dd_calcu(url):\n            try:\n                result = subprocess.run(\n                    [\"node\", f\"{JS_SCRIPT_PATH}/migu.js\", url],\n                    capture_output=True,\n                    text=True,\n                    check=True\n                )\n                return result.stdout.strip()\n            except execjs.ProgramError:\n                raise execjs.ProgramError('Failed to execute JS code. Please check if the Node.js environment')\n\n        ddCalcu = await _get_dd_calcu(source_url)\n        real_source_url = f'{source_url}&ddCalcu={ddCalcu}&sv=10010'\n        if '.m3u8' in real_source_url:\n            m3u8_url = await async_req(\n                real_source_url, proxy_addr=proxy_addr, headers=headers, redirect_url=True)\n            result['m3u8_url'] = m3u8_url\n            result['record_url'] = m3u8_url\n        else:\n            result['flv_url'] = real_source_url\n            result['record_url'] = real_source_url\n        result['is_live'] = True\n    return result\n\n\n@trace_error_decorator\nasync def get_lianjie_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\n                          'Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0',\n            'accept-language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n        }\n\n    if cookies:\n        headers['cookie'] = cookies\n\n    room_id = url.split('?')[0].rsplit('lailianjie.com/', maxsplit=1)[-1]\n    play_api = f'https://api.lailianjie.com/ApiServices/service/live/getRoomInfo?&_$t=&_sign=&roomNumber={room_id}'\n    json_str = await async_req(play_api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n\n    room_data = json_data['data']\n    anchor_name = room_data['nickname']\n    live_status = room_data['isonline']\n\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if live_status == 1:\n        title = room_data['defaultRoomTitle']\n        webrtc_url = room_data['videoUrl']\n        https_url = \"https://\" + webrtc_url.split('webrtc://')[1]\n        flv_url = https_url.replace('?', '.flv?')\n        m3u8_url = https_url.replace('?', '.m3u8?')\n        result |= {'is_live': True, 'title': title, 'm3u8_url': m3u8_url, 'flv_url': flv_url, 'record_url': flv_url}\n    return result\n\n\n@trace_error_decorator\nasync def get_laixiu_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    def generate_uuid(ua_type: str):\n        if ua_type == \"mobile\":\n            return str(uuid.uuid4())\n        return str(uuid.uuid4()).replace('-', '')\n\n    def calculate_sign(ua_type: str = 'pc'):\n        a = int(time.time() * 1000)\n        s = generate_uuid(ua_type)\n        u = 'kk792f28d6ff1f34ec702c08626d454b39pro'\n\n        input_str = f\"web{s}{a}{u}\"\n        md5_hash = hashlib.md5(input_str.encode('utf-8')).hexdigest()\n\n        return {\n            'timestamp': a,\n            'imei': s,\n            'requestId': md5_hash,\n            'inputString': input_str\n        }\n\n    sign_data = calculate_sign(ua_type='pc')\n    headers = {\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\n                      'Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0',\n        'mobileModel': 'web',\n        'timestamp': str(sign_data['timestamp']),\n        'loginType': '2',\n        'versionCode': '10003',\n        'imei': sign_data['imei'],\n        'requestId': sign_data['requestId'],\n        'channel': '9',\n        'version': '1.0.0',\n        'os': 'web',\n        'platform': 'WEB',\n        'Origin': 'https://www.imkktv.com',\n        'Referer': 'https://www.imkktv.com/',\n        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',\n    }\n\n    if cookies:\n        headers['cookie'] = cookies\n\n    pattern = r\"(?:roomId|anchorId)=(.*?)(?=&|$)\"\n    match = re.search(pattern, url)\n    room_id = match.group(1) if match else ''\n    play_api = f'https://api.imkktv.com/liveroom/getShareLiveVideo?roomId={room_id}'\n    json_str = await async_req(play_api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n\n    room_data = json_data['data']\n    anchor_name = room_data['nickname']\n    live_status = room_data['playStatus'] == 0\n\n    result = {\"anchor_name\": anchor_name, \"is_live\": False}\n    if live_status:\n        flv_url = room_data['playUrl']\n        result |= {'is_live': True, 'flv_url': flv_url, 'record_url': flv_url}\n    return result\n\n\n@trace_error_decorator\nasync def get_picarto_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:\n    headers = {\n            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '\n                          'Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0',\n            'accept-language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',\n        }\n\n    if cookies:\n        headers['cookie'] = cookies\n\n    anchor_id = url.split('?')[0].rsplit('/', maxsplit=1)[-1]\n    api = f'https://ptvintern.picarto.tv/api/channel/detail/{anchor_id}'\n\n    json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers)\n    json_data = json.loads(json_str)\n\n    anchor_name = json_data['channel']['name']\n    live_status = json_data['channel']['online']\n\n    result = {\"anchor_name\": anchor_name, \"is_live\": live_status}\n    if live_status:\n        title = json_data['channel']['title']\n        m3u8_url = f\"https://1-edge1-us-newyork.picarto.tv/stream/hls/golive+{anchor_name}/index.m3u8\"\n        result |= {'is_live': True, 'title': title, 'm3u8_url': m3u8_url, 'record_url': m3u8_url}\n    return result"
  },
  {
    "path": "src/stream.py",
    "content": "# -*- encoding: utf-8 -*-\n\n\"\"\"\nAuthor: Hmily\nGitHub: https://github.com/ihmily\nDate: 2023-07-15 23:15:00\nUpdate: 2025-02-06 02:28:00\nCopyright (c) 2023-2025 by Hmily, All Rights Reserved.\nFunction: Get live stream data.\n\"\"\"\nimport base64\nimport hashlib\nimport json\nimport time\nimport random\nimport re\nfrom operator import itemgetter\nimport urllib.parse\nimport urllib.request\nfrom .utils import trace_error_decorator\nfrom .spider import (\n    get_douyu_stream_data, get_bilibili_stream_data\n)\nfrom .http_clients.async_http import get_response_status\n\nQUALITY_MAPPING = {\"OD\": 0, \"BD\": 0, \"UHD\": 1, \"HD\": 2, \"SD\": 3, \"LD\": 4}\n\n\ndef get_quality_index(quality) -> tuple:\n    if not quality:\n        return list(QUALITY_MAPPING.items())[0]\n\n    quality_str = str(quality).upper()\n    if quality_str.isdigit():\n        quality_int = int(quality_str[0])\n        quality_str = list(QUALITY_MAPPING.keys())[quality_int]\n    return quality_str, QUALITY_MAPPING.get(quality_str, 0)\n\n\n@trace_error_decorator\nasync def get_douyin_stream_url(json_data: dict, video_quality: str, proxy_addr: str) -> dict:\n    anchor_name = json_data.get('anchor_name')\n\n    result = {\n        \"anchor_name\": anchor_name,\n        \"is_live\": False,\n    }\n\n    status = json_data.get(\"status\", 4)\n\n    if status == 2:\n        stream_url = json_data['stream_url']\n        flv_url_dict = stream_url['flv_pull_url']\n        flv_url_list: list = list(flv_url_dict.values())\n        m3u8_url_dict = stream_url['hls_pull_url_map']\n        m3u8_url_list: list = list(m3u8_url_dict.values())\n\n        while len(flv_url_list) < 5:\n            flv_url_list.append(flv_url_list[-1])\n            m3u8_url_list.append(m3u8_url_list[-1])\n\n        video_quality, quality_index = get_quality_index(video_quality)\n        m3u8_url = m3u8_url_list[quality_index]\n        flv_url = flv_url_list[quality_index]\n        ok = await get_response_status(url=m3u8_url, proxy_addr=proxy_addr)\n        if not ok:\n            index = quality_index + 1 if quality_index < 4 else quality_index - 1\n            m3u8_url = m3u8_url_list[index]\n            flv_url = flv_url_list[index]\n        result |= {\n            'is_live': True,\n            'title': json_data['title'],\n            'quality': video_quality,\n            'm3u8_url': m3u8_url,\n            'flv_url': flv_url,\n            'record_url': m3u8_url or flv_url,\n        }\n    return result\n\n\n@trace_error_decorator\nasync def get_tiktok_stream_url(json_data: dict, video_quality: str, proxy_addr: str) -> dict:\n    if not json_data:\n        return {\"anchor_name\": None, \"is_live\": False}\n\n    def get_video_quality_url(stream, q_key) -> list:\n        play_list = []\n        for key in stream:\n            url_info = stream[key]['main']\n            sdk_params = url_info['sdk_params']\n            sdk_params = json.loads(sdk_params)\n            vbitrate = int(sdk_params['vbitrate'])\n            v_codec = sdk_params.get('VCodec', '')\n\n            play_url = ''\n            if url_info.get(q_key):\n                if url_info[q_key].endswith(\".flv\") or url_info[q_key].endswith(\".m3u8\"):\n                    play_url = url_info[q_key] + '?codec=' + v_codec\n                else:\n                    play_url = url_info[q_key] + '&codec=' + v_codec\n\n            resolution = sdk_params['resolution']\n            if vbitrate != 0 and resolution:\n                width, height = map(int, resolution.split('x'))\n                play_list.append({'url': play_url, 'vbitrate': vbitrate, 'resolution': (width, height)})\n\n        play_list.sort(key=itemgetter('vbitrate'), reverse=True)\n        play_list.sort(key=lambda x: (-x['vbitrate'], -x['resolution'][0], -x['resolution'][1]))\n        return play_list\n\n    live_room = json_data['LiveRoom']['liveRoomUserInfo']\n    user = live_room['user']\n    anchor_name = f\"{user['nickname']}-{user['uniqueId']}\"\n    status = user.get(\"status\", 4)\n\n    result = {\n        \"anchor_name\": anchor_name,\n        \"is_live\": False,\n    }\n\n    if status == 2:\n        stream_data = live_room['liveRoom']['streamData']['pull_data']['stream_data']\n        stream_data = json.loads(stream_data).get('data', {})\n        flv_url_list = get_video_quality_url(stream_data, 'flv')\n        m3u8_url_list = get_video_quality_url(stream_data, 'hls')\n\n        while len(flv_url_list) < 5:\n            flv_url_list.append(flv_url_list[-1])\n        while len(m3u8_url_list) < 5:\n            m3u8_url_list.append(m3u8_url_list[-1])\n        video_quality, quality_index = get_quality_index(video_quality)\n        flv_dict: dict = flv_url_list[quality_index]\n        m3u8_dict: dict = m3u8_url_list[quality_index]\n\n        check_url = m3u8_dict.get('url') or flv_dict.get('url')\n        ok = await get_response_status(url=check_url, proxy_addr=proxy_addr, http2=False)\n\n        if not ok:\n            index = quality_index + 1 if quality_index < 4 else quality_index - 1\n            flv_dict: dict = flv_url_list[index]\n            m3u8_dict: dict = m3u8_url_list[index]\n\n        flv_url = flv_dict['url']\n        m3u8_url = m3u8_dict['url']\n        result |= {\n            'is_live': True,\n            'title': live_room['liveRoom']['title'],\n            'quality': video_quality,\n            'm3u8_url': m3u8_url,\n            'flv_url': flv_url,\n            'record_url': m3u8_url or flv_url,\n        }\n    return result\n\n\n@trace_error_decorator\nasync def get_kuaishou_stream_url(json_data: dict, video_quality: str) -> dict:\n    if json_data['type'] == 1 and not json_data[\"is_live\"]:\n        return json_data\n    live_status = json_data['is_live']\n\n    result = {\n        \"type\": 2,\n        \"anchor_name\": json_data['anchor_name'],\n        \"is_live\": live_status,\n    }\n\n    if live_status:\n        quality_mapping_bit = {'OD': 99999, 'BD': 4000, 'UHD': 2000, 'HD': 1000, 'SD': 800, 'LD': 600}\n        if video_quality in QUALITY_MAPPING:\n\n            quality, quality_index = get_quality_index(video_quality)\n            if 'm3u8_url_list' in json_data:\n                m3u8_url_list = json_data['m3u8_url_list'][::-1]\n                while len(m3u8_url_list) < 5:\n                    m3u8_url_list.append(m3u8_url_list[-1])\n                m3u8_url = m3u8_url_list[quality_index]['url']\n                result['m3u8_url'] = m3u8_url\n\n            if 'flv_url_list' in json_data:\n                if 'bitrate' in json_data['flv_url_list'][0]:\n                    flv_url_list = json_data['flv_url_list']\n                    flv_url_list = sorted(flv_url_list, key=lambda x: x['bitrate'], reverse=True)\n                    quality_str = str(video_quality).upper()\n                    if quality_str.isdigit():\n                        video_quality, quality_index_bitrate_value = list(quality_mapping_bit.items())[int(quality_str)]\n                    else:\n                        quality_index_bitrate_value = quality_mapping_bit.get(quality_str, 99999)\n                        video_quality = quality_str\n                    quality_index = next(\n                        (i for i, x in enumerate(flv_url_list) if x['bitrate'] <= quality_index_bitrate_value), None)\n                    if quality_index is None:\n                        quality_index = len(flv_url_list) - 1\n                    flv_url = flv_url_list[quality_index]['url']\n\n                    result['flv_url'] = flv_url\n                    result['record_url'] = flv_url\n                else:\n                    flv_url_list = json_data['flv_url_list'][::-1]\n                    while len(flv_url_list) < 5:\n                        flv_url_list.append(flv_url_list[-1])\n                    flv_url = flv_url_list[quality_index]['url']\n                    result |= {'flv_url': flv_url, 'record_url': flv_url}\n            result['is_live'] = True\n            result['quality'] = video_quality\n    return result\n\n\n@trace_error_decorator\nasync def get_huya_stream_url(json_data: dict, video_quality: str) -> dict:\n    game_live_info = json_data['data'][0]['gameLiveInfo']\n    live_title = game_live_info['introduction']\n    stream_info_list = json_data['data'][0]['gameStreamInfoList']\n    anchor_name = game_live_info.get('nick', '')\n\n    result = {\n        \"anchor_name\": anchor_name,\n        \"is_live\": False,\n    }\n\n    if stream_info_list:\n        select_cdn = stream_info_list[0]\n        flv_url = select_cdn.get('sFlvUrl')\n        stream_name = select_cdn.get('sStreamName')\n        flv_url_suffix = select_cdn.get('sFlvUrlSuffix')\n        hls_url = select_cdn.get('sHlsUrl')\n        hls_url_suffix = select_cdn.get('sHlsUrlSuffix')\n        flv_anti_code = select_cdn.get('sFlvAntiCode')\n\n        def get_anti_code(old_anti_code: str) -> str:\n\n            # js地址：https://hd.huya.com/cdn_libs/mobile/hysdk-m-202402211431.js\n\n            params_t = 100\n            sdk_version = 2403051612\n\n            # sdk_id是13位数毫秒级时间戳\n            t13 = int(time.time()) * 1000\n            sdk_sid = t13\n\n            # 计算uuid和uid参数值\n            init_uuid = (int(t13 % 10 ** 10 * 1000) + int(1000 * random.random())) % 4294967295  # 直接初始化\n            uid = random.randint(1400000000000, 1400009999999)  # 经过测试uid也可以使用init_uuid代替\n            seq_id = uid + sdk_sid  # 移动端请求的直播流地址中包含seqId参数\n\n            # 计算ws_time参数值(16进制) 可以是当前毫秒时间戳，当然也可以直接使用url_query['wsTime'][0]\n            # 原始最大误差不得慢240000毫秒\n            target_unix_time = (t13 + 110624) // 1000\n            ws_time = f\"{target_unix_time:x}\".lower()\n\n            # fm参数值是经过url编码然后base64编码得到的，解码结果类似 DWq8BcJ3h6DJt6TY_$0_$1_$2_$3\n            # 具体细节在上面js中查看，大概在32657行代码开始，有base64混淆代码请自行替换\n            url_query = urllib.parse.parse_qs(old_anti_code)\n            ws_secret_pf = base64.b64decode(urllib.parse.unquote(url_query['fm'][0]).encode()).decode().split(\"_\")[0]\n            ws_secret_hash = hashlib.md5(f'{seq_id}|{url_query[\"ctype\"][0]}|{params_t}'.encode()).hexdigest()\n            ws_secret = f'{ws_secret_pf}_{uid}_{stream_name}_{ws_secret_hash}_{ws_time}'\n            ws_secret_md5 = hashlib.md5(ws_secret.encode()).hexdigest()\n\n            anti_code = (\n                f'wsSecret={ws_secret_md5}&wsTime={ws_time}&seqid={seq_id}&ctype={url_query[\"ctype\"][0]}&ver=1'\n                f'&fs={url_query[\"fs\"][0]}&uuid={init_uuid}&u={uid}&t={params_t}&sv={sdk_version}'\n                f'&sdk_sid={sdk_sid}&codec=264'\n            )\n            return anti_code\n\n        new_anti_code = get_anti_code(flv_anti_code)\n        flv_url = f'{flv_url}/{stream_name}.{flv_url_suffix}?{new_anti_code}&ratio='\n        m3u8_url = f'{hls_url}/{stream_name}.{hls_url_suffix}?{new_anti_code}&ratio='\n\n        quality_list = flv_anti_code.split('&exsphd=')\n        if len(quality_list) > 1 and video_quality not in [\"OD\", \"BD\"]:\n            pattern = r\"(?<=264_)\\d+\"\n            quality_list = list(re.findall(pattern, quality_list[1]))[::-1]\n            while len(quality_list) < 5:\n                quality_list.append(quality_list[-1])\n\n            video_quality_options = {\n                \"UHD\": quality_list[0],\n                \"HD\": quality_list[1],\n                \"SD\": quality_list[2],\n                \"LD\": quality_list[3]\n            }\n\n            if video_quality not in video_quality_options:\n                raise ValueError(\n                    f\"Invalid video quality. Available options are: {', '.join(video_quality_options.keys())}\")\n\n            flv_url = flv_url + str(video_quality_options[video_quality])\n            m3u8_url = m3u8_url + str(video_quality_options[video_quality])\n\n        result |= {\n            'is_live': True,\n            'title': live_title,\n            'quality': video_quality,\n            'm3u8_url': m3u8_url,\n            'flv_url': flv_url,\n            'record_url': flv_url or m3u8_url\n        }\n    return result\n\n\n@trace_error_decorator\nasync def get_douyu_stream_url(json_data: dict, video_quality: str, cookies: str, proxy_addr: str) -> dict:\n    if not json_data[\"is_live\"]:\n        return json_data\n\n    video_quality_options = {\n        \"OD\": '0',\n        \"BD\": '0',\n        \"UHD\": '3',\n        \"HD\": '2',\n        \"SD\": '1',\n        \"LD\": '1'\n    }\n\n    rid = str(json_data[\"room_id\"])\n    json_data.pop(\"room_id\")\n    rate = video_quality_options.get(video_quality, '0')\n    flv_data = await get_douyu_stream_data(rid, rate, cookies=cookies, proxy_addr=proxy_addr)\n    rtmp_url = flv_data['data'].get('rtmp_url')\n    rtmp_live = flv_data['data'].get('rtmp_live')\n    if rtmp_live:\n        flv_url = f'{rtmp_url}/{rtmp_live}'\n        json_data |= {'quality': video_quality, 'flv_url': flv_url, 'record_url': flv_url}\n    return json_data\n\n\n@trace_error_decorator\nasync def get_yy_stream_url(json_data: dict) -> dict:\n    anchor_name = json_data.get('anchor_name', '')\n    result = {\n        \"anchor_name\": anchor_name,\n        \"is_live\": False,\n    }\n    if 'avp_info_res' in json_data:\n        stream_line_addr = json_data['avp_info_res']['stream_line_addr']\n        cdn_info = list(stream_line_addr.values())[0]\n        flv_url = cdn_info['cdn_info']['url']\n        result |= {\n            'is_live': True,\n            'title': json_data['title'],\n            'quality': 'OD',\n            'flv_url': flv_url,\n            'record_url': flv_url\n        }\n    return result\n\n\n@trace_error_decorator\nasync def get_bilibili_stream_url(json_data: dict, video_quality: str, proxy_addr: str, cookies: str) -> dict:\n    anchor_name = json_data[\"anchor_name\"]\n    if not json_data[\"live_status\"]:\n        return {\n            \"anchor_name\": anchor_name,\n            \"is_live\": False\n        }\n\n    room_url = json_data['room_url']\n\n    video_quality_options = {\n        \"OD\": '10000',\n        \"BD\": '400',\n        \"UHD\": '250',\n        \"HD\": '150',\n        \"SD\": '80',\n        \"LD\": '80'\n    }\n\n    select_quality = video_quality_options[video_quality]\n    play_url = await get_bilibili_stream_data(\n        room_url, qn=select_quality, platform='web', proxy_addr=proxy_addr, cookies=cookies)\n    return {\n        'anchor_name': json_data['anchor_name'],\n        'is_live': True,\n        'title': json_data['title'],\n        'quality': video_quality,\n        'record_url': play_url\n    }\n\n\n@trace_error_decorator\nasync def get_netease_stream_url(json_data: dict, video_quality: str) -> dict:\n    if not json_data['is_live']:\n        return json_data\n\n    m3u8_url = json_data['m3u8_url']\n    flv_url = None\n    if json_data.get('stream_list'):\n        stream_list = json_data['stream_list']['resolution']\n        order = ['blueray', 'ultra', 'high', 'standard']\n        sorted_keys = [key for key in order if key in stream_list]\n        while len(sorted_keys) < 5:\n            sorted_keys.append(sorted_keys[-1])\n        video_quality, quality_index = get_quality_index(video_quality)\n        selected_quality = sorted_keys[quality_index]\n        flv_url_list = stream_list[selected_quality]['cdn']\n        selected_cdn = list(flv_url_list.keys())[0]\n        flv_url = flv_url_list[selected_cdn]\n\n    return {\n        \"is_live\": True,\n        \"anchor_name\": json_data['anchor_name'],\n        \"title\": json_data['title'],\n        'quality': video_quality,\n        \"m3u8_url\": m3u8_url,\n        \"flv_url\": flv_url,\n        \"record_url\": flv_url or m3u8_url\n    }\n\n\nasync def get_stream_url(json_data: dict, video_quality: str, url_type: str = 'm3u8', spec: bool = False,\n                         hls_extra_key: str | int = None, flv_extra_key: str | int = None) -> dict:\n    if not json_data['is_live']:\n        return json_data\n\n    play_url_list = json_data['play_url_list']\n    while len(play_url_list) < 5:\n        play_url_list.append(play_url_list[-1])\n\n    video_quality, selected_quality = get_quality_index(video_quality)\n    data = {\n        \"anchor_name\": json_data['anchor_name'],\n        \"is_live\": True\n    }\n\n    def get_url(key):\n        play_url = play_url_list[selected_quality]\n        return play_url[key] if key else play_url\n\n    if url_type == 'all':\n        m3u8_url = get_url(hls_extra_key)\n        flv_url = get_url(flv_extra_key)\n        data |= {\n            \"m3u8_url\": json_data['m3u8_url'] if spec else m3u8_url,\n            \"flv_url\": json_data['flv_url'] if spec else flv_url,\n            \"record_url\": m3u8_url\n        }\n    elif url_type == 'm3u8':\n        m3u8_url = get_url(hls_extra_key)\n        data |= {\"m3u8_url\": json_data['m3u8_url'] if spec else m3u8_url, \"record_url\": m3u8_url}\n    else:\n        flv_url = get_url(flv_extra_key)\n        data |= {\"flv_url\": flv_url, \"record_url\": flv_url}\n    data['title'] = json_data.get('title')\n    data['quality'] = video_quality\n    return data"
  },
  {
    "path": "src/utils.py",
    "content": "# -*- coding: utf-8 -*-\nimport json\nimport os\nimport random\nimport shutil\nimport string\nfrom pathlib import Path\nimport functools\nimport hashlib\nimport re\nimport traceback\nfrom typing import Any\nfrom urllib.parse import parse_qs, urlparse\nfrom collections import OrderedDict\nimport execjs\nfrom .logger import logger\nimport configparser\n\nOptionalStr = str | None\nOptionalDict = dict | None\n\n\nclass Color:\n    RED = \"\\033[31m\"\n    GREEN = \"\\033[32m\"\n    YELLOW = \"\\033[33m\"\n    BLUE = \"\\033[34m\"\n    MAGENTA = \"\\033[35m\"\n    CYAN = \"\\033[36m\"\n    WHITE = \"\\033[37m\"\n    RESET = \"\\033[0m\"\n\n    @staticmethod\n    def print_colored(text, color):\n        print(f\"{color}{text}{Color.RESET}\")\n\n\ndef trace_error_decorator(func: callable) -> callable:\n    @functools.wraps(func)\n    def wrapper(*args: list, **kwargs: dict) -> Any:\n        try:\n            return func(*args, **kwargs)\n        except execjs.ProgramError:\n            logger.warning('Failed to execute JS code. Please check if the Node.js environment')\n        except Exception as e:\n            error_line = traceback.extract_tb(e.__traceback__)[-1].lineno\n            error_info = f\"message: type: {type(e).__name__}, {str(e)} in function {func.__name__} at line: {error_line}\"\n            logger.error(error_info)\n            return []\n\n    return wrapper\n\n\ndef check_md5(file_path: str | Path) -> str:\n    with open(file_path, 'rb') as fp:\n        file_md5 = hashlib.md5(fp.read()).hexdigest()\n    return file_md5\n\n\ndef dict_to_cookie_str(cookies_dict: dict) -> str:\n    cookie_str = '; '.join([f\"{key}={value}\" for key, value in cookies_dict.items()])\n    return cookie_str\n\n\ndef read_config_value(file_path: str | Path, section: str, key: str) -> str | None:\n    config = configparser.ConfigParser()\n\n    try:\n        config.read(file_path, encoding='utf-8-sig')\n    except Exception as e:\n        print(f\"Error occurred while reading the configuration file: {e}\")\n        return None\n\n    if section in config:\n        if key in config[section]:\n            return config[section][key]\n        else:\n            print(f\"Key [{key}] does not exist in section [{section}].\")\n    else:\n        print(f\"Section [{section}] does not exist in the file.\")\n\n    return None\n\n\ndef update_config(file_path: str | Path, section: str, key: str, new_value: str) -> None:\n    config = configparser.ConfigParser()\n\n    try:\n        config.read(file_path, encoding='utf-8-sig')\n    except Exception as e:\n        print(f\"An error occurred while reading the configuration file: {e}\")\n        return\n\n    if section not in config:\n        print(f\"Section [{section}] does not exist in the file.\")\n        return\n\n    # 转义%字符\n    escaped_value = new_value.replace('%', '%%')\n    config[section][key] = escaped_value\n\n    try:\n        with open(file_path, 'w', encoding='utf-8-sig') as configfile:\n            config.write(configfile)\n        print(f\"The value of {key} under [{section}] in the configuration file has been updated.\")\n    except Exception as e:\n        print(f\"Error occurred while writing to the configuration file: {e}\")\n\n\ndef get_file_paths(directory: str) -> list:\n    file_paths = []\n    for root, dirs, files in os.walk(directory):\n        for file in files:\n            file_paths.append(os.path.join(root, file))\n    return file_paths\n\n\ndef remove_emojis(text: str, replace_text: str = '') -> str:\n    emoji_pattern = re.compile(\n        \"[\"\n        \"\\U0001F1E0-\\U0001F1FF\"  # flags (iOS)\n        \"\\U0001F300-\\U0001F5FF\"  # symbols & pictographs\n        \"\\U0001F600-\\U0001F64F\"  # emoticons\n        \"\\U0001F680-\\U0001F6FF\"  # transport & map symbols\n        \"\\U0001F700-\\U0001F77F\"  # alchemical symbols\n        \"\\U0001F780-\\U0001F7FF\"  # Geometric Shapes Extended\n        \"\\U0001F800-\\U0001F8FF\"  # Supplemental Arrows-C\n        \"\\U0001F900-\\U0001F9FF\"  # Supplemental Symbols and Pictographs\n        \"\\U0001FA00-\\U0001FA6F\"  # Chess Symbols\n        \"\\U0001FA70-\\U0001FAFF\"  # Symbols and Pictographs Extended-A\n        \"\\U00002702-\\U000027B0\"  # Dingbats\n        \"]+\",\n        flags=re.UNICODE\n    )\n    return emoji_pattern.sub(replace_text, text)\n\n\ndef remove_duplicate_lines(file_path: str | Path) -> None:\n    unique_lines = OrderedDict()\n    text_encoding = 'utf-8-sig'\n    with open(file_path, 'r', encoding=text_encoding) as input_file:\n        for line in input_file:\n            unique_lines[line.strip()] = None\n    with open(file_path, 'w', encoding=text_encoding) as output_file:\n        for line in unique_lines:\n            output_file.write(line + '\\n')\n\n\ndef check_disk_capacity(file_path: str | Path, show: bool = False) -> float:\n    absolute_path = os.path.abspath(file_path)\n    directory = os.path.dirname(absolute_path)\n    disk_usage = shutil.disk_usage(directory)\n    disk_root = Path(directory).anchor\n    free_space_gb = disk_usage.free / (1024 ** 3)\n    if show:\n        print(f\"{disk_root} Total: {disk_usage.total / (1024 ** 3):.2f} GB \"\n              f\"Used: {disk_usage.used / (1024 ** 3):.2f} GB \"\n              f\"Free: {free_space_gb:.2f} GB\\n\")\n    return free_space_gb\n\n\ndef handle_proxy_addr(proxy_addr):\n    if proxy_addr:\n        if not proxy_addr.startswith('http'):\n            proxy_addr = 'http://' + proxy_addr\n    else:\n        proxy_addr = None\n    return proxy_addr\n\n\ndef generate_random_string(length: int) -> str:\n    characters = string.ascii_uppercase + string.digits\n    random_string = ''.join(random.choices(characters, k=length))\n    return random_string\n\n\ndef jsonp_to_json(jsonp_str: str) -> OptionalDict:\n    pattern = r'(\\w+)\\((.*)\\);?$'\n    match = re.search(pattern, jsonp_str)\n\n    if match:\n        _, json_str = match.groups()\n        json_obj = json.loads(json_str)\n        return json_obj\n    else:\n        raise Exception(\"No JSON data found in JSONP response.\")\n\n\ndef replace_url(file_path: str | Path, old: str, new: str) -> None:\n    with open(file_path, 'r', encoding='utf-8-sig') as f:\n        content = f.read()\n    if old in content:\n        with open(file_path, 'w', encoding='utf-8-sig') as f:\n            f.write(content.replace(old, new))\n\n\ndef get_query_params(url: str, param_name: OptionalStr) -> dict | list[str]:\n    parsed_url = urlparse(url)\n    query_params = parse_qs(parsed_url.query)\n\n    if param_name is None:\n        return query_params\n    else:\n        values = query_params.get(param_name, [])\n        return values\n    "
  }
]