├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug.yml │ ├── bug_en.yml │ ├── feature.yml │ ├── feature_en.yml │ ├── question.yml │ └── question_en.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build-image.yml │ ├── issue-translator.yml │ └── sync.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── StopRecording.vbs ├── config ├── URL_config.ini └── config.ini ├── demo.py ├── docker-compose.yaml ├── ffmpeg_install.py ├── i18n.py ├── i18n ├── en │ └── LC_MESSAGES │ │ └── .gitkeep └── zh_CN │ └── LC_MESSAGES │ ├── zh_CN.mo │ └── zh_CN.po ├── index.html ├── main.py ├── msg_push.py ├── requirements.txt └── src ├── __init__.py ├── http_clients ├── __init__.py ├── async_http.py └── sync_http.py ├── initializer.py ├── javascript ├── crypto-js.min.js ├── haixiu.js ├── liveme.js ├── taobao-sign.js └── x-bogus.js ├── logger.py ├── proxy.py ├── room.py ├── spider.py ├── stream.py └── utils.py /.dockerignore: -------------------------------------------------------------------------------- 1 | .github/workflows/build-image.yml 2 | .git 3 | .gitignore 4 | .dockerignore 5 | README.md 6 | LICENSE 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug report 2 | description: 创建Bug报告以帮助项目改进。 3 | title: 🐛[BUG] 请输入标题 4 | labels: bug 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 📝 **请在上方的`title`中填写一个简洁明了的标题**,格式建议为:🐛[Bug] 简短描述。 10 | 例如:🐛[Bug] B站某些直播间无法录制。 11 | - type: checkboxes 12 | attributes: 13 | label: ⚠️ 确认是否已存在类似问题 14 | description: > 15 | 🔍 [点击这里搜索历史issue](https://github.com/ihmily/DouyinLiveRecorder/issues?q=is%3Aissue) 16 | 请确保你的问题没有被报告过。 17 | options: 18 | - label: 我已经搜索过issues,没有找到类似问题 19 | required: true 20 | - type: dropdown 21 | attributes: 22 | label: 🔧 运行方式 23 | description: 请选择你是如何运行程序的。 24 | options: 25 | - 直接运行的exe文件 26 | - 使用源代码运行 27 | - 使用docker运行 28 | validations: 29 | required: true 30 | - type: dropdown 31 | attributes: 32 | label: 🐍 如果是使用源代码运行,请选择你的Python环境版本 33 | description: 请选择你运行程序的Python版本。 34 | options: 35 | - Python 3.10 36 | - Python 3.11 37 | - Python 3.12 38 | - Python 3.13 39 | - Other (请在问题中说明) 40 | validations: 41 | required: false 42 | - type: dropdown 43 | attributes: 44 | label: 💻 请选择你的系统环境 45 | description: 请选择你运行程序的具体系统版本。 46 | options: 47 | - Windows 10 48 | - Windows 11 49 | - macOS 50 | - Ubuntu 51 | - CentOS 52 | - Fedora 53 | - Debian 54 | - Other (请在问题中说明) 55 | validations: 56 | required: true 57 | - type: checkboxes 58 | attributes: 59 | label: ⚠️ 确认是否已经重试多次 60 | description: > 61 | 有时可能是你的设备或者网络问题导致的。 62 | options: 63 | - label: 我已经尝试过多次,仍然出现问题 64 | required: true 65 | - type: textarea 66 | attributes: 67 | label: 🕹 复现步骤 68 | description: | 69 | **⚠️ 不能复现将会关闭issue.** 70 | 请按照以下格式填写: 71 | 1. 录制的直播间地址是... 72 | 2. 使用的录制格式是... 73 | 3. ... 74 | placeholder: | 75 | 1. ... 76 | 2. ... 77 | 3. ... 78 | validations: 79 | required: true 80 | - type: textarea 81 | attributes: 82 | label: 😯 问题描述 83 | description: 详细描述出现的问题,或提供有关截图。 84 | validations: 85 | required: true 86 | - type: textarea 87 | attributes: 88 | label: 📜 错误信息 89 | description: 如果有,请贴出相关的日志错误信息或者截图。 90 | validations: 91 | required: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_en.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 (English)Bug report 2 | description: Create a bug report to help improve the project. 3 | title: 🐛[BUG] Please enter a title 4 | labels: bug 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 📝 **Please fill in a concise and clear title in the `title` above**, the format is suggested as: 🐛[Bug] Short description. 10 | For example: 🐛[Bug] Unable to record certain TikTok live rooms. 11 | - type: checkboxes 12 | attributes: 13 | label: ⚠️ Confirm if similar issues exist 14 | description: > 15 | 🔍 [Click here to search historical issues](https://github.com/ihmily/DouyinLiveRecorder/issues?q=is%3Aissue) 16 | Please make sure your issue hasn't been reported before. 17 | options: 18 | - label: I have searched the issues and found no similar problems 19 | required: true 20 | - type: dropdown 21 | attributes: 22 | label: 🔧 How did you run the program? 23 | description: Please select how you ran the program. 24 | options: 25 | - Directly running the exe file 26 | - Running with source code 27 | - Running with docker 28 | validations: 29 | required: true 30 | - type: dropdown 31 | attributes: 32 | label: 🐍 If running with source code, please select your Python environment version 33 | description: Please select the Python version you used to run the program. 34 | options: 35 | - Python 3.10 36 | - Python 3.11 37 | - Python 3.12 38 | - Python 3.13 39 | - Other (please specify in the issue) 40 | validations: 41 | required: false 42 | - type: dropdown 43 | attributes: 44 | label: 💻 Please select your system environment 45 | description: Please select the specific system version you are running the program on. 46 | options: 47 | - Windows 10 48 | - Windows 11 49 | - macOS 50 | - Ubuntu 51 | - CentOS 52 | - Fedora 53 | - Debian 54 | - Other (please specify in the issue) 55 | validations: 56 | required: true 57 | - type: checkboxes 58 | attributes: 59 | label: ⚠️ Confirm if you have retried multiple times 60 | description: > 61 | Sometimes it might be due to your device or network issues. 62 | options: 63 | - label: I have tried multiple times and still encounter the problem 64 | required: true 65 | - type: textarea 66 | attributes: 67 | label: 🕹 Reproduction steps 68 | description: | 69 | **⚠️ Issues that cannot be reproduced will be closed.** 70 | Please fill in according to the following format: 71 | 1. The live room address I tried to record is... 72 | 2. The recording format I used is... 73 | 3. ... 74 | placeholder: | 75 | 1. ... 76 | 2. ... 77 | 3. ... 78 | validations: 79 | required: true 80 | - type: textarea 81 | attributes: 82 | label: 😯 Problem description 83 | description: Describe the problem in detail or provide relevant screenshots. 84 | validations: 85 | required: true 86 | - type: textarea 87 | attributes: 88 | label: 📜 Error information 89 | description: If available, please paste the relevant log error information or screenshots. 90 | validations: 91 | required: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Feature request 2 | description: 提出你对项目的新想法或建议。 3 | title: 🚀[Feature] 请输入标题 4 | labels: enhancement 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 📝 **请在上方的`title`中填写一个简洁明了的标题**,格式建议为:🚀[Feature] 简短描述。 10 | 例如:🚀[Feature] 添加xx直播录制。 11 | - type: checkboxes 12 | attributes: 13 | label: ⚠️ 搜索是否存在类似issue 14 | description: > 15 | 🔍 [点击这里搜索历史issue](https://github.com/ihmily/DouyinLiveRecorder/issues?q=is%3Aissue) 使用关键词搜索,确保没有重复的issue。 16 | options: 17 | - label: 我已经搜索过issues,没有发现相似issue 18 | required: true 19 | - type: textarea 20 | attributes: 21 | label: 📜 功能描述 22 | description: 请详细描述你希望添加的功能,包括它的工作方式和预期效果。 23 | placeholder: | 24 | 功能描述: 25 | - type: textarea 26 | attributes: 27 | label: 🌐 举例(可选) 28 | description: 如果可能,请提供功能相关的示例、截图或相关网址。 29 | placeholder: | 30 | 直播间示例地址: 31 | `https://www.example.com/live/xxxx` 32 | 33 | - type: textarea 34 | attributes: 35 | label: 💡 动机 36 | description: 描述你提出该feature的动机,以及没有这项feature对你的使用造成了怎样的影响。 37 | placeholder: | 38 | 我需要这个功能是因为... -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_en.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 (English)Feature request 2 | description: Propose new ideas or suggestions for the project. 3 | title: 🚀[Feature] Please enter a title 4 | labels: enhancement 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 📝 **Please fill in a concise and clear title in the `title` above**, the format is suggested as: 🚀[Feature] Short description. 10 | For example: 🚀[Feature] Add xx live recording. 11 | - type: checkboxes 12 | attributes: 13 | label: ⚠️ Search for similar issues 14 | description: > 15 | 🔍 [Click here to search historical issues](https://github.com/ihmily/DouyinLiveRecorder/issues?q=is%3Aissue) using keywords to ensure there are no duplicate issues. 16 | options: 17 | - label: I have searched the issues and found no similar issues 18 | required: true 19 | - type: textarea 20 | attributes: 21 | label: 📜 Feature description 22 | description: Please describe in detail the feature you would like to add, including how it should work and what its expected outcomes are. 23 | placeholder: | 24 | Feature description: 25 | - type: textarea 26 | attributes: 27 | label: 🌐 Example (Optional) 28 | description: If possible, provide examples, screenshots, or related URLs related to the feature. 29 | placeholder: | 30 | Live room example URL: 31 | `https://www.example.com/live/xxxx` 32 | - type: textarea 33 | attributes: 34 | label: 💡 Motivation 35 | description: Describe the motivation behind your feature request and how not having this feature impacts your use of the project. 36 | placeholder: | 37 | I need this feature because... -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yml: -------------------------------------------------------------------------------- 1 | name: ❓ Question 2 | description: 对程序使用有疑问?在这里提出你的问题。 3 | title: ❓[Question] 请输入标题 4 | labels: question 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 📝 **请在上方的`title`中填写一个简洁明了的问题标题**。这将帮助其他人快速理解你的问题。 10 | 例如:❓[Question] 如果设置单个直播间的录制清晰度。 11 | - type: checkboxes 12 | attributes: 13 | label: ⚠️ 搜索是否存在类似问题 14 | description: > 15 | 🔍 [点击这里搜索历史issue](https://github.com/ihmily/DouyinLiveRecorder/issues?q=is%3Aissue) 使用关键词搜索,看看是否已经有人问过类似的问题。 16 | options: 17 | - label: 我已经搜索过issues,没有找到相似的问题 18 | required: true 19 | - type: dropdown 20 | attributes: 21 | label: 🔧 运行方式 22 | description: 请选择你是如何运行程序的。 23 | options: 24 | - 直接运行的exe文件 25 | - 使用源代码运行 26 | - 使用docker运行 27 | validations: 28 | required: true 29 | - type: dropdown 30 | attributes: 31 | label: 🐍 如果是使用源代码运行,请选择你的Python环境版本 32 | description: 请选择你运行程序的Python版本。 33 | options: 34 | - Python 3.10 35 | - Python 3.11 36 | - Python 3.12 37 | - Python 3.13 38 | - Other (请在问题中说明) 39 | validations: 40 | required: false 41 | - type: dropdown 42 | attributes: 43 | label: 💻 请选择你的系统环境 44 | description: 请选择你运行程序的具体系统版本。 45 | options: 46 | - Windows 10 47 | - Windows 11 48 | - macOS 49 | - Ubuntu 50 | - CentOS 51 | - Fedora 52 | - Debian 53 | - Other (请在问题中说明) 54 | validations: 55 | required: true 56 | - type: textarea 57 | attributes: 58 | label: 🤔 问题详情 59 | description: 请提供与你的问题相关的所有详细信息。 60 | placeholder: | 61 | 你的问题具体是关于什么? 62 | validations: 63 | required: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question_en.yml: -------------------------------------------------------------------------------- 1 | name: ❓ (English)Question 2 | description: Have questions about using the program? Ask them here. 3 | title: ❓[Question] Please enter a title 4 | labels: question 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 📝 **Please fill in a concise and clear question title in the `title` above**. This will help others quickly understand your question. 10 | For example: ❓[Question] How to set the recording quality for a single live room. 11 | - type: checkboxes 12 | attributes: 13 | label: ⚠️ Search for similar issues 14 | description: > 15 | 🔍 [Click here to search historical issues](https://github.com/ihmily/DouyinLiveRecorder/issues?q=is%3Aissue) see if your question has already been asked. 16 | options: 17 | - label: I have searched the issues and found no similar questions 18 | required: true 19 | - type: dropdown 20 | attributes: 21 | label: 🔧 How did you run the program? 22 | description: Please select how you ran the program. 23 | options: 24 | - Executable file run directly 25 | - Running with source code 26 | - Running with docker 27 | validations: 28 | required: true 29 | - type: dropdown 30 | attributes: 31 | label: 🐍 If running with source code, please select your Python environment version 32 | description: Please select the Python version you used to run the program. 33 | options: 34 | - Python 3.10 35 | - Python 3.11 36 | - Python 3.12 37 | - Python 3.13 38 | - Other (please specify in the question) 39 | validations: 40 | required: false 41 | - type: dropdown 42 | attributes: 43 | label: 💻 Please select your system environment 44 | description: Please select the specific system version you are running the program on. 45 | options: 46 | - Windows 10 47 | - Windows 11 48 | - macOS 49 | - Ubuntu 50 | - CentOS 51 | - Fedora 52 | - Debian 53 | - Other (please specify in the question) 54 | validations: 55 | required: true 56 | - type: textarea 57 | attributes: 58 | label: 🤔 Question details 59 | description: Please provide all the details relevant to your question. 60 | placeholder: | 61 | What is your question about? 62 | validations: 63 | required: true -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### 📜 标题(Title) 2 | 3 | **请提供这个Pull Request中提议的更改的简洁描述:** 4 | 5 | 6 | - 7 | 8 | ### 🔍 描述(Description) 9 | 10 | **请描述这个PR做了什么/为什么这些更改是必要的:** 11 | 12 | 13 | - 14 | 15 | ### 📝 类型(Type of Change) 16 | 17 | **这个PR引入了哪种类型的更改?(请勾选所有适用的选项)** 18 | 19 | 20 | - [ ] 修复Bug 21 | - [ ] 新功能 22 | - [ ] 代码风格更新(格式化,局部变量) 23 | - [ ] 重构(改进代码结构) 24 | - [ ] 构建相关更改(依赖项,构建脚本等) 25 | - [ ] 其他:_请描述_ 26 | 27 | ### 🏗️ 测试(Testing) 28 | 29 | **请描述您已经进行的测试:** 30 | 31 | 32 | - 33 | 34 | **如果适用,请提供测试更改的说明:** 35 | 36 | 37 | - 38 | 39 | ### 📋 检查清单(Checklist) 40 | 41 | 在您创建这个PR之前,请确保以下所有框都被勾选,方法是在每个框中放置一个`x`: 42 | 43 | 44 | - [ ] 我已经阅读了**贡献指南**文档 45 | - [ ] 我的更改没有产生新的警告 46 | - [ ] 我已经添加了覆盖我更改的测试 47 | - [ ] 我已经相应地更新了文档(如果适用) 48 | - [ ] 我遵循了这个项目的代码风格 49 | 50 | **注意:** 这个PR在所有复选框被勾选之前不会被合并。 51 | 52 | 53 | --- 54 | 55 | **感谢您的贡献!** 56 | -------------------------------------------------------------------------------- /.github/workflows/build-image.yml: -------------------------------------------------------------------------------- 1 | name: Build and Push Docker Image 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | build_and_push: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | 16 | - name: Set up QEMU 17 | uses: docker/setup-qemu-action@v3 18 | 19 | - name: Set up Docker Buildx 20 | uses: docker/setup-buildx-action@v3 21 | 22 | - name: Cache Docker layers 23 | uses: actions/cache@v2 24 | with: 25 | path: /tmp/.buildx-cache 26 | key: ${{ runner.os }}-buildx-${{ github.sha }} 27 | restore-keys: | 28 | ${{ runner.os }}-buildx- 29 | 30 | - name: Log in to Docker Hub 31 | uses: docker/login-action@v2 32 | with: 33 | username: ${{ secrets.DOCKERHUB_USERNAME }} 34 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 35 | registry: docker.io 36 | 37 | - name: Build and push Docker image 38 | uses: docker/build-push-action@v5 39 | with: 40 | context: . 41 | file: ./Dockerfile 42 | push: true 43 | tags: | 44 | ihmily/douyin-live-recorder:${{ github.ref_name }} 45 | ihmily/douyin-live-recorder:latest 46 | platforms: linux/amd64,linux/arm64 47 | cache-from: type=local,src=/tmp/.buildx-cache 48 | cache-to: type=local,dest=/tmp/.buildx-cache 49 | -------------------------------------------------------------------------------- /.github/workflows/issue-translator.yml: -------------------------------------------------------------------------------- 1 | name: Issue Translator 2 | on: 3 | issue_comment: 4 | types: [created] 5 | issues: 6 | types: [opened] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: usthe/issues-translate-action@v2.7 13 | with: 14 | IS_MODIFY_TITLE: false 15 | CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. -------------------------------------------------------------------------------- /.github/workflows/sync.yml: -------------------------------------------------------------------------------- 1 | name: 'Upstream Sync' 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | schedule: 8 | - cron: "0 0 * * *" # every day 9 | 10 | workflow_dispatch: # click the button on Github repo! 11 | inputs: 12 | sync_test_mode: # Adds a boolean option that appears during manual workflow run for easy test mode config 13 | description: 'Fork Sync Test Mode' 14 | type: boolean 15 | default: false 16 | 17 | jobs: 18 | sync_latest_from_upstream: 19 | runs-on: ubuntu-latest 20 | name: Sync latest commits from upstream repo 21 | if: ${{ github.event.repository.fork }} 22 | 23 | steps: 24 | # Step 1: run a standard checkout action, provided by github 25 | - name: Checkout target repo 26 | uses: actions/checkout@v3 27 | with: 28 | # optional: set the branch to checkout, 29 | # sync action checks out your 'target_sync_branch' anyway 30 | ref: ${{ secrets.MY_TARGET_SYNC_BRANCH }} 31 | # REQUIRED if your upstream repo is private (see wiki) 32 | persist-credentials: false 33 | 34 | # Step 2: run the sync action 35 | - name: Sync upstream changes 36 | id: sync 37 | uses: aormsby/Fork-Sync-With-Upstream-action@v3.4.1 38 | with: 39 | target_sync_branch: ${{ secrets.MY_TARGET_SYNC_BRANCH }} # need to set 40 | # REQUIRED 'target_repo_token' exactly like this! 41 | target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set 42 | upstream_sync_branch: main 43 | upstream_sync_repo: ihmily/DouyinLiveRecorder 44 | 45 | # Set test_mode true during manual dispatch to run tests instead of the true action!! 46 | test_mode: ${{ inputs.sync_test_mode }} 47 | 48 | # Step 3: Display a sample message based on the sync output var 'has_new_commits' 49 | - name: New commits found 50 | if: steps.sync.outputs.has_new_commits == 'true' 51 | run: echo "New commits were found to sync." 52 | 53 | - name: No new commits 54 | if: steps.sync.outputs.has_new_commits == 'false' 55 | run: echo "There were no new commits." 56 | 57 | - name: Show value of 'has_new_commits' 58 | run: echo ${{ steps.sync.outputs.has_new_commits }} 59 | 60 | - name: Sync check 61 | if: failure() 62 | run: | 63 | echo "[Error] 由于上游仓库的 workflow 文件变更,导致 GitHub 自动暂停了本次自动更新,你需要手动 Sync Fork 一次" 64 | 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." 65 | exit 1 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | .pybuilder/ 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # DouyinLiveRecord 85 | backup_config/ 86 | logs/ 87 | node/ 88 | node-v*.zip 89 | 90 | # pyenv 91 | # For a library or package, you might want to ignore these files since the code is 92 | # intended to run in multiple environments; otherwise, check them in: 93 | # .python-version 94 | 95 | # pipenv 96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 99 | # install all needed dependencies. 100 | #Pipfile.lock 101 | 102 | # poetry 103 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 104 | # This is especially recommended for binary packages to ensure reproducibility, and is more 105 | # commonly ignored for libraries. 106 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 107 | poetry.lock 108 | 109 | # pdm 110 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 111 | #pdm.lock 112 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 113 | # in version control. 114 | # https://pdm.fming.dev/#use-with-ide 115 | .pdm.toml 116 | 117 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 118 | __pypackages__/ 119 | 120 | # Celery stuff 121 | celerybeat-schedule 122 | celerybeat.pid 123 | 124 | # SageMath parsed files 125 | *.sage.py 126 | 127 | # Environments 128 | .env 129 | .venv 130 | env/ 131 | venv/ 132 | ENV/ 133 | env.bak/ 134 | venv.bak/ 135 | 136 | # Spyder project settings 137 | .spyderproject 138 | .spyproject 139 | 140 | # Rope project settings 141 | .ropeproject 142 | 143 | # mkdocs documentation 144 | /site 145 | 146 | # mypy 147 | .mypy_cache/ 148 | .dmypy.json 149 | dmypy.json 150 | 151 | # Pyre type checker 152 | .pyre/ 153 | 154 | # pytype static type analyzer 155 | .pytype/ 156 | 157 | # Cython debug symbols 158 | cython_debug/ 159 | 160 | # PyCharm 161 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 162 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 163 | # and can be added to the global gitignore or merged into this file. For a more nuclear 164 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 165 | #.idea/ 166 | 167 | backup_config/ 168 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-slim 2 | 3 | WORKDIR /app 4 | 5 | COPY . /app 6 | 7 | RUN apt-get update && \ 8 | apt-get install -y curl gnupg && \ 9 | curl -sL https://deb.nodesource.com/setup_20.x | bash - && \ 10 | apt-get install -y nodejs 11 | 12 | RUN pip install --no-cache-dir -r requirements.txt 13 | 14 | RUN apt-get update && \ 15 | apt-get install -y ffmpeg tzdata && \ 16 | ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ 17 | dpkg-reconfigure -f noninteractive tzdata 18 | 19 | CMD ["python", "main.py"] 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Hmily 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![video_spider](https://socialify.git.ci/ihmily/DouyinLiveRecorder/image?font=Inter&forks=1&language=1&owner=1&pattern=Circuit%20Board&stargazers=1&theme=Light) 2 | 3 | ## 💡简介 4 | [![Python Version](https://img.shields.io/badge/python-3.11.6-blue.svg)](https://www.python.org/downloads/release/python-3116/) 5 | [![Supported Platforms](https://img.shields.io/badge/platforms-Windows%20%7C%20Linux-blue.svg)](https://github.com/ihmily/DouyinLiveRecorder) 6 | [![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) 7 | ![GitHub issues](https://img.shields.io/github/issues/ihmily/DouyinLiveRecorder.svg) 8 | [![Latest Release](https://img.shields.io/github/v/release/ihmily/DouyinLiveRecorder)](https://github.com/ihmily/DouyinLiveRecorder/releases/latest) 9 | [![Downloads](https://img.shields.io/github/downloads/ihmily/DouyinLiveRecorder/total)](https://github.com/ihmily/DouyinLiveRecorder/releases/latest) 10 | 11 | 一款**简易**的可循环值守的直播录制工具,基于FFmpeg实现多平台直播源录制,支持自定义配置录制以及直播状态推送。 12 | 13 | 14 | 15 | ## 😺已支持平台 16 | 17 | - [x] 抖音 18 | - [x] TikTok 19 | - [x] 快手 20 | - [x] 虎牙 21 | - [x] 斗鱼 22 | - [x] YY 23 | - [x] B站 24 | - [x] 小红书 25 | - [x] bigo 26 | - [x] blued 27 | - [x] SOOP(原AfreecaTV) 28 | - [x] 网易cc 29 | - [x] 千度热播 30 | - [x] PandaTV 31 | - [x] 猫耳FM 32 | - [x] Look直播 33 | - [x] WinkTV 34 | - [x] FlexTV 35 | - [x] PopkonTV 36 | - [x] TwitCasting 37 | - [x] 百度直播 38 | - [x] 微博直播 39 | - [x] 酷狗直播 40 | - [x] TwitchTV 41 | - [x] LiveMe 42 | - [x] 花椒直播 43 | - [x] 流星直播 44 | - [x] ShowRoom 45 | - [x] Acfun 46 | - [x] 映客直播 47 | - [x] 音播直播 48 | - [x] 知乎直播 49 | - [x] CHZZK 50 | - [x] 嗨秀直播 51 | - [x] vv星球直播 52 | - [x] 17Live 53 | - [x] 浪Live 54 | - [x] 畅聊直播 55 | - [x] 飘飘直播 56 | - [x] 六间房直播 57 | - [x] 乐嗨直播 58 | - [x] 花猫直播 59 | - [x] Shopee 60 | - [x] Youtube 61 | - [x] 淘宝 62 | - [x] 京东 63 | - [x] Faceit 64 | - [ ] 更多平台正在更新中 65 | 66 | 67 | 68 | ## 🎈项目结构 69 | 70 | ``` 71 | . 72 | └── DouyinLiveRecorder/ 73 | ├── /config -> (config record) 74 | ├── /logs -> (save runing log file) 75 | ├── /backup_config -> (backup file) 76 | ├── /douyinliverecorder -> (package) 77 | ├── initializer.py-> (check and install nodejs) 78 | ├── spider.py-> (get live data) 79 | ├── stream.py-> (get live stream address) 80 | ├── utils.py -> (contains utility functions) 81 | ├── logger.py -> (logger handdle) 82 | ├── room.py -> (get room info) 83 | ├── /javascript -> (some decrypt code) 84 | ├── main.py -> (main file) 85 | ├── ffmpeg_install.py -> (ffmpeg install script) 86 | ├── demo.py -> (call package test demo) 87 | ├── msg_push.py -> (send live status update message) 88 | ├── ffmpeg.exe -> (record video) 89 | ├── index.html -> (play m3u8 and flv video) 90 | ├── requirements.txt -> (library dependencies) 91 | ├── docker-compose.yaml -> (Container Orchestration File) 92 | ├── Dockerfile -> (Application Build Recipe) 93 | ├── StopRecording.vbs -> (stop recording script on Windows) 94 | ... 95 | ``` 96 | 97 | 98 | 99 | ## 🌱使用说明 100 | 101 | - 对于只想使用录制软件的小白用户,进入[Releases](https://github.com/ihmily/DouyinLiveRecorder/releases) 中下载最新发布的 zip压缩包即可,里面有打包好的录制软件。(有些电脑可能会报毒,直接忽略即可,如果下载时被浏览器屏蔽,请更换浏览器下载) 102 | 103 | - 压缩包解压后,在 `config` 文件夹内的 `URL_config.ini` 中添加录制直播间地址,一行一个直播间地址。如果要自定义配置录制,可以修改`config.ini` 文件,推荐将录制格式修改为`ts`。 104 | - 以上步骤都做好后,就可以运行`DouyinLiveRecorder.exe` 程序进行录制了。录制的视频文件保存在同目录下的 `downloads` 文件夹内。 105 | 106 | - 另外,如果需要录制TikTok、AfreecaTV等海外平台,请在配置文件中设置开启代理并添加proxy_addr链接 如:`127.0.0.1:7890` (这只是示例地址,具体根据实际填写)。 107 | 108 | - 假如`URL_config.ini`文件中添加的直播间地址,有个别直播间暂时不想录制又不想移除链接,可以在对应直播间的链接开头加上`#`,那么将停止该直播间的监测以及录制。 109 | 110 | - 软件默认录制清晰度为 `原画` ,如果要单独设置某个直播间的录制画质,可以在添加直播间地址时前面加上画质即可,如`超清,https://live.douyin.com/745964462470` 记得中间要有`,` 分隔。 111 | 112 | - 如果要长时间挂着软件循环监测直播,最好循环时间设置长一点(咱也不差没录制到的那几分钟),避免因请求频繁导致被官方封禁IP 。 113 | 114 | - 要停止直播录制,Windows平台可执行StopRecording.vbs脚本文件,或者在录制界面使用 `Ctrl+C ` 组合键中断录制,若要停止其中某个直播间的录制,可在`URL_config.ini`文件中的地址前加#,会自动停止对应直播间的录制并正常保存已录制的视频。 115 | - 最后,欢迎右上角给本项目一个star,同时也非常乐意大家提交pr。 116 | 117 |   118 | 119 | 直播间链接示例: 120 | 121 | ``` 122 | 抖音: 123 | https://live.douyin.com/745964462470 124 | https://v.douyin.com/iQFeBnt/ 125 | https://live.douyin.com/yall1102 (链接+抖音号) 126 | https://v.douyin.com/CeiU5cbX (主播主页地址) 127 | 128 | TikTok: 129 | https://www.tiktok.com/@pearlgaga88/live 130 | 131 | 快手: 132 | https://live.kuaishou.com/u/yall1102 133 | 134 | 虎牙: 135 | https://www.huya.com/52333 136 | 137 | 斗鱼: 138 | https://www.douyu.com/3637778?dyshid= 139 | https://www.douyu.com/topic/wzDBLS6?rid=4921614&dyshid= 140 | 141 | YY: 142 | https://www.yy.com/22490906/22490906 143 | 144 | B站: 145 | https://live.bilibili.com/320 146 | 147 | 小红书(直播间分享地址): 148 | http://xhslink.com/xpJpfM 149 | 150 | bigo直播: 151 | https://www.bigo.tv/cn/716418802 152 | 153 | buled直播: 154 | https://app.blued.cn/live?id=Mp6G2R 155 | 156 | SOOP: 157 | https://play.sooplive.co.kr/sw7love 158 | 159 | 网易cc: 160 | https://cc.163.com/583946984 161 | 162 | 千度热播: 163 | https://qiandurebo.com/web/video.php?roomnumber=33333 164 | 165 | PandaTV: 166 | https://www.pandalive.co.kr/live/play/bara0109 167 | 168 | 猫耳FM: 169 | https://fm.missevan.com/live/868895007 170 | 171 | Look直播: 172 | https://look.163.com/live?id=65108820&position=3 173 | 174 | WinkTV: 175 | https://www.winktv.co.kr/live/play/anjer1004 176 | 177 | FlexTV: 178 | https://www.flextv.co.kr/channels/593127/live 179 | 180 | PopkonTV: 181 | https://www.popkontv.com/live/view?castId=wjfal007&partnerCode=P-00117 182 | https://www.popkontv.com/channel/notices?mcid=wjfal007&mcPartnerCode=P-00117 183 | 184 | TwitCasting: 185 | https://twitcasting.tv/c:uonq 186 | 187 | 百度直播: 188 | https://live.baidu.com/m/media/pclive/pchome/live.html?room_id=9175031377&tab_category 189 | 190 | 微博直播: 191 | https://weibo.com/l/wblive/p/show/1022:2321325026370190442592 192 | 193 | 酷狗直播: 194 | https://fanxing2.kugou.com/50428671?refer=2177&sourceFrom= 195 | 196 | TwitchTV: 197 | https://www.twitch.tv/gamerbee 198 | 199 | LiveMe: 200 | https://www.liveme.com/zh/v/17141543493018047815/index.html 201 | 202 | 花椒直播: 203 | https://www.huajiao.com/l/345096174 204 | 205 | 流星直播: 206 | https://www.7u66.com/100960 207 | 208 | ShowRoom: 209 | https://www.showroom-live.com/room/profile?room_id=480206 (主播主页地址) 210 | 211 | Acfun: 212 | https://live.acfun.cn/live/179922 213 | 214 | 映客直播: 215 | https://www.inke.cn/liveroom/index.html?uid=22954469&id=1720860391070904 216 | 217 | 音播直播: 218 | https://live.ybw1666.com/800002949 219 | 220 | 知乎直播: 221 | https://www.zhihu.com/people/ac3a467005c5d20381a82230101308e9 (主播主页地址) 222 | 223 | CHZZK: 224 | https://chzzk.naver.com/live/458f6ec20b034f49e0fc6d03921646d2 225 | 226 | 嗨秀直播: 227 | https://www.haixiutv.com/6095106 228 | 229 | VV星球直播: 230 | https://h5webcdn-pro.vvxqiu.com//activity/videoShare/videoShare.html?h5Server=https://h5p.vvxqiu.com&roomId=LP115924473&platformId=vvstar 231 | 232 | 17Live: 233 | https://17.live/en/live/6302408 234 | 235 | 浪Live: 236 | https://www.lang.live/en-US/room/3349463 237 | 238 | 畅聊直播: 239 | https://live.tlclw.com/106188 240 | 241 | 飘飘直播: 242 | https://m.pp.weimipopo.com/live/preview.html?uid=91648673&anchorUid=91625862&app=plpl 243 | 244 | 六间房直播: 245 | https://v.6.cn/634435 246 | 247 | 乐嗨直播: 248 | https://www.lehaitv.com/8059096 249 | 250 | 花猫直播: 251 | https://h.catshow168.com/live/preview.html?uid=19066357&anchorUid=18895331 252 | 253 | Shopee: 254 | https://sg.shp.ee/GmpXeuf?uid=1006401066&session=802458 255 | 256 | Youtube: 257 | https://www.youtube.com/watch?v=cS6zS5hi1w0 258 | 259 | 淘宝(需cookie): 260 | https://m.tb.cn/h.TWp0HTd 261 | 262 | 京东: 263 | https://3.cn/28MLBy-E 264 | 265 | Faceit: 266 | https://www.faceit.com/zh/players/Compl1/stream 267 | ``` 268 | 269 |   270 | 271 | ## 🎃源码运行 272 | 使用源码运行,前提要有**Python>=3.10**环境,如果没有请先自行安装Python,再执行下面步骤。 273 | 274 | 1.首先拉取或手动下载本仓库项目代码 275 | 276 | ```bash 277 | git clone https://github.com/ihmily/DouyinLiveRecorder.git 278 | ``` 279 | 280 | 2.进入项目文件夹,安装依赖 281 | 282 | ```bash 283 | cd DouyinLiveRecorder 284 | pip3 install -r requirements.txt 285 | ``` 286 | 287 | 3.安装[FFmpeg](https://ffmpeg.org/download.html#build-linux),如果是Windows系统,这一步可跳过。对于Linux系统,执行以下命令安装 288 | 289 | CentOS执行 290 | 291 | ```bash 292 | yum install epel-release 293 | yum install ffmpeg 294 | ``` 295 | 296 | Ubuntu则执行 297 | 298 | ```bash 299 | apt update 300 | apt install ffmpeg 301 | ``` 302 | 303 | macOS 执行 304 | 305 | **如果已经安装 Homebrew 请跳过这一步** 306 | 307 | ```bash 308 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 309 | ``` 310 | 311 | ```bash 312 | brew install ffmpeg 313 | ``` 314 | 315 | 4.运行程序 316 | 317 | ```python 318 | python main.py 319 | ``` 320 | 321 | 其中Linux系统请使用`python3 main.py` 运行。 322 | 323 |   324 | ## 🐋容器运行 325 | 326 | 在运行命令之前,请确保您的机器上安装了 [Docker](https://docs.docker.com/get-docker/) 和 [Docker Compose](https://docs.docker.com/compose/install/) 327 | 328 | 1.快速启动 329 | 330 | 最简单方法是运行项目中的 [docker-compose.yaml](https://github.com/ihmily/DouyinLiveRecorder/blob/main/docker-compose.yaml) 文件,只需简单执行以下命令: 331 | 332 | ```bash 333 | docker-compose up 334 | ``` 335 | 336 | 可选 `-d` 在后台运行。 337 | 338 | 339 | 340 | 2.构建镜像(可选) 341 | 342 | 如果你只想简单的运行程序,则不需要做这一步。Docker镜像仓库中代码版本可能不是最新的,如果要运行本仓库主分支最新代码,可以本地自定义构建,通过修改 [docker-compose.yaml](https://github.com/ihmily/DouyinLiveRecorder/blob/main/docker-compose.yaml) 文件,如将镜像名修改为 `douyin-live-recorder:latest`,并取消 `# build: .` 注释,然后再执行 343 | 344 | ```bash 345 | docker build -t douyin-live-recorder:latest . 346 | docker-compose up 347 | ``` 348 | 349 | 或者直接使用下面命令进行构建并启动 350 | 351 | ```bash 352 | docker-compose -f docker-compose.yaml up 353 | ``` 354 | 355 | 356 | 357 | 3.停止容器实例 358 | 359 | ```bash 360 | docker-compose stop 361 | ``` 362 | 363 | 364 | 365 | 4.注意事项 366 | 367 | ①在docker容器内运行本程序之前,请先在配置文件中添加要录制的直播间地址。 368 | 369 | ②在容器内时,如果手动中断容器运行停止录制,会导致正在录制的视频文件损坏! 370 | 371 | **无论哪种运行方式,为避免手动中断或者异常中断导致录制的视频文件损坏的情况,推荐使用 `ts` 格式保存**。 372 | 373 |   374 | 375 | ## ❤️贡献者 376 | 377 |    [![Hmily](https://github.com/ihmily.png?size=50)](https://github.com/ihmily) 378 | [![iridescentGray](https://github.com/iridescentGray.png?size=50)](https://github.com/iridescentGray) 379 | [![annidy](https://github.com/annidy.png?size=50)](https://github.com/annidy) 380 | [![wwkk2580](https://github.com/wwkk2580.png?size=50)](https://github.com/wwkk2580) 381 | [![missuo](https://github.com/missuo.png?size=50)](https://github.com/missuo) 382 | xueli12 383 | kaine1973 384 | yinruiqing 385 | Max-Tortoise 386 | [![justdoiting](https://github.com/justdoiting.png?size=50)](https://github.com/justdoiting) 387 | [![dhbxs](https://github.com/dhbxs.png?size=50)](https://github.com/dhbxs) 388 | [![wujiyu115](https://github.com/wujiyu115.png?size=50)](https://github.com/wujiyu115) 389 | [![zhanghao333](https://github.com/zhanghao333.png?size=50)](https://github.com/zhanghao333) 390 | gyc0123 391 | 392 |    [![HoratioShaw](https://github.com/HoratioShaw.png?size=50)](https://github.com/HoratioShaw) 393 | [![nov30th](https://github.com/nov30th.png?size=50)](https://github.com/nov30th) 394 |   395 | 396 | ## ⏳提交日志 397 | 398 | - 20250127 399 | - 新增淘宝、京东、faceit直播录制 400 | - 修复小红书直播流录制以及转码问题 401 | - 修复畅聊、VV星球、flexTV直播录制 402 | - 修复批量微信直播推送 403 | - 新增email发送ssl和port配置 404 | - 新增强制转h264配置 405 | - 更新ffmpeg版本 406 | - 重构包为异步函数! 407 | 408 | - 20241130 409 | - 新增shopee、youtube直播录制 410 | - 新增支持自定义m3u8、flv地址录制 411 | - 新增自定义执行脚本,支持python、bat、bash等 412 | - 修复YY直播、花椒直播和小红书直播录制 413 | - 修复b站标题获取错误 414 | - 修复log日志错误 415 | - 20241030 416 | - 新增嗨秀直播、vv星球直播、17Live、浪Live、SOOP、畅聊直播(原时光直播)、飘飘直播、六间房直播、乐嗨直播、花猫直播等10个平台直播录制 417 | - 修复小红书直播录制,支持小红书作者主页地址录制直播 418 | - 新增支持ntfy消息推送,以及新增支持批量推送多个地址(逗号分隔多个推送地址) 419 | - 修复Liveme直播录制、twitch直播录制 420 | - 新增Windows平台一键停止录制VB脚本程序 421 | - 20241005 422 | - 新增邮箱和Bark推送 423 | - 新增直播注释停止录制 424 | - 优化分段录制 425 | - 重构部分代码 426 | - 20240928 427 | - 新增知乎直播、CHZZK直播录制 428 | - 修复音播直播录制 429 | - 20240903 430 | - 新增抖音双屏录制、音播直播录制 431 | - 修复PandaTV、bigo直播录制 432 | - 20240713 433 | - 新增映客直播录制 434 | - 20240705 435 | - 新增时光直播录制 436 | - 20240701 437 | - 修复虎牙直播录制2分钟断流问题 438 | - 新增自定义直播推送内容 439 | - 20240621 440 | - 新增Acfun、ShowRoom直播录制 441 | - 修复微博录制、新增直播源线路 442 | - 修复斗鱼直播60帧录制 443 | - 修复酷狗直播录制 444 | - 修复TikTok部分无法解析直播源 445 | - 修复抖音无法录制连麦直播 446 | - 20240510 447 | - 修复部分虎牙直播间录制错误 448 | - 20240508 449 | - 修复花椒直播录制 450 | - 更改文件路径解析方式 [@kaine1973](https://github.com/kaine1973) 451 | - 20240506 452 | - 修复抖音录制画质解析bug 453 | - 修复虎牙录制 60帧最高画质问题 454 | - 新增流星直播录制 455 | - 20240427 456 | - 新增LiveMe、花椒直播录制 457 | - 20240425 458 | - 新增TwitchTV直播录制 459 | - 20240424 460 | - 新增酷狗直播录制、优化PopkonTV直播录制 461 | - 20240423 462 | - 新增百度直播录制、微博直播录制 463 | - 修复斗鱼录制直播回放的问题 464 | - 新增直播源地址显示以及输出到日志文件设置 465 | - 20240311 466 | - 修复海外平台录制bug,增加画质选择,增强录制稳定性 467 | - 修复虎牙录制bug (虎牙`一起看`频道 有特殊限制,有时无法录制) 468 | - 20240309 469 | - 修复虎牙直播、小红书直播和B站直播录制 470 | - 新增5个直播平台录制,包括winktv、flextv、look、popkontv、twitcasting 471 | - 新增部分海外平台账号密码配置,实现自动登录并更新配置文件中的cookie 472 | - 新增自定义配置需要使用代理录制的平台 473 | - 新增只推送开播消息不进行录制设置 474 | - 修复了一些bug 475 | - 20240209 476 | - 优化AfreecaTV录制,新增账号密码登录获取cookie以及持久保存 477 | - 修复了小红书直播因官方更新直播域名,导致无法录制直播的问题 478 | - 修复了更新URL配置文件的bug 479 | - 最后,祝大家新年快乐! 480 | 481 |
点击展开更多提交日志 482 | 483 | - 20240129 484 | - 新增猫耳FM直播录制 485 | - 20240127 486 | - 新增千度热播直播录制、新增pandaTV(韩国)直播录制 487 | - 新增telegram直播状态消息推送,修复了某些bug 488 | - 新增自定义设置不同直播间的录制画质(即每个直播间录制画质可不同) 489 | - 修改录制视频保存路径为 `downloads` 文件夹,并且分平台进行保存。 490 | - 20240114 491 | - 新增网易cc直播录制,优化ffmpeg参数,修改AfreecaTV输入直播地址格式 492 | - 修改日志记录器 @[iridescentGray](https://github.com/iridescentGray) 493 | - 20240102 494 | - 修复Linux上运行,新增docker配置文件 495 | - 20231210 496 | - 修复录制分段bug,修复bigo录制检测bug 497 | - 新增自定义修改录制主播名 498 | - 新增AfreecaTV直播录制,修复某些可能会发生的bug 499 | - 20231207 500 | - 新增blued直播录制,修复YY直播录制,新增直播结束消息推送 501 | - 20231206 502 | - 新增bigo直播录制 503 | - 20231203 504 | - 新增小红书直播录制(全网首发),目前小红书官方没有切换清晰度功能,因此直播录制也只有默认画质 505 | - 小红书录制暂时无法循环监测,每次主播开启直播,都要重新获取一次链接 506 | - 获取链接的方式为 将直播间转发到微信,在微信中打开后,复制页面的链接。 507 | - 20231030 508 | - 本次更新只是进行修复,没时间新增功能。 509 | - 欢迎各位大佬提pr 帮忙更新维护 510 | - 20230930 511 | - 新增抖音从接口获取直播流,增强稳定性 512 | - 修改快手获取直播流的方式,改用从官方接口获取 513 | - 祝大家中秋节快乐! 514 | - 20230919 515 | - 修复了快手版本更新后录制出错的问题,增加了其自动获取cookie(~~稳定性未知~~) 516 | - 修复了TikTok显示正在直播但不进行录制的问题 517 | - 20230907 518 | - 修复了因抖音官方更新了版本导致的录制出错以及短链接转换出错 519 | - 修复B站无法录制原画视频的bug 520 | - 修改了配置文件字段,新增各平台自定义设置Cookie 521 | - 20230903 522 | - 修复了TikTok录制时报644无法录制的问题 523 | - 新增直播状态推送到钉钉和微信的功能,如有需要请看 [设置推送教程](https://d04vqdiqwr3.feishu.cn/docx/XFPwdDDvfobbzlxhmMYcvouynDh?from=from_copylink) 524 | - 最近比较忙,其他问题有时间再更新 525 | - 20230816 526 | - 修复斗鱼直播(官方更新了字段)和快手直播录制出错的问题 527 | - 20230814 528 | - 新增B站直播录制 529 | - 写了一个在线播放M3U8和FLV视频的网页源码,打开即可食用 530 | - 20230812 531 | - 新增YY直播录制 532 | - 20230808 533 | - 修复主播重新开播无法再次录制的问题 534 | - 20230807 535 | - 新增了斗鱼直播录制 536 | - 修复显示录制完成之后会重新开始录制的问题 537 | - 20230805 538 | - 新增了虎牙直播录制,其暂时只能用flv视频流进行录制 539 | - Web API 新增了快手和虎牙这两个平台的直播流解析(TikTok要代理) 540 | - 20230804 541 | - 新增了快手直播录制,优化了部分代码 542 | - 上传了一个自动化获取抖音直播间页面Cookie的代码,可以用于录制 543 | - 20230803 544 | - 通宵更新 545 | - 新增了国际版抖音TikTok的直播录制,去除冗余 简化了部分代码 546 | - 20230724 547 | - 新增了一个通过抖音直播间地址获取直播视频流链接的API接口,上传即可用 548 |
549 |   550 | 551 | ## 有问题可以提issue, 我会在这里持续添加更多直播平台的录制 欢迎Star 552 | #### 553 | -------------------------------------------------------------------------------- /StopRecording.vbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihmily/DouyinLiveRecorder/fd06bc89da6ad6d270e1858bcb86302e954e543d/StopRecording.vbs -------------------------------------------------------------------------------- /config/URL_config.ini: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /config/config.ini: -------------------------------------------------------------------------------- 1 | [录制设置] 2 | language(zh_cn/en) = zh_cn 3 | 是否跳过代理检测(是/否) = 否 4 | 直播保存路径(不填则默认) = 5 | 保存文件夹是否以作者区分 = 是 6 | 保存文件夹是否以时间区分 = 否 7 | 保存文件夹是否以标题区分 = 否 8 | 保存文件名是否包含标题 = 否 9 | 是否去除名称中的表情符号 = 是 10 | 视频保存格式ts|mkv|flv|mp4|mp3音频|m4a音频 = ts 11 | 原画|超清|高清|标清|流畅 = 原画 12 | 是否使用代理ip(是/否) = 是 13 | 代理地址 = 14 | 同一时间访问网络的线程数 = 3 15 | 循环时间(秒) = 300 16 | 排队读取网址时间(秒) = 0 17 | 是否显示循环秒数 = 否 18 | 是否显示直播源地址 = 否 19 | 分段录制是否开启 = 是 20 | 是否强制启用https录制 = 否 21 | 录制空间剩余阈值(gb) = 1.0 22 | 视频分段时间(秒) = 1800 23 | 录制完成后自动转为mp4格式 = 是 24 | mp4格式重新编码为h264 = 否 25 | 追加格式后删除原文件 = 是 26 | 生成时间字幕文件 = 否 27 | 是否录制完成后执行自定义脚本 = 否 28 | 自定义脚本执行命令 = 29 | 使用代理录制的平台(逗号分隔) = tiktok, sooplive, pandalive, winktv, flextv, popkontv, twitch, liveme, showroom, chzzk, shopee, shp, youtu 30 | 额外使用代理录制的平台(逗号分隔) = 31 | 32 | [推送配置] 33 | # 可选微信|钉钉|tg|邮箱|bark|ntfy 可填多个 34 | 直播状态推送渠道 = 35 | 钉钉推送接口链接 = 36 | 微信推送接口链接 = 37 | bark推送接口链接 = 38 | bark推送中断级别 = active 39 | bark推送铃声 = 40 | 钉钉通知@对象(填手机号) = 41 | 钉钉通知@全体(是/否) = 否 42 | tgapi令牌 = 43 | tg聊天id(个人或者群组id) = 44 | smtp邮件服务器 = 45 | 是否使用SMTP服务SSL加密(是/否) = 46 | SMTP邮件服务器端口 = 47 | 邮箱登录账号 = 48 | 发件人密码(授权码) = 49 | 发件人邮箱 = 50 | 发件人显示昵称 = 51 | 收件人邮箱 = 52 | ntfy推送地址 = https://ntfy.sh/xxxx 53 | ntfy推送标签 = tada 54 | ntfy推送邮箱 = 55 | 自定义推送标题 = 56 | 自定义开播推送内容 = 57 | 自定义关播推送内容 = 58 | 只推送通知不录制(是/否) = 否 59 | 直播推送检测频率(秒) = 1800 60 | 开播推送开启(是/否) = 是 61 | 关播推送开启(是/否)= 否 62 | 63 | [Cookie] 64 | # 录制抖音必填 65 | 抖音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 66 | 快手cookie = 67 | tiktok_cookie = 68 | 虎牙cookie = 69 | 斗鱼cookie = 70 | yy_cookie = 71 | b站cookie = 72 | 小红书cookie = 73 | bigo_cookie = 74 | blued_cookie = 75 | sooplive_cookie = 76 | netease_cookie = 77 | 千度热播_cookie = 78 | pandatv_cookie = 79 | 猫耳fm_cookie = 80 | winktv_cookie = 81 | flextv_cookie = 82 | look_cookie = 83 | twitcasting_cookie = 84 | baidu_cookie = 85 | weibo_cookie = 86 | kugou_cookie = 87 | twitch_cookie = 88 | liveme_cookie = 89 | huajiao_cookie = 90 | liuxing_cookie = 91 | showroom_cookie = 92 | acfun_cookie = 93 | changliao_cookie = 94 | yinbo_cookie = 95 | yingke_cookie = 96 | zhihu_cookie = 97 | chzzk_cookie = 98 | haixiu_cookie = 99 | vvxqiu_cookie = 100 | 17live_cookie = 101 | langlive_cookie = 102 | pplive_cookie = 103 | 6room_cookie = 104 | lehaitv_cookie = 105 | huamao_cookie = 106 | shopee_cookie = 107 | youtube_cookie = 108 | taobao_cookie = 109 | jd_cookie = 110 | faceit_cookie = 111 | 112 | [Authorization] 113 | popkontv_token = 114 | 115 | [账号密码] 116 | sooplive账号 = 117 | sooplive密码 = 118 | flextv账号 = 119 | flextv密码 = 120 | popkontv账号 = 121 | partner_code = P-00001 122 | popkontv密码 = 123 | twitcasting账号类型 = normal 124 | twitcasting账号 = 125 | twitcasting密码 = 126 | -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import asyncio 3 | from src.logger import logger 4 | from src import spider 5 | 6 | # 以下示例直播间链接不保证时效性,请自行查看链接是否能正常访问 7 | # Please note that the following example live room links may not be up-to-date 8 | LIVE_STREAM_CONFIG = { 9 | "douyin": { 10 | "url": "https://live.douyin.com/745964462470", 11 | "func": spider.get_douyin_app_stream_data, 12 | }, 13 | "tiktok": { 14 | "url": "https://www.tiktok.com/@pearlgaga88/live", 15 | "func": spider.get_tiktok_stream_data, 16 | }, 17 | "kuaishou": { 18 | "url": "https://live.kuaishou.com/u/yall1102", 19 | "func": spider.get_kuaishou_stream_data, 20 | }, 21 | "huya": { 22 | "url": "https://www.huya.com/116", 23 | "func": spider.get_huya_app_stream_url, 24 | }, 25 | "douyu": { 26 | "url": "https://www.douyu.com/topic/wzDBLS6?rid=4921614&dyshid=", 27 | "func": spider.get_douyu_info_data, 28 | }, 29 | "yy": { 30 | "url": "https://www.yy.com/22490906/22490906", 31 | "func": spider.get_yy_stream_data, 32 | }, 33 | "bilibili": { 34 | "url": "https://live.bilibili.com/21593109", 35 | "func": spider.get_bilibili_stream_data, 36 | }, 37 | "xhs": { 38 | "url": "https://www.xiaohongshu.com/user/profile/6330049c000000002303c7ed?appuid=5f3f478a00000000010005b3", 39 | "func": spider.get_xhs_stream_url, 40 | }, 41 | "bigo": { 42 | "url": "https://www.bigo.tv/cn/716418802", 43 | "func": spider.get_bigo_stream_url, 44 | }, 45 | "blued": { 46 | "url": "https://app.blued.cn/live?id=Mp6G2R", 47 | "func": spider.get_blued_stream_url, 48 | }, 49 | "sooplive": { 50 | "url": "https://play.sooplive.co.kr/sw7love", 51 | "func": spider.get_sooplive_stream_data, 52 | }, 53 | "netease": { 54 | "url": "https://cc.163.com/583946984", 55 | "func": spider.get_netease_stream_data, 56 | }, 57 | "qiandurebo": { 58 | "url": "https://qiandurebo.com/web/video.php?roomnumber=33333", 59 | "func": spider.get_qiandurebo_stream_data, 60 | }, 61 | "pandatv": { 62 | "url": "https://www.pandalive.co.kr/live/play/bara0109", 63 | "func": spider.get_pandatv_stream_data, 64 | }, 65 | "maoerfm": { 66 | "url": "https://fm.missevan.com/live/868895007", 67 | "func": spider.get_maoerfm_stream_url, 68 | }, 69 | "winktv": { 70 | "url": "https://www.winktv.co.kr/live/play/anjer1004", 71 | "func": spider.get_winktv_stream_data, 72 | }, 73 | "flextv": { 74 | "url": "https://www.flextv.co.kr/channels/593127/live", 75 | "func": spider.get_flextv_stream_data, 76 | }, 77 | "looklive": { 78 | "url": "https://look.163.com/live?id=65108820&position=3", 79 | "func": spider.get_looklive_stream_url, 80 | }, 81 | "popkontv": { 82 | "url": "https://www.popkontv.com/live/view?castId=wjfal007&partnerCode=P-00117", 83 | "func": spider.get_popkontv_stream_url, 84 | }, 85 | "twitcasting": { 86 | "url": "https://twitcasting.tv/c:uonq", 87 | "func": spider.get_twitcasting_stream_url, 88 | }, 89 | "baidu": { 90 | "url": "https://live.baidu.com/m/media/pclive/pchome/live.html?room_id=9175031377&tab_category", 91 | "func": spider.get_baidu_stream_data, 92 | }, 93 | "weibo": { 94 | "url": "https://weibo.com/u/7849520225", 95 | "func": spider.get_weibo_stream_data, 96 | }, 97 | "kugou": { 98 | "url": "https://fanxing2.kugou.com/50428671?refer=2177&sourceFrom=", 99 | "func": spider.get_kugou_stream_url, 100 | }, 101 | "twitchtv": { 102 | "url": "https://www.twitch.tv/gamerbee", 103 | "func": spider.get_twitchtv_stream_data, 104 | }, 105 | "liveme": { 106 | "url": "https://www.liveme.com/zh/v/17141937295821012854/index.html", 107 | "func": spider.get_liveme_stream_url, 108 | }, 109 | "huajiao": { 110 | "url": "https://www.huajiao.com/user/207446325", 111 | "func": spider.get_huajiao_stream_url, 112 | }, 113 | "showroom": { 114 | "url": "https://www.showroom-live.com/room/profile?room_id=511033", 115 | "func": spider.get_showroom_stream_data, 116 | }, 117 | "acfun": { 118 | "url": "https://live.acfun.cn/live/17912421", 119 | "func": spider.get_acfun_stream_data, 120 | }, 121 | "changliao": { 122 | "url": "https://www.tlclw.com/801044397", 123 | "func": spider.get_changliao_stream_url, 124 | }, 125 | "yingke": { 126 | "url": "https://www.inke.cn/liveroom/index.html?uid=710032101&id=1720857535354099", 127 | "func": spider.get_yingke_stream_url, 128 | }, 129 | "yinbo": { 130 | "url": "https://live.ybw1666.com/800008687", 131 | "func": spider.get_yinbo_stream_url, 132 | }, 133 | "zhihu": { 134 | "url": "https://www.zhihu.com/people/ac3a467005c5d20381a82230101308e9", 135 | "func": spider.get_zhihu_stream_url, 136 | }, 137 | "chzzk": { 138 | "url": "https://chzzk.naver.com/live/458f6ec20b034f49e0fc6d03921646d2", 139 | "func": spider.get_chzzk_stream_data, 140 | }, 141 | "haixiu": { 142 | "url": "https://www.haixiutv.com/6095106", 143 | "func": spider.get_haixiu_stream_url, 144 | }, 145 | "vvxqiu": { 146 | "url": "https://h5webcdnp.vvxqiu.com//activity/videoShare/videoShare.html?h5Server=https://h5p.vvxqiu.com&" 147 | "roomId=LP115664695&platformId=vvstar", 148 | "func": spider.get_vvxqiu_stream_url, 149 | }, 150 | "17live": { 151 | "url": "https://17.live/en/live/6302408", 152 | "func": spider.get_17live_stream_url, 153 | }, 154 | "langlive": { 155 | "url": "https://www.lang.live/en-US/room/3349463", 156 | "func": spider.get_langlive_stream_url, 157 | }, 158 | "pplive": { 159 | "url": "https://m.pp.weimipopo.com/live/preview.html?uid=91648673&anchorUid=91625862&app=plpl", 160 | "func": spider.get_pplive_stream_url, 161 | }, 162 | "6room": { 163 | "url": "https://v.6.cn/634435", 164 | "func": spider.get_6room_stream_url, 165 | }, 166 | "lehai": { 167 | "url": "https://www.lehaitv.com/8059096", 168 | "func": spider.get_haixiu_stream_url, 169 | }, 170 | "huamao": { 171 | "url": "https://h.catshow168.com/live/preview.html?uid=19066357&anchorUid=18895331", 172 | "func": spider.get_pplive_stream_url, 173 | }, 174 | "shopee": { 175 | "url": "https://sg.shp.ee/GmpXeuf?uid=1006401066&session=802458", 176 | "func": spider.get_shopee_stream_url, 177 | }, 178 | "youtube": { 179 | "url": "https://www.youtube.com/watch?v=cS6zS5hi1w0", 180 | "func": spider.get_youtube_stream_url, 181 | }, 182 | "taobao": { 183 | "url": "https://m.tb.cn/h.TWp0HTd", 184 | "func": spider.get_taobao_stream_url, 185 | }, 186 | "jd": { 187 | "url": "https://3.cn/28MLBy-E", 188 | "func": spider.get_jd_stream_url, 189 | }, 190 | "faceit": { 191 | "url": "https://www.faceit.com/zh/players/Compl1/stream", 192 | "func": spider.get_faceit_stream_data, 193 | } 194 | } 195 | 196 | 197 | def test_live_stream(platform_name: str, proxy_addr=None, cookies=None) -> None: 198 | if platform_name in LIVE_STREAM_CONFIG: 199 | config = LIVE_STREAM_CONFIG[platform_name] 200 | try: 201 | stream_data = asyncio.run(config['func'](config['url'], proxy_addr=proxy_addr, cookies=cookies)) 202 | logger.debug(f"Stream data for {platform_name}: {stream_data}") 203 | except Exception as e: 204 | logger.error(f"Error fetching stream data for {platform_name}: {e}") 205 | else: 206 | logger.warning(f"No configuration found for platform: {platform_name}") 207 | 208 | 209 | if __name__ == "__main__": 210 | platform = "douyin" 211 | test_live_stream(platform) 212 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | app: 5 | image: ihmily/douyin-live-recorder:latest 6 | environment: 7 | - TERM=xterm-256color 8 | tty: true 9 | stdin_open: true 10 | #build: . 11 | volumes: 12 | - ./config:/app/config 13 | - ./logs:/app/logs 14 | - ./backup_config:/app/backup_config 15 | - ./downloads:/app/downloads 16 | restart: always -------------------------------------------------------------------------------- /ffmpeg_install.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Author: Hmily 5 | GitHub: https://github.com/ihmily 6 | Copyright (c) 2024 by Hmily, All Rights Reserved. 7 | """ 8 | 9 | import os 10 | import re 11 | import subprocess 12 | import sys 13 | import platform 14 | import zipfile 15 | from pathlib import Path 16 | import requests 17 | from tqdm import tqdm 18 | from src.logger import logger 19 | 20 | current_platform = platform.system() 21 | execute_dir = os.path.split(os.path.realpath(sys.argv[0]))[0] 22 | current_env_path = os.environ.get('PATH') 23 | ffmpeg_path = os.path.join(execute_dir, 'ffmpeg') 24 | 25 | 26 | def unzip_file(zip_path: str | Path, extract_to: str | Path, delete: bool = True) -> None: 27 | if not os.path.exists(extract_to): 28 | os.makedirs(extract_to) 29 | 30 | with zipfile.ZipFile(zip_path, 'r') as zip_ref: 31 | zip_ref.extractall(extract_to) 32 | 33 | if delete and os.path.exists(zip_path): 34 | os.remove(zip_path) 35 | 36 | 37 | def get_lanzou_download_link(url: str, password: str | None = None) -> str | None: 38 | try: 39 | headers = { 40 | 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 41 | 'Origin': 'https://wweb.lanzouv.com', 42 | 'Referer': 'https://wweb.lanzouv.com/iXncv0dly6mh', 43 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' 44 | 'Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', 45 | } 46 | response = requests.get(url, headers=headers) 47 | sign = re.search("var skdklds = '(.*?)';", response.text).group(1) 48 | data = { 49 | 'action': 'downprocess', 50 | 'sign': sign, 51 | 'p': password, 52 | 'kd': '1', 53 | } 54 | response = requests.post('https://wweb.lanzouv.com/ajaxm.php', headers=headers, data=data) 55 | json_data = response.json() 56 | download_url = json_data['dom'] + "/file/" + json_data['url'] 57 | response = requests.get(download_url, headers=headers) 58 | return response.url 59 | except Exception as e: 60 | logger.error(f"Failed to obtain ffmpeg download address. {e}") 61 | 62 | 63 | def install_ffmpeg_windows(): 64 | try: 65 | logger.warning("ffmpeg is not installed.") 66 | logger.debug("Installing the latest version of ffmpeg for Windows...") 67 | ffmpeg_url = get_lanzou_download_link('https://wweb.lanzouv.com/iHAc22ly3r3g', 'eots') 68 | if ffmpeg_url: 69 | full_file_name = 'ffmpeg_latest_build_20250124.zip' 70 | version = 'v20250124' 71 | zip_file_path = Path(execute_dir) / full_file_name 72 | if Path(zip_file_path).exists(): 73 | logger.debug("ffmpeg installation file already exists, start install...") 74 | else: 75 | response = requests.get(ffmpeg_url, stream=True) 76 | total_size = int(response.headers.get('Content-Length', 0)) 77 | block_size = 1024 78 | 79 | with tqdm(total=total_size, unit="B", unit_scale=True, 80 | ncols=100, desc=f'Downloading ffmpeg ({version})') as t: 81 | with open(zip_file_path, 'wb') as f: 82 | for data in response.iter_content(block_size): 83 | t.update(len(data)) 84 | f.write(data) 85 | 86 | unzip_file(zip_file_path, execute_dir) 87 | os.environ['PATH'] = ffmpeg_path + os.pathsep + current_env_path 88 | result = subprocess.run(["ffmpeg", "-version"], capture_output=True) 89 | if result.returncode == 0: 90 | logger.debug('ffmpeg installation was successful') 91 | else: 92 | logger.error('ffmpeg installation failed. Please manually install ffmpeg by yourself') 93 | return True 94 | else: 95 | logger.error("Please manually install ffmpeg by yourself") 96 | except Exception as e: 97 | logger.error(f"type: {type(e).__name__}, ffmpeg installation failed {e}") 98 | 99 | 100 | def install_ffmpeg_mac(): 101 | logger.warning("ffmpeg is not installed.") 102 | logger.debug("Installing the stable version of ffmpeg for macOS...") 103 | try: 104 | result = subprocess.run(["brew", "install", "ffmpeg"], capture_output=True) 105 | if result.returncode == 0: 106 | logger.debug('ffmpeg installation was successful. Restart for changes to take effect.') 107 | return True 108 | else: 109 | logger.error("ffmpeg installation failed") 110 | except subprocess.CalledProcessError as e: 111 | logger.error(f"Failed to install ffmpeg using Homebrew. {e}") 112 | logger.error("Please install ffmpeg manually or check your Homebrew installation.") 113 | except Exception as e: 114 | logger.error(f"An unexpected error occurred: {e}") 115 | 116 | 117 | def install_ffmpeg_linux(): 118 | is_RHS = True 119 | 120 | try: 121 | logger.warning("ffmpeg is not installed.") 122 | logger.debug("Trying to install the stable version of ffmpeg") 123 | result = subprocess.run(['yum', '-y', 'update'], capture_output=True) 124 | if result.returncode != 0: 125 | logger.error(f"Failed to update package lists using yum.") 126 | return False 127 | 128 | result = subprocess.run(['yum', 'install', '-y', 'ffmpeg'], capture_output=True) 129 | if result.returncode == 0: 130 | logger.debug("ffmpeg installation was successful using yum. Restart for changes to take effect.") 131 | return True 132 | logger.error(result.stderr.decode('utf-8').strip()) 133 | except FileNotFoundError: 134 | logger.debug("yum command not found, trying to install using apt...") 135 | is_RHS = False 136 | except Exception as e: 137 | logger.error(f"An error occurred while trying to install ffmpeg using yum: {e}") 138 | 139 | if not is_RHS: 140 | try: 141 | logger.debug("Trying to install the stable version of ffmpeg for Linux using apt...") 142 | result = subprocess.run(['apt', 'update'], capture_output=True) 143 | if result.returncode != 0: 144 | logger.error("Failed to update package lists using apt") 145 | return False 146 | 147 | result = subprocess.run(['apt', 'install', '-y', 'ffmpeg'], capture_output=True) 148 | if result.returncode == 0: 149 | logger.debug("ffmpeg installation was successful using apt. Restart for changes to take effect.") 150 | return True 151 | else: 152 | logger.error(result.stderr.decode('utf-8').strip()) 153 | except FileNotFoundError: 154 | logger.error("apt command not found, unable to install ffmpeg. Please manually install ffmpeg by yourself") 155 | except Exception as e: 156 | logger.error(f"An error occurred while trying to install ffmpeg using apt: {e}") 157 | logger.error("Manual installation of ffmpeg is required. Please manually install ffmpeg by yourself.") 158 | return False 159 | 160 | 161 | def install_ffmpeg() -> bool: 162 | if current_platform == "Windows": 163 | return install_ffmpeg_windows() 164 | elif current_platform == "Linux": 165 | return install_ffmpeg_linux() 166 | elif current_platform == "Darwin": 167 | return install_ffmpeg_mac() 168 | else: 169 | logger.debug(f"ffmpeg auto installation is not supported on this platform: {current_platform}. " 170 | f"Please install ffmpeg manually.") 171 | return False 172 | 173 | 174 | def ensure_ffmpeg_installed(func): 175 | def wrapper(*args, **kwargs): 176 | try: 177 | result = subprocess.run(['ffmpeg', '-version'], capture_output=True) 178 | version = result.stdout.strip() 179 | if result.returncode == 0 and version: 180 | return func(*args, **kwargs) 181 | except FileNotFoundError: 182 | pass 183 | return False 184 | 185 | def wrapped_func(*args, **kwargs): 186 | if sys.version_info >= (3, 7): 187 | res = wrapper(*args, **kwargs) 188 | else: 189 | res = wrapper(*args, **kwargs) 190 | if not res: 191 | install_ffmpeg() 192 | res = wrapper(*args, **kwargs) 193 | 194 | if not res: 195 | raise RuntimeError("ffmpeg is not installed.") 196 | 197 | return func(*args, **kwargs) 198 | 199 | return wrapped_func 200 | 201 | 202 | def check_ffmpeg_installed() -> bool: 203 | try: 204 | result = subprocess.run(['ffmpeg', '-version'], capture_output=True) 205 | version = result.stdout.strip() 206 | if result.returncode == 0 and version: 207 | return True 208 | except FileNotFoundError: 209 | pass 210 | except OSError as e: 211 | print(f"OSError occurred: {e}. ffmpeg may not be installed correctly or is not available in the system PATH.") 212 | print("Please delete the ffmpeg and try to download and install again.") 213 | except Exception as e: 214 | print(f"An unexpected error occurred: {e}") 215 | return False 216 | 217 | 218 | def check_ffmpeg() -> bool: 219 | if not check_ffmpeg_installed(): 220 | return install_ffmpeg() 221 | return True 222 | -------------------------------------------------------------------------------- /i18n.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import gettext 4 | import inspect 5 | import builtins 6 | from pathlib import Path 7 | 8 | 9 | def init_gettext(locale_dir, locale_name): 10 | gettext.bindtextdomain('zh_CN', locale_dir) 11 | gettext.textdomain('zh_CN') 12 | os.environ['LANG'] = f'{locale_name}.utf8' 13 | return gettext.gettext 14 | 15 | 16 | execute_dir = os.path.split(os.path.realpath(sys.argv[0]))[0] 17 | if os.path.exists(Path(execute_dir) / '_internal/i18n'): 18 | locale_path = Path(execute_dir) / '_internal/i18n' 19 | else: 20 | locale_path = Path(execute_dir) / 'i18n' 21 | _tr = init_gettext(locale_path, 'zh_CN') 22 | original_print = builtins.print 23 | package_name = 'src' 24 | 25 | 26 | def translated_print(*args, **kwargs): 27 | for arg in args: 28 | if package_name in inspect.stack()[1].filename: 29 | translated_arg = _tr(str(arg)) 30 | else: 31 | translated_arg = str(arg) 32 | original_print(translated_arg, **kwargs) 33 | -------------------------------------------------------------------------------- /i18n/en/LC_MESSAGES/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihmily/DouyinLiveRecorder/fd06bc89da6ad6d270e1858bcb86302e954e543d/i18n/en/LC_MESSAGES/.gitkeep -------------------------------------------------------------------------------- /i18n/zh_CN/LC_MESSAGES/zh_CN.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihmily/DouyinLiveRecorder/fd06bc89da6ad6d270e1858bcb86302e954e543d/i18n/zh_CN/LC_MESSAGES/zh_CN.mo -------------------------------------------------------------------------------- /i18n/zh_CN/LC_MESSAGES/zh_CN.po: -------------------------------------------------------------------------------- 1 | # DouyinLiveRecorder. 2 | # Copyright (C) 2024 Hmily 3 | # This file is distributed under the same license as the DouyinLiveRecorder package. 4 | # 5 | #, fuzzy 6 | 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: 4.0.1\n" 10 | "POT-Creation-Date: 2024-10-20 00:00+0800\n" 11 | "PO-Revision-Date: 2024-11-09 03:05+0800\n" 12 | "Last-Translator: Hmily \n" 13 | "Language-Team: Chinese\n" 14 | "Content-Type: text/plain; charset=UTF-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | "Language: zh_CN\n" 17 | "Plural-Forms: nplurals=1; plural=0;\n" 18 | 19 | #: douyinliverecorder/spider.py 20 | msgid "IP banned. Please change device or network." 21 | msgstr "IP被禁止 请更换设备或网络" 22 | 23 | msgid "The anchor did not start broadcasting." 24 | msgstr "主播并未开播" 25 | 26 | msgid "sooplive platform login successful! Starting to fetch live streaming data..." 27 | msgstr "sooplive平台登录成功!开始获取直播数据..." 28 | 29 | msgid "sooplive live stream failed to retrieve, the live stream just ended." 30 | msgstr "sooplive直播获取失败,该直播间刚结束直播" 31 | 32 | msgid "sooplive live stream retrieval failed, the live needs 19+, you are not logged in." 33 | msgstr "soop直播获取失败,该直播间需要年龄19+观看,您尚未登录" 34 | 35 | msgid "Attempting to log in to the sooplive live streaming platform with your account and password, please ensure it is configured." 36 | msgstr "正在尝试使用您的账号和密码登录soop直播平台,请确保已在config配置文件中配置" 37 | 38 | msgid "error message:Please check if the input sooplive live room address is correct." 39 | msgstr "错误信息:请检查输入的sooplive直播间地址是否正确" 40 | 41 | msgid "Please check if the FlexTV account and password in the configuration file are correct." 42 | msgstr "请检查配置文件中的FlexTV账号和密码是否正确" 43 | 44 | msgid "FlexTV live stream retrieval failed [not logged in]: 19+ live streams are only available for logged-in adults." 45 | msgstr "FlexTV直播获取失败[未登录]: 19+直播需要登录后是成人才可观看" 46 | 47 | msgid "Attempting to log in to the FlexTV live streaming platform, please ensure your account and password are correctly filled in the configuration file." 48 | msgstr "正在尝试登录FlexTV直播平台,请确保已在配置文件中填写好您的账号和密码" 49 | 50 | msgid "Logging into FlexTV platform..." 51 | msgstr "FlexTV平台登录中..." 52 | 53 | msgid "Logged into FlexTV platform successfully! Starting to fetch live streaming data..." 54 | msgstr "FlexTV平台登录成功!开始获取直播数据..." 55 | 56 | msgid "Look live currently only supports audio live streaming, not video live streaming!" 57 | msgstr "Look直播暂时只支持音频直播,不支持Look视频直播!" 58 | 59 | msgid "Failed to retrieve popkontv live stream [token does not exist or has expired]: Please log in to watch." 60 | msgstr "popkontv直播获取失败[token不存在或者已过期]: 请登录后观看" 61 | 62 | msgid "Attempting to log in to the popkontv live streaming platform, please ensure your account and password are correctly filled in the configuration file." 63 | msgstr "正在尝试登录popkontv直播平台,请确保已在配置文件中填写好您的账号和密码" 64 | 65 | msgid "Logging into popkontv platform..." 66 | msgstr "popkontv平台登录中..." 67 | 68 | msgid "Logged into popkontv platform successfully! Starting to fetch live streaming data..." 69 | msgstr "popkontv平台登录成功!开始获取直播数据..." 70 | 71 | msgid "Attempting to log in to TwitCasting..." 72 | msgstr "TwitCasting正在尝试登录..." 73 | 74 | msgid "TwitCasting login successful! Starting to fetch data..." 75 | msgstr "TwitCasting 登录成功!开始获取数据..." 76 | 77 | msgid "Failed to retrieve TwitCasting data, attempting to log in..." 78 | msgstr "获取TwitCasting数据失败,正在尝试登录..." 79 | 80 | msgid "Failed to retrieve live room data, the Huajiao live room address is not fixed, please manually change the address for recording." 81 | msgstr "获取直播间数据失败,花椒直播间地址是非固定的,请手动更换地址进行录制" 82 | 83 | msgid "Fetch shopee live data failed, please update the address of the live broadcast room and try again." 84 | msgstr "获取shopee直播间数据失败,请手动更换直播录制地址后重试" 85 | 86 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | M3U8 视频播放器 14 | 15 | 16 | 17 | 18 | 19 | 20 | 138 | 139 | 140 |
141 | 142 | 143 |
144 | 145 |
146 |
147 |

说明

148 |

M3U8文件格式

149 |

M3U8文件是采用UTF-8编码格式的M3U文件。M3U文件本身是一个纯文本索引文件,其核心功能是记录多媒体文件链接。当用户打开此类文件时,播放软件会根据索引查找相应的音视频文件网络地址,然后进行在线播放。

150 |

M3U最初设计用于播放音频文件,例如MP3。但随着时间推移,更多的播放器和软件开始使用M3U来播放视频文件列表,同时也支持在线流媒体音频源的指定。目前,许多播放器和软件都兼容M3U文件格式。

151 |

FLV文件格式(Flash Video Format)是Adobe公司开发的一种专门用于网页视频播放的文件格式。FLV格式的视频文件通常用于播放短视频和在线流媒体,可以嵌入到网页中供用户观看。FLV视频通常由Adobe Flash Player播放器播放,而其他第三方播放器也支持此格式。

152 |
153 | 156 | 213 |
214 | 215 | 216 | -------------------------------------------------------------------------------- /msg_push.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Author: Hmily 5 | GitHub: https://github.com/ihmily 6 | Date: 2023-09-03 19:18:36 7 | Update: 2025-01-23 17:16:12 8 | Copyright (c) 2023-2024 by Hmily, All Rights Reserved. 9 | """ 10 | from typing import Dict, Any 11 | import json 12 | import base64 13 | import urllib.request 14 | import urllib.error 15 | import smtplib 16 | from email.header import Header 17 | from email.mime.multipart import MIMEMultipart 18 | from email.mime.text import MIMEText 19 | 20 | no_proxy_handler = urllib.request.ProxyHandler({}) 21 | opener = urllib.request.build_opener(no_proxy_handler) 22 | headers: Dict[str, str] = {'Content-Type': 'application/json'} 23 | 24 | 25 | def dingtalk(url: str, content: str, number: str = None, is_atall: bool = False) -> Dict[str, Any]: 26 | success = [] 27 | error = [] 28 | api_list = url.replace(',', ',').split(',') if url.strip() else [] 29 | for api in api_list: 30 | json_data = { 31 | 'msgtype': 'text', 32 | 'text': { 33 | 'content': content, 34 | }, 35 | "at": { 36 | "atMobiles": [ 37 | number 38 | ], 39 | "isAtAll": is_atall 40 | }, 41 | } 42 | try: 43 | data = json.dumps(json_data).encode('utf-8') 44 | req = urllib.request.Request(api, data=data, headers=headers) 45 | response = opener.open(req, timeout=10) 46 | json_str = response.read().decode('utf-8') 47 | json_data = json.loads(json_str) 48 | if json_data['errcode'] == 0: 49 | success.append(api) 50 | else: 51 | error.append(api) 52 | print(f'钉钉推送失败, 推送地址:{api}, {json_data["errmsg"]}') 53 | except Exception as e: 54 | error.append(api) 55 | print(f'钉钉推送失败, 推送地址:{api}, 错误信息:{e}') 56 | return {"success": success, "error": error} 57 | 58 | 59 | def xizhi(url: str, title: str, content: str) -> Dict[str, Any]: 60 | success = [] 61 | error = [] 62 | api_list = url.replace(',', ',').split(',') if url.strip() else [] 63 | for api in api_list: 64 | json_data = { 65 | 'title': title, 66 | 'content': content 67 | } 68 | try: 69 | data = json.dumps(json_data).encode('utf-8') 70 | req = urllib.request.Request(api, data=data, headers=headers) 71 | response = opener.open(req, timeout=10) 72 | json_str = response.read().decode('utf-8') 73 | json_data = json.loads(json_str) 74 | if json_data['code'] == 200: 75 | success.append(api) 76 | else: 77 | error.append(api) 78 | print(f'微信推送失败, 推送地址:{api}, 失败信息:{json_data["msg"]}') 79 | except Exception as e: 80 | error.append(api) 81 | print(f'微信推送失败, 推送地址:{api}, 错误信息:{e}') 82 | return {"success": success, "error": error} 83 | 84 | 85 | def send_email(email_host: str, login_email: str, email_pass: str, sender_email: str, sender_name: str, 86 | to_email: str, title: str, content: str, smtp_port: str = None, open_ssl: bool = True) -> Dict[str, Any]: 87 | receivers = to_email.replace(',', ',').split(',') if to_email.strip() else [] 88 | 89 | try: 90 | message = MIMEMultipart() 91 | send_name = base64.b64encode(sender_name.encode("utf-8")).decode() 92 | message['From'] = f'=?UTF-8?B?{send_name}?= <{sender_email}>' 93 | message['Subject'] = Header(title, 'utf-8') 94 | if len(receivers) == 1: 95 | message['To'] = receivers[0] 96 | 97 | t_apart = MIMEText(content, 'plain', 'utf-8') 98 | message.attach(t_apart) 99 | 100 | if open_ssl: 101 | smtp_port = int(smtp_port) or 465 102 | smtp_obj = smtplib.SMTP_SSL(email_host, smtp_port) 103 | else: 104 | smtp_port = int(smtp_port) or 25 105 | smtp_obj = smtplib.SMTP(email_host, smtp_port) 106 | smtp_obj.login(login_email, email_pass) 107 | smtp_obj.sendmail(sender_email, receivers, message.as_string()) 108 | return {"success": receivers, "error": []} 109 | except smtplib.SMTPException as e: 110 | print(f'邮件推送失败, 推送邮箱:{to_email}, 错误信息:{e}') 111 | return {"success": [], "error": receivers} 112 | 113 | 114 | def tg_bot(chat_id: int, token: str, content: str) -> Dict[str, Any]: 115 | try: 116 | json_data = { 117 | "chat_id": chat_id, 118 | 'text': content 119 | } 120 | url = f'https://api.telegram.org/bot{token}/sendMessage' 121 | data = json.dumps(json_data).encode('utf-8') 122 | req = urllib.request.Request(url, data=data, headers=headers) 123 | response = urllib.request.urlopen(req, timeout=15) 124 | json_str = response.read().decode('utf-8') 125 | _json_data = json.loads(json_str) 126 | return {"success": [1], "error": []} 127 | except Exception as e: 128 | print(f'tg推送失败, 聊天ID:{chat_id}, 错误信息:{e}') 129 | return {"success": [], "error": [1]} 130 | 131 | 132 | def bark(api: str, title: str = "message", content: str = 'test', level: str = "active", 133 | badge: int = 1, auto_copy: int = 1, sound: str = "", icon: str = "", group: str = "", 134 | is_archive: int = 1, url: str = "") -> Dict[str, Any]: 135 | success = [] 136 | error = [] 137 | api_list = api.replace(',', ',').split(',') if api.strip() else [] 138 | for _api in api_list: 139 | json_data = { 140 | "title": title, 141 | "body": content, 142 | "level": level, 143 | "badge": badge, 144 | "autoCopy": auto_copy, 145 | "sound": sound, 146 | "icon": icon, 147 | "group": group, 148 | "isArchive": is_archive, 149 | "url": url 150 | } 151 | try: 152 | data = json.dumps(json_data).encode('utf-8') 153 | req = urllib.request.Request(_api, data=data, headers=headers) 154 | response = opener.open(req, timeout=10) 155 | json_str = response.read().decode("utf-8") 156 | json_data = json.loads(json_str) 157 | if json_data['code'] == 200: 158 | success.append(_api) 159 | else: 160 | error.append(_api) 161 | print(f'Bark推送失败, 推送地址:{_api}, 失败信息:{json_data["message"]}') 162 | except Exception as e: 163 | error.append(api) 164 | print(f'Bark推送失败, 推送地址:{_api}, 错误信息:{e}') 165 | return {"success": success, "error": error} 166 | 167 | 168 | def ntfy(api: str, title: str = "message", content: str = 'test', tags: str = 'tada', priority: int = 3, 169 | action_url: str = "", attach: str = "", filename: str = "", click: str = "", icon: str = "", 170 | delay: str = "", email: str = "", call: str = "") -> Dict[str, Any]: 171 | success = [] 172 | error = [] 173 | api_list = api.replace(',', ',').split(',') if api.strip() else [] 174 | tags = tags.replace(',', ',').split(',') if tags else ['partying_face'] 175 | actions = [{"action": "view", "label": "view live", "url": action_url}] if action_url else [] 176 | for _api in api_list: 177 | server, topic = _api.rsplit('/', maxsplit=1) 178 | json_data = { 179 | "topic": topic, 180 | "title": title, 181 | "message": content, 182 | "tags": tags, 183 | "priority": priority, 184 | "attach": attach, 185 | "filename": filename, 186 | "click": click, 187 | "actions": actions, 188 | "markdown": False, 189 | "icon": icon, 190 | "delay": delay, 191 | "email": email, 192 | "call": call 193 | } 194 | 195 | try: 196 | data = json.dumps(json_data, ensure_ascii=False).encode('utf-8') 197 | req = urllib.request.Request(server, data=data, headers=headers) 198 | response = opener.open(req, timeout=10) 199 | json_str = response.read().decode("utf-8") 200 | json_data = json.loads(json_str) 201 | if "error" not in json_data: 202 | success.append(_api) 203 | else: 204 | error.append(_api) 205 | print(f'ntfy推送失败, 推送地址:{_api}, 失败信息:{json_data["error"]}') 206 | except urllib.error.HTTPError as e: 207 | error.append(_api) 208 | error_msg = e.read().decode("utf-8") 209 | print(f'ntfy推送失败, 推送地址:{_api}, 错误信息:{json.loads(error_msg)["error"]}') 210 | except Exception as e: 211 | error.append(api) 212 | print(f'ntfy推送失败, 推送地址:{_api}, 错误信息:{e}') 213 | return {"success": success, "error": error} 214 | 215 | 216 | if __name__ == '__main__': 217 | send_title = '直播通知' # 标题 218 | send_content = '张三 开播了!' # 推送内容 219 | 220 | # 钉钉推送通知 221 | webhook_api = '' # 替换成自己Webhook链接,参考文档:https://open.dingtalk.com/document/robots/custom-robot-access 222 | phone_number = '' # 被@用户的手机号码 223 | is_atall = '' # 是否@全体 224 | # dingtalk(webhook_api, send_content, phone_number) 225 | 226 | # 微信推送通知 227 | # 替换成自己的单点推送接口,获取地址:https://xz.qqoq.net/#/admin/one 228 | # 当然也可以使用其他平台API 如server酱 使用方法一样 229 | xizhi_api = 'https://xizhi.qqoq.net/xxxxxxxxx.send' 230 | # xizhi(xizhi_api, send_content) 231 | 232 | # telegram推送通知 233 | tg_token = '' # tg搜索"BotFather"获取的token值 234 | tg_chat_id = 000000 # tg搜索"userinfobot"获取的chat_id值,即可发送推送消息给你自己,如果下面的是群组id则发送到群 235 | # tg_bot(tg_chat_id, tg_token, send_content) 236 | 237 | # email_message( 238 | # email_host="smtp.qq.com", 239 | # login_email="", 240 | # email_pass="", 241 | # sender_email="", 242 | # sender_name="", 243 | # to_email="", 244 | # title="", 245 | # content="", 246 | # ) 247 | 248 | bark_url = 'https://xxx.xxx.com/key/' 249 | # bark(bark_url, send_title, send_content) 250 | 251 | ntfy( 252 | api="https://ntfy.sh/xxxxx", 253 | title="直播推送", 254 | content="xxx已开播", 255 | ) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.31.0 2 | loguru>=0.7.3 3 | pycryptodome>=3.20.0 4 | distro>=1.9.0 5 | tqdm>=4.67.1 6 | httpx[http2]>=0.28.1 7 | PyExecJS>=1.5.1 -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from pathlib import Path 4 | from .initializer import check_node 5 | 6 | current_file_path = Path(__file__).resolve() 7 | current_dir = current_file_path.parent 8 | JS_SCRIPT_PATH = current_dir / 'javascript' 9 | 10 | execute_dir = os.path.split(os.path.realpath(sys.argv[0]))[0] 11 | node_execute_dir = Path(execute_dir) / 'node' 12 | current_env_path = os.environ.get('PATH') 13 | os.environ['PATH'] = str(node_execute_dir) + os.pathsep + current_env_path 14 | check_node() 15 | -------------------------------------------------------------------------------- /src/http_clients/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ihmily/DouyinLiveRecorder/fd06bc89da6ad6d270e1858bcb86302e954e543d/src/http_clients/__init__.py -------------------------------------------------------------------------------- /src/http_clients/async_http.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import httpx 3 | from typing import Dict, Any 4 | from .. import utils 5 | 6 | OptionalStr = str | None 7 | OptionalDict = Dict[str, Any] | None 8 | 9 | 10 | async def async_req( 11 | url: str, 12 | proxy_addr: OptionalStr = None, 13 | headers: OptionalDict = None, 14 | data: dict | bytes | None = None, 15 | json_data: dict | list | None = None, 16 | timeout: int = 20, 17 | redirect_url: bool = False, 18 | return_cookies: bool = False, 19 | include_cookies: bool = False, 20 | abroad: bool = False, 21 | content_conding: str = 'utf-8', 22 | verify: bool = False, 23 | http2: bool = True 24 | ) -> OptionalDict | OptionalStr | tuple: 25 | if headers is None: 26 | headers = {} 27 | try: 28 | proxy_addr = utils.handle_proxy_addr(proxy_addr) 29 | if data or json_data: 30 | async with httpx.AsyncClient(proxy=proxy_addr, timeout=timeout, verify=verify, http2=http2) as client: 31 | response = await client.post(url, data=data, json=json_data, headers=headers) 32 | else: 33 | async with httpx.AsyncClient(proxy=proxy_addr, timeout=timeout, verify=verify, http2=http2) as client: 34 | response = await client.get(url, headers=headers, follow_redirects=True) 35 | 36 | if redirect_url: 37 | return str(response.url) 38 | elif return_cookies: 39 | cookies_dict = {name: value for name, value in response.cookies.items()} 40 | return (response.text, cookies_dict) if include_cookies else cookies_dict 41 | else: 42 | resp_str = response.text 43 | except Exception as e: 44 | resp_str = str(e) 45 | 46 | return resp_str 47 | 48 | 49 | async def get_response_status(url: str, proxy_addr: OptionalStr = None, headers: OptionalDict = None, 50 | timeout: int = 10, abroad: bool = False, verify: bool = False, http2=False) -> bool: 51 | 52 | try: 53 | proxy_addr = utils.handle_proxy_addr(proxy_addr) 54 | async with httpx.AsyncClient(proxy=proxy_addr, timeout=timeout, verify=verify) as client: 55 | response = await client.head(url, headers=headers, follow_redirects=True) 56 | return response.status_code == 200 57 | except Exception as e: 58 | print(e) 59 | return False 60 | -------------------------------------------------------------------------------- /src/http_clients/sync_http.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import gzip 3 | import urllib.parse 4 | import urllib.error 5 | from urllib.request import Request 6 | import requests 7 | import ssl 8 | import json 9 | import urllib.request 10 | 11 | no_proxy_handler = urllib.request.ProxyHandler({}) 12 | opener = urllib.request.build_opener(no_proxy_handler) 13 | 14 | ssl_context = ssl.create_default_context() 15 | ssl_context.check_hostname = False 16 | ssl_context.verify_mode = ssl.CERT_NONE 17 | OptionalStr = str | None 18 | OptionalDict = dict | None 19 | 20 | 21 | def sync_req( 22 | url: str, 23 | proxy_addr: OptionalStr = None, 24 | headers: OptionalDict = None, 25 | data: dict | bytes | None = None, 26 | json_data: dict | list | None = None, 27 | timeout: int = 20, 28 | redirect_url: bool = False, 29 | abroad: bool = False, 30 | content_conding: str = 'utf-8' 31 | ) -> str: 32 | if headers is None: 33 | headers = {} 34 | try: 35 | if proxy_addr: 36 | proxies = { 37 | 'http': proxy_addr, 38 | 'https': proxy_addr 39 | } 40 | if data or json_data: 41 | response = requests.post( 42 | url, data=data, json=json_data, headers=headers, proxies=proxies, timeout=timeout 43 | ) 44 | else: 45 | response = requests.get(url, headers=headers, proxies=proxies, timeout=timeout) 46 | if redirect_url: 47 | return response.url 48 | resp_str = response.text 49 | else: 50 | if data and not isinstance(data, bytes): 51 | data = urllib.parse.urlencode(data).encode(content_conding) 52 | if json_data and isinstance(json_data, (dict, list)): 53 | data = json.dumps(json_data).encode(content_conding) 54 | 55 | req = urllib.request.Request(url, data=data, headers=headers) 56 | 57 | try: 58 | if abroad: 59 | response = urllib.request.urlopen(req, timeout=timeout) 60 | else: 61 | response = opener.open(req, timeout=timeout) 62 | if redirect_url: 63 | return response.url 64 | content_encoding = response.info().get('Content-Encoding') 65 | try: 66 | if content_encoding == 'gzip': 67 | with gzip.open(response, 'rt', encoding=content_conding) as gzipped: 68 | resp_str = gzipped.read() 69 | else: 70 | resp_str = response.read().decode(content_conding) 71 | finally: 72 | response.close() 73 | 74 | except urllib.error.HTTPError as e: 75 | if e.code == 400: 76 | resp_str = e.read().decode(content_conding) 77 | else: 78 | raise 79 | except urllib.error.URLError as e: 80 | print(f"URL Error: {e}") 81 | raise 82 | except Exception as e: 83 | print(f"An error occurred: {e}") 84 | raise 85 | 86 | except Exception as e: 87 | resp_str = str(e) 88 | 89 | return resp_str 90 | -------------------------------------------------------------------------------- /src/initializer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Author: Hmily 5 | GitHub:https://github.com/ihmily 6 | Copyright (c) 2024 by Hmily, All Rights Reserved. 7 | """ 8 | 9 | import os 10 | import subprocess 11 | import sys 12 | import platform 13 | import zipfile 14 | from pathlib import Path 15 | import requests 16 | import re 17 | import distro 18 | from tqdm import tqdm 19 | from .logger import logger 20 | 21 | current_platform = platform.system() 22 | execute_dir = os.path.split(os.path.realpath(sys.argv[0]))[0] 23 | current_env_path = os.environ.get('PATH') 24 | 25 | 26 | def unzip_file(zip_path: str | Path, extract_to: str | Path, delete: bool = True) -> None: 27 | if not os.path.exists(extract_to): 28 | os.makedirs(extract_to) 29 | 30 | with zipfile.ZipFile(zip_path, 'r') as zip_ref: 31 | zip_ref.extractall(extract_to) 32 | 33 | if delete and os.path.exists(zip_path): 34 | os.remove(zip_path) 35 | 36 | 37 | def install_nodejs_windows(): 38 | try: 39 | logger.warning("Node.js is not installed.") 40 | logger.debug("Installing the stable version of Node.js for Windows...") 41 | response = requests.get('https://nodejs.cn/download/') 42 | if response.status_code == 200: 43 | match = re.search('https://npmmirror.com/mirrors/node/(v.*?)/node-(v.*?)-x64.msi', 44 | response.text) 45 | if match: 46 | version = match.group(1) 47 | system_bit = 'x64' if '32' not in platform.machine() else 'x86' 48 | url = f'https://npmmirror.com/mirrors/node/{version}/node-{version}-win-{system_bit}.zip' 49 | else: 50 | logger.error("Failed to retrieve the download URL for the latest version of Node.js...") 51 | return 52 | 53 | full_file_name = url.rsplit('/', maxsplit=1)[-1] 54 | zip_file_path = Path(execute_dir) / full_file_name 55 | 56 | if Path(zip_file_path).exists(): 57 | logger.debug("Node.js installation file already exists, start install...") 58 | else: 59 | response = requests.get(url, stream=True) 60 | total_size = int(response.headers.get('Content-Length', 0)) 61 | block_size = 1024 62 | 63 | with tqdm(total=total_size, unit="B", unit_scale=True, 64 | ncols=100, desc=f'Downloading Node.js ({version})') as t: 65 | with open(zip_file_path, 'wb') as f: 66 | for data in response.iter_content(block_size): 67 | t.update(len(data)) 68 | f.write(data) 69 | 70 | unzip_file(zip_file_path, execute_dir) 71 | extract_dir_path = str(zip_file_path).rsplit('.', maxsplit=1)[0] 72 | f_path, f_name = os.path.splitext(zip_file_path) 73 | new_extract_dir_path = Path(f_path).parent / 'node' 74 | if Path(extract_dir_path).exists() and not Path(new_extract_dir_path).exists(): 75 | os.rename(extract_dir_path, new_extract_dir_path) 76 | os.environ['PATH'] = execute_dir + '/node' + os.pathsep + current_env_path 77 | result = subprocess.run(["node", "-v"], capture_output=True) 78 | if result.returncode == 0: 79 | logger.debug('Node.js installation was successful. Restart for changes to take effect') 80 | else: 81 | logger.debug('Node.js installation failed') 82 | return True 83 | else: 84 | logger.error("Failed to retrieve the Node.js version page") 85 | 86 | except Exception as e: 87 | logger.error(f"type: {type(e).__name__}, Node.js installation failed {e}") 88 | 89 | 90 | def install_nodejs_centos(): 91 | try: 92 | logger.warning("Node.js is not installed.") 93 | logger.debug("Installing the latest version of Node.js for CentOS...") 94 | result = subprocess.run('curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/nodesource/rpm/setup_lts.x | ' 95 | 'bash -', shell=True, capture_output=True) 96 | if result.returncode != 0: 97 | logger.error("Failed to run NodeSource installation script") 98 | return 99 | 100 | result = subprocess.run(['yum', 'install', '-y', 'epel-release'], capture_output=True) 101 | if result.returncode != 0: 102 | logger.error("Failed to install EPEL repository") 103 | return 104 | 105 | result = subprocess.run(['yum', 'install', '-y', 'nodejs'], capture_output=True) 106 | if result.returncode == 0: 107 | logger.debug('Node.js installation was successful. Restart for changes to take effect.') 108 | return True 109 | else: 110 | logger.error("Node.js installation failed") 111 | 112 | except Exception as e: 113 | logger.error(f"type: {type(e).__name__}, Node.js installation failed {e}") 114 | 115 | 116 | def install_nodejs_ubuntu(): 117 | try: 118 | logger.warning("Node.js is not installed.") 119 | logger.debug("Installing the latest version of Node.js for Ubuntu...") 120 | install_script = 'curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -' 121 | result = subprocess.run(install_script, shell=True, capture_output=True) 122 | if result.returncode != 0: 123 | logger.error("Failed to run NodeSource installation script") 124 | return 125 | 126 | install_command = ['apt', 'install', '-y', 'nodejs'] 127 | result = subprocess.run(install_command, capture_output=True) 128 | if result.returncode == 0: 129 | logger.debug('Node.js installation was successful. Restart for changes to take effect.') 130 | return True 131 | else: 132 | logger.error("Node.js installation failed") 133 | except Exception as e: 134 | logger.error(f"type: {type(e).__name__}, Node.js installation failed, {e}") 135 | 136 | 137 | def install_nodejs_mac(): 138 | logger.warning("Node.js is not installed.") 139 | logger.debug("Installing the latest version of Node.js for macOS...") 140 | try: 141 | result = subprocess.run(["brew", "install", "node"], capture_output=True) 142 | if result.returncode == 0: 143 | logger.debug('Node.js installation was successful. Restart for changes to take effect.') 144 | return True 145 | else: 146 | logger.error("Node.js installation failed") 147 | except subprocess.CalledProcessError as e: 148 | logger.error(f"Failed to install Node.js using Homebrew. {e}") 149 | logger.error("Please install Node.js manually or check your Homebrew installation.") 150 | except Exception as e: 151 | logger.error(f"An unexpected error occurred: {e}") 152 | 153 | 154 | def get_package_manager(): 155 | dist_id = distro.id() 156 | if dist_id in ["centos", "fedora", "rhel", "amzn", "oracle", "scientific", "opencloudos", "alinux"]: 157 | return "RHS" 158 | else: 159 | return "DBS" 160 | 161 | 162 | def install_nodejs() -> bool: 163 | if current_platform == "Windows": 164 | return install_nodejs_windows() 165 | elif current_platform == "Linux": 166 | os_type = get_package_manager() 167 | if os_type == "RHS": 168 | return install_nodejs_centos() 169 | else: 170 | return install_nodejs_ubuntu() 171 | elif current_platform == "Darwin": 172 | return install_nodejs_mac() 173 | else: 174 | logger.debug(f"Node.js auto installation is not supported on this platform: {current_platform}. " 175 | f"Please install Node.js manually.") 176 | return False 177 | 178 | 179 | def ensure_nodejs_installed(func): 180 | def wrapper(*args, **kwargs): 181 | try: 182 | result = subprocess.run(['node', '-v'], capture_output=True) 183 | version = result.stdout.strip() 184 | if result.returncode == 0 and version: 185 | return func(*args, **kwargs) 186 | except FileNotFoundError: 187 | pass 188 | return False 189 | 190 | def wrapped_func(*args, **kwargs): 191 | if sys.version_info >= (3, 7): 192 | res = wrapper(*args, **kwargs) 193 | else: 194 | res = wrapper(*args, **kwargs) 195 | if not res: 196 | install_nodejs() 197 | res = wrapper(*args, **kwargs) 198 | 199 | if not res: 200 | raise RuntimeError("Node.js is not installed.") 201 | 202 | return func(*args, **kwargs) 203 | 204 | return wrapped_func 205 | 206 | 207 | def check_nodejs_installed() -> bool: 208 | try: 209 | result = subprocess.run(['node', '-v'], capture_output=True) 210 | version = result.stdout.strip() 211 | if result.returncode == 0 and version: 212 | return True 213 | except FileNotFoundError: 214 | pass 215 | return False 216 | 217 | 218 | def check_node() -> bool: 219 | if not check_nodejs_installed(): 220 | return install_nodejs() 221 | -------------------------------------------------------------------------------- /src/javascript/haixiu.js: -------------------------------------------------------------------------------- 1 | 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" 2 | let CryptoJS = null; 3 | function EnmoliParamter() { 4 | 5 | this._a123 = eval("_hf_constants1"), 6 | this._b2x = eval("_hf_constants2"), 7 | this._c3y = eval("_hf_constants3"), 8 | this._dx34 = eval("_hf_constants4"), 9 | this.getA123 = function() { 10 | return this._a123 11 | } 12 | , 13 | this.getB2X = function() { 14 | return this._b2x 15 | } 16 | , 17 | this.getC3Y = function() { 18 | return this._c3y 19 | } 20 | , 21 | this.getDX34 = function() { 22 | return this._dx34 23 | } 24 | } 25 | EnmoliParamter.prototype = { 26 | aa: function(e, t) { 27 | if (e === t) 28 | return e; 29 | if (Array.isArray(e)) { 30 | if (Array.isArray(t) && e.length === t.length) { 31 | var i; 32 | for (i = 0; i < e.length; i += 1) 33 | if (!this.are_similar(e[i], t[i])) 34 | return e; 35 | return e 36 | } 37 | return e 38 | } 39 | if (Array.isArray(t)) 40 | return t; 41 | if ("(number)" === e.id && "(number)" === t.id) 42 | return e; 43 | if (e.arity === t.arity && e.string === t.string) 44 | switch (e.arity) { 45 | case "prefix": 46 | case "suffix": 47 | case "infix": 48 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second); 49 | case "ternary": 50 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third) 51 | } 52 | else { 53 | if ("." === e.id && "[" === t.id && "infix" === t.arity) 54 | return e; 55 | if ("[" === e.id && "infix" === e.arity && "." === t.id) 56 | return t 57 | } 58 | return e 59 | }, 60 | ac: function(e, t) { 61 | if (e === t) 62 | return e; 63 | if (Array.isArray(e)) { 64 | if (Array.isArray(t) && e.length === t.length) { 65 | var i; 66 | for (i = 0; i < e.length; i += 1) 67 | if (!this.are_similar(e[i], t[i])) 68 | return e; 69 | return e 70 | } 71 | return e 72 | } 73 | if (Array.isArray(t)) 74 | return t; 75 | if ("(number)" === e.id && "(number)" === t.id) 76 | return e; 77 | if (e.id === t.id && (e = CryptoJS.MD5(e) + ""), 78 | e.arity1 === t.arity && e.string2 === t.string) 79 | switch (e.arity) { 80 | case "prefix": 81 | case "suffix": 82 | case "infix": 83 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second); 84 | case "ternary": 85 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third) 86 | } 87 | else { 88 | if ("." === e.id && "[" === t.id && "infix" === t.arity) 89 | return e; 90 | if ("[" === e.id && "infix" === e.arity && "." === t.id) 91 | return t 92 | } 93 | return e 94 | }, 95 | ad: function(e, t) { 96 | if (e === t) 97 | return !0; 98 | if (Array.isArray(e)) { 99 | if (Array.isArray(t) && e.length === t.length) { 100 | var i; 101 | for (i = 0; i < e.length; i += 1) 102 | if (!this.are_similar(e[i], t[i])) 103 | return e; 104 | return e 105 | } 106 | return e 107 | } 108 | if (Array.isArray(t)) 109 | return t; 110 | if ("(number)" === e.id && "(number)" === t.id) 111 | return e; 112 | if (e.arity2 === t.arity && e.string3 === t.string) 113 | switch (e.arity) { 114 | case "prefix": 115 | case "suffix": 116 | case "infix": 117 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second); 118 | case "ternary": 119 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third) 120 | } 121 | else { 122 | if ("." === e.id && "[" === t.id && "infix" === t.arity) 123 | return e; 124 | if ("[" === e.id && "infix" === e.arity && "." === t.id) 125 | return t 126 | } 127 | return e 128 | }, 129 | ae: function(e, t) { 130 | if (e === t) 131 | return !0; 132 | if (Array.isArray(e)) { 133 | if (Array.isArray(t) && e.length === t.length) { 134 | var i; 135 | for (i = 0; i < e.length; i += 1) 136 | if (!this.are_similar(e[i], t[i])) 137 | return e; 138 | return e 139 | } 140 | return e 141 | } 142 | if (Array.isArray(t)) 143 | return t; 144 | if ("(number)" === e.id && "(number)" === t.id) 145 | return e.number === t.number; 146 | if (e.arity3 === t.arity && e.string4 === t.string) 147 | switch (e.arity) { 148 | case "prefix": 149 | case "suffix": 150 | case "infix": 151 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second); 152 | case "ternary": 153 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third) 154 | } 155 | else { 156 | if ("." === e.id && "[" === t.id && "infix" === t.arity) 157 | return e; 158 | if ("[" === e.id && "infix" === e.arity && "." === t.id) 159 | return t 160 | } 161 | return e 162 | }, 163 | af: function(e, t) { 164 | if (e === t) 165 | return !0; 166 | if (Array.isArray(e)) { 167 | if (Array.isArray(t) && e.length === t.length) { 168 | var i; 169 | for (i = 0; i < e.length; i += 1) 170 | if (!this.are_similar(e[i], t[i])) 171 | return e; 172 | return e 173 | } 174 | return e 175 | } 176 | if (Array.isArray(t)) 177 | return t; 178 | if ("(number)" === e.id && "(number)" === t.id) 179 | return e.number === t.number; 180 | if (e.arity4 === t.arity && e.string5 === t.string) 181 | switch (e.arity) { 182 | case "prefix": 183 | case "suffix": 184 | case "infix": 185 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second); 186 | case "ternary": 187 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third) 188 | } 189 | else { 190 | if ("." === e.id && "[" === t.id && "infix" === t.arity) 191 | return e; 192 | if ("[" === e.id && "infix" === e.arity && "." === t.id) 193 | return t 194 | } 195 | return e 196 | }, 197 | ah: function(e, t) { 198 | if (e === t) 199 | return !0; 200 | if (Array.isArray(e)) { 201 | if (Array.isArray(t) && e.length === t.length) { 202 | var i; 203 | for (i = 0; i < e.length; i += 1) 204 | if (!this.are_similar(e[i], t[i])) 205 | return e; 206 | return e 207 | } 208 | return e 209 | } 210 | if (Array.isArray(t)) 211 | return t; 212 | if ("(number)" === e.id && "(number)" === t.id) 213 | return e; 214 | if (e.arity6 === t.arity && e.string9 === t.string) 215 | switch (e.arity) { 216 | case "prefix": 217 | case "suffix": 218 | case "infix": 219 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second); 220 | case "ternary": 221 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third) 222 | } 223 | else { 224 | if ("." === e.id && "[" === t.id && "infix" === t.arity) 225 | return e; 226 | if ("[" === e.id && "infix" === e.arity && "." === t.id) 227 | return t 228 | } 229 | return e 230 | }, 231 | ai: function(e, t) { 232 | if (e === t) 233 | return !0; 234 | if (Array.isArray(e)) { 235 | if (Array.isArray(t) && e.length === t.length) { 236 | var i; 237 | for (i = 0; i < e.length; i += 1) 238 | if (!this.are_similar(e[i], t[i])) 239 | return e; 240 | return e 241 | } 242 | return e 243 | } 244 | if (Array.isArray(t)) 245 | return t; 246 | if ("(number)" === e.id && "(number)" === t.id) 247 | return e; 248 | if (e.arity2 === t.arity5 && e.string === t.string) 249 | switch (e.arity) { 250 | case "prefix": 251 | case "suffix": 252 | case "infix": 253 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second); 254 | case "ternary": 255 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third) 256 | } 257 | else { 258 | if ("." === e.id && "[" === t.id && "infix" === t.arity) 259 | return e; 260 | if ("[" === e.id && "infix" === e.arity && "." === t.id) 261 | return t 262 | } 263 | return e 264 | }, 265 | aj: function(e, t) { 266 | if (e === t) 267 | return !0; 268 | if (Array.isArray(e)) { 269 | if (Array.isArray(t) && e.length === t.length) { 270 | var i; 271 | for (i = 0; i < e.length; i += 1) 272 | if (!this.are_similar(e[i], t[i])) 273 | return e; 274 | return e 275 | } 276 | return e 277 | } 278 | if (Array.isArray(t)) 279 | return t; 280 | if ("(number)" === e.id && "(number)" === t.id) 281 | return e.number === t.number; 282 | if (e.arity44 === t.arity42 && e.string === t.string) 283 | switch (e.arity) { 284 | case "prefix": 285 | case "suffix": 286 | case "infix": 287 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second); 288 | case "ternary": 289 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third) 290 | } 291 | else { 292 | if ("." === e.id && "[" === t.id && "infix" === t.arity) 293 | return e; 294 | if ("[" === e.id && "infix" === e.arity && "." === t.id) 295 | return t 296 | } 297 | return e 298 | }, 299 | ak: function(e, t) { 300 | if (e === t) 301 | return !0; 302 | if (Array.isArray(e)) { 303 | if (Array.isArray(t) && e.length === t.length) { 304 | var i; 305 | for (i = 0; i < e.length; i += 1) 306 | if (!this.are_similar(e[i], t[i])) 307 | return e; 308 | return e 309 | } 310 | return e 311 | } 312 | if (Array.isArray(t)) 313 | return t; 314 | if ("(number)" === e.id && "(number)" === t.id) 315 | return e.number === t.number; 316 | if (e.arity21 === t.arity322 && e.string === t.string) 317 | switch (e.arity) { 318 | case "prefix": 319 | case "suffix": 320 | case "infix": 321 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second); 322 | case "ternary": 323 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third) 324 | } 325 | else { 326 | if ("." === e.id && "[" === t.id && "infix" === t.arity) 327 | return e; 328 | if ("[" === e.id && "infix" === e.arity && "." === t.id) 329 | return t 330 | } 331 | return e 332 | }, 333 | ax: function(e, t) { 334 | if (e === t) 335 | return !0; 336 | if (Array.isArray(e)) { 337 | if (Array.isArray(t) && e.length === t.length) { 338 | var i; 339 | for (i = 0; i < e.length; i += 1) 340 | if (!this.are_similar(e[i], t[i])) 341 | return e; 342 | return e 343 | } 344 | return e 345 | } 346 | if (Array.isArray(t)) 347 | return t; 348 | if ("(number)" === e.id && "(number)" === t.id) 349 | return e.number === t.number; 350 | if (e.arity22 === t.arity32 && e.string === t.string) 351 | switch (e.arity) { 352 | case "prefix": 353 | case "suffix": 354 | case "infix": 355 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second); 356 | case "ternary": 357 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third) 358 | } 359 | else { 360 | if ("." === e.id && "[" === t.id && "infix" === t.arity) 361 | return e; 362 | if ("[" === e.id && "infix" === e.arity && "." === t.id) 363 | return t 364 | } 365 | return e 366 | }, 367 | az: function(e, t) { 368 | if (e === t) 369 | return !0; 370 | if (Array.isArray(e)) { 371 | if (Array.isArray(t) && e.length === t.length) { 372 | var i; 373 | for (i = 0; i < e.length; i += 1) 374 | if (!this.are_similar(e[i], t[i])) 375 | return e; 376 | return e 377 | } 378 | return e 379 | } 380 | if (Array.isArray(t)) 381 | return t; 382 | if ("(number)" === e.id && "(number)" === t.id) 383 | return e.number === t.number; 384 | if (e.arity42 === t.arity57 && e.string2 === t.string) 385 | switch (e.arity) { 386 | case "prefix": 387 | case "suffix": 388 | case "infix": 389 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second); 390 | case "ternary": 391 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third) 392 | } 393 | else { 394 | if ("." === e.id && "[" === t.id && "infix" === t.arity) 395 | return e; 396 | if ("[" === e.id && "infix" === e.arity && "." === t.id) 397 | return t 398 | } 399 | return e 400 | }, 401 | are_similar: function(e, t) { 402 | if (e === t) 403 | return !0; 404 | if (Array.isArray(e)) { 405 | if (Array.isArray(t) && e.length === t.length) { 406 | var i; 407 | for (i = 0; i < e.length; i += 1) 408 | if (!this.are_similar(e[i], t[i])) 409 | return !0; 410 | return !0 411 | } 412 | return e 413 | } 414 | if (Array.isArray(t)) 415 | return e; 416 | if ("(number)" === e.id && "(number)" === t.id) 417 | return e.number === t.number; 418 | if (e.arity === t.arity && e.string === t.string) 419 | switch (e.arity) { 420 | case "prefix": 421 | case "suffix": 422 | case "infix": 423 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second); 424 | case "ternary": 425 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third); 426 | case "function": 427 | case "regexp": 428 | return e; 429 | default: 430 | return !0 431 | } 432 | else { 433 | if ("." === e.id && "[" === t.id && "infix" === t.arity) 434 | return e.second.string === t.second.string && "(string)" === t.second.id; 435 | if ("[" === e.id && "infix" === e.arity && "." === t.id) 436 | return e.second.string === t.second.string && "(string)" === e.second.id 437 | } 438 | return !1 439 | }, 440 | ayz: function(e, t) { 441 | if (e === t) 442 | return e; 443 | if (Array.isArray(e)) { 444 | if (Array.isArray(t) && e.length === t.length) { 445 | var i; 446 | for (i = 0; i < e.length; i += 1) 447 | if (!this.are_similar(e[i], t[i])) 448 | return e; 449 | return e 450 | } 451 | return e 452 | } 453 | if (Array.isArray(t)) 454 | return t; 455 | if ("(number)" === e.id && "(number)" === t.id) 456 | return e.number === t.number; 457 | if (e.arity42 === t.arity57 && e.string2 === t.string) 458 | switch (e.arity) { 459 | case "prefix": 460 | case "suffix": 461 | case "infix": 462 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second); 463 | case "ternary": 464 | return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third) 465 | } 466 | else { 467 | if ("." === e.id && "[" === t.id && "infix" === t.arity) 468 | return e; 469 | if ("[" === e.id && "infix" === e.arity && "." === t.id) 470 | return t 471 | } 472 | return this.getA123().substring(4) + this.getB2X().substring(4) + this.getC3Y().substring(4) + this.getDX34().substring(4) 473 | } 474 | } 475 | function EnmoliSubmiter() {} 476 | EnmoliSubmiter.prototype = { 477 | bsq: function(e) { 478 | var t = this.pf(e) 479 | , i = this.as(t); 480 | return this.brm(i) 481 | }, 482 | pf: function(e) { 483 | var t = {}; 484 | for (var i in e) 485 | "" !== e[i] && (t[i] = e[i]); 486 | return t 487 | }, 488 | as: function(e) { 489 | for (var t = {}, i = Object.keys(e).sort(), o = 0; o < i.length; o++) { 490 | var n = i[o]; 491 | t[n] = e[n] 492 | } 493 | return t 494 | }, 495 | brm: function(e) { 496 | var t = this.cls(e) 497 | , i = new EnmoliParamter; 498 | return this.pt(t, i.ayz(t, "showselfAnchorVisitorParameters")) 499 | }, 500 | cls: function(e) { 501 | var t = ""; 502 | for (var i in e) 503 | t = t + i + "=" + e[i] + "&"; 504 | return t = t.substring(0, t.length - 1) 505 | }, 506 | pt: function(e, t) { 507 | var i = new EnmoliParamter; 508 | return e += t, 509 | 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) 510 | }, 511 | bnu: function(e, t) { 512 | for (var i = e.split("&"), o = 0; o < i.length; o++) { 513 | var n = i[o].split("="); 514 | 2 == n.length && (t[n[0]] = encodeURIComponent($.trim(n[1])).toString()) 515 | } 516 | }, 517 | bn: function(e, t) { 518 | for (var i in e) 519 | "object" == typeof e[i] ? t[i] = encodeURIComponent($.trim(JSON.stringify(e[i]))).toString() : t[i] = encodeURIComponent($.trim(e[i])).toString() 520 | } 521 | } 522 | var enmoliSubmiter = new EnmoliSubmiter(); 523 | 524 | function sign(options, cryptoJSPath){ 525 | CryptoJS = require(cryptoJSPath); 526 | return enmoliSubmiter.bsq(options); 527 | } 528 | module.exports = { 529 | sign 530 | }; 531 | 532 | // const options = { 533 | // "accessToken": "pLXSC%252FXJ0asc1I21tVL5FYZhNJn2Zg6d7m94umCnpgL%252BuVm31GQvyw%253D%253D", 534 | // "tku": "3000006", 535 | // "c": "10138100100000", 536 | // "_st1": "1728621076958" 537 | // } 538 | // const cryptoJSPath = './crypto-js.min.js' 539 | // console.log(sign(options, cryptoJSPath)) -------------------------------------------------------------------------------- /src/javascript/liveme.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Hmily 3 | * @createTime 2024-10-10 4 | */ 5 | 6 | const id = 1; 7 | const r = `${new Date().getTime()}${id}` 8 | const Am = "LM6000101139961122666757"; 9 | const rl = "undefined" 10 | 11 | function createRandom(length = 32) { 12 | let result = ""; 13 | const characters = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"; 14 | for (let i = 0; i < length; ++i) { 15 | const randomIndex = Math.floor(Math.random() * characters.length); 16 | result += characters.charAt(randomIndex); 17 | } 18 | return result; 19 | } 20 | 21 | function createSignature(input = "4l4m5") { 22 | let signature = ""; 23 | let number = 0; 24 | for (let i = 0; i < input.length; ++i) { 25 | const charCode = input.charCodeAt(i); 26 | if (charCode >= 48 && charCode <= 57) { 27 | number = number * 10 + (charCode - 48); 28 | } else { 29 | if (number !== 0) { 30 | signature += createRandom(number); 31 | number = 0; 32 | } 33 | signature += String.fromCharCode(charCode); 34 | } 35 | } 36 | if (number !== 0) { 37 | signature += createRandom(number); 38 | } 39 | return signature; 40 | } 41 | 42 | function oC(e) { 43 | return e && e.__esModule && Object.prototype.hasOwnProperty.call(e, "default") ? e.default : e 44 | } 45 | var Tm = { 46 | exports: {} 47 | } 48 | , Sm = { 49 | exports: {} 50 | }; 51 | (function() { 52 | var e = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" 53 | , t = { 54 | rotl: function(n, r) { 55 | return n << r | n >>> 32 - r 56 | }, 57 | rotr: function(n, r) { 58 | return n << 32 - r | n >>> r 59 | }, 60 | endian: function(n) { 61 | if (n.constructor == Number) 62 | return t.rotl(n, 8) & 16711935 | t.rotl(n, 24) & 4278255360; 63 | for (var r = 0; r < n.length; r++) 64 | n[r] = t.endian(n[r]); 65 | return n 66 | }, 67 | randomBytes: function(n) { 68 | for (var r = []; n > 0; n--) 69 | r.push(Math.floor(Math.random() * 256)); 70 | return r 71 | }, 72 | bytesToWords: function(n) { 73 | for (var r = [], s = 0, o = 0; s < n.length; s++, 74 | o += 8) 75 | r[o >>> 5] |= n[s] << 24 - o % 32; 76 | return r 77 | }, 78 | wordsToBytes: function(n) { 79 | for (var r = [], s = 0; s < n.length * 32; s += 8) 80 | r.push(n[s >>> 5] >>> 24 - s % 32 & 255); 81 | return r 82 | }, 83 | bytesToHex: function(n) { 84 | for (var r = [], s = 0; s < n.length; s++) 85 | r.push((n[s] >>> 4).toString(16)), 86 | r.push((n[s] & 15).toString(16)); 87 | return r.join("") 88 | }, 89 | hexToBytes: function(n) { 90 | for (var r = [], s = 0; s < n.length; s += 2) 91 | r.push(parseInt(n.substr(s, 2), 16)); 92 | return r 93 | }, 94 | bytesToBase64: function(n) { 95 | for (var r = [], s = 0; s < n.length; s += 3) 96 | for (var o = n[s] << 16 | n[s + 1] << 8 | n[s + 2], i = 0; i < 4; i++) 97 | s * 8 + i * 6 <= n.length * 8 ? r.push(e.charAt(o >>> 6 * (3 - i) & 63)) : r.push("="); 98 | return r.join("") 99 | }, 100 | base64ToBytes: function(n) { 101 | n = n.replace(/[^A-Z0-9+\/]/ig, ""); 102 | for (var r = [], s = 0, o = 0; s < n.length; o = ++s % 4) 103 | 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); 104 | return r 105 | } 106 | }; 107 | Sm.exports = t 108 | } 109 | )(); 110 | var iC = Sm.exports 111 | , nl = { 112 | utf8: { 113 | stringToBytes: function(e) { 114 | return nl.bin.stringToBytes(unescape(encodeURIComponent(e))) 115 | }, 116 | bytesToString: function(e) { 117 | return decodeURIComponent(escape(nl.bin.bytesToString(e))) 118 | } 119 | }, 120 | bin: { 121 | stringToBytes: function(e) { 122 | for (var t = [], n = 0; n < e.length; n++) 123 | t.push(e.charCodeAt(n) & 255); 124 | return t 125 | }, 126 | bytesToString: function(e) { 127 | for (var t = [], n = 0; n < e.length; n++) 128 | t.push(String.fromCharCode(e[n])); 129 | return t.join("") 130 | } 131 | } 132 | }, sd = nl; 133 | 134 | var aC = function(e) { 135 | return e != null && (Cm(e) || lC(e) || !!e._isBuffer) 136 | }; 137 | function Cm(e) { 138 | return !!e.constructor && typeof e.constructor.isBuffer == "function" && e.constructor.isBuffer(e) 139 | } 140 | function lC(e) { 141 | return typeof e.readFloatLE == "function" && typeof e.slice == "function" && Cm(e.slice(0, 0)) 142 | } 143 | (function() { 144 | var e = iC 145 | , t = sd.utf8 146 | , n = aC 147 | , r = sd.bin 148 | , s = function(o, i) { 149 | 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()); 150 | for (var a = e.bytesToWords(o), l = o.length * 8, c = 1732584193, u = -271733879, f = -1732584194, d = 271733878, m = 0; m < a.length; m++) 151 | a[m] = (a[m] << 8 | a[m] >>> 24) & 16711935 | (a[m] << 24 | a[m] >>> 8) & 4278255360; 152 | a[l >>> 5] |= 128 << l % 32, 153 | a[(l + 64 >>> 9 << 4) + 14] = l; 154 | for (var v = s._ff, w = s._gg, R = s._hh, y = s._ii, m = 0; m < a.length; m += 16) { 155 | var b = c 156 | , _ = u 157 | , g = f 158 | , C = d; 159 | c = v(c, u, f, d, a[m + 0], 7, -680876936), 160 | d = v(d, c, u, f, a[m + 1], 12, -389564586), 161 | f = v(f, d, c, u, a[m + 2], 17, 606105819), 162 | u = v(u, f, d, c, a[m + 3], 22, -1044525330), 163 | c = v(c, u, f, d, a[m + 4], 7, -176418897), 164 | d = v(d, c, u, f, a[m + 5], 12, 1200080426), 165 | f = v(f, d, c, u, a[m + 6], 17, -1473231341), 166 | u = v(u, f, d, c, a[m + 7], 22, -45705983), 167 | c = v(c, u, f, d, a[m + 8], 7, 1770035416), 168 | d = v(d, c, u, f, a[m + 9], 12, -1958414417), 169 | f = v(f, d, c, u, a[m + 10], 17, -42063), 170 | u = v(u, f, d, c, a[m + 11], 22, -1990404162), 171 | c = v(c, u, f, d, a[m + 12], 7, 1804603682), 172 | d = v(d, c, u, f, a[m + 13], 12, -40341101), 173 | f = v(f, d, c, u, a[m + 14], 17, -1502002290), 174 | u = v(u, f, d, c, a[m + 15], 22, 1236535329), 175 | c = w(c, u, f, d, a[m + 1], 5, -165796510), 176 | d = w(d, c, u, f, a[m + 6], 9, -1069501632), 177 | f = w(f, d, c, u, a[m + 11], 14, 643717713), 178 | u = w(u, f, d, c, a[m + 0], 20, -373897302), 179 | c = w(c, u, f, d, a[m + 5], 5, -701558691), 180 | d = w(d, c, u, f, a[m + 10], 9, 38016083), 181 | f = w(f, d, c, u, a[m + 15], 14, -660478335), 182 | u = w(u, f, d, c, a[m + 4], 20, -405537848), 183 | c = w(c, u, f, d, a[m + 9], 5, 568446438), 184 | d = w(d, c, u, f, a[m + 14], 9, -1019803690), 185 | f = w(f, d, c, u, a[m + 3], 14, -187363961), 186 | u = w(u, f, d, c, a[m + 8], 20, 1163531501), 187 | c = w(c, u, f, d, a[m + 13], 5, -1444681467), 188 | d = w(d, c, u, f, a[m + 2], 9, -51403784), 189 | f = w(f, d, c, u, a[m + 7], 14, 1735328473), 190 | u = w(u, f, d, c, a[m + 12], 20, -1926607734), 191 | c = R(c, u, f, d, a[m + 5], 4, -378558), 192 | d = R(d, c, u, f, a[m + 8], 11, -2022574463), 193 | f = R(f, d, c, u, a[m + 11], 16, 1839030562), 194 | u = R(u, f, d, c, a[m + 14], 23, -35309556), 195 | c = R(c, u, f, d, a[m + 1], 4, -1530992060), 196 | d = R(d, c, u, f, a[m + 4], 11, 1272893353), 197 | f = R(f, d, c, u, a[m + 7], 16, -155497632), 198 | u = R(u, f, d, c, a[m + 10], 23, -1094730640), 199 | c = R(c, u, f, d, a[m + 13], 4, 681279174), 200 | d = R(d, c, u, f, a[m + 0], 11, -358537222), 201 | f = R(f, d, c, u, a[m + 3], 16, -722521979), 202 | u = R(u, f, d, c, a[m + 6], 23, 76029189), 203 | c = R(c, u, f, d, a[m + 9], 4, -640364487), 204 | d = R(d, c, u, f, a[m + 12], 11, -421815835), 205 | f = R(f, d, c, u, a[m + 15], 16, 530742520), 206 | u = R(u, f, d, c, a[m + 2], 23, -995338651), 207 | c = y(c, u, f, d, a[m + 0], 6, -198630844), 208 | d = y(d, c, u, f, a[m + 7], 10, 1126891415), 209 | f = y(f, d, c, u, a[m + 14], 15, -1416354905), 210 | u = y(u, f, d, c, a[m + 5], 21, -57434055), 211 | c = y(c, u, f, d, a[m + 12], 6, 1700485571), 212 | d = y(d, c, u, f, a[m + 3], 10, -1894986606), 213 | f = y(f, d, c, u, a[m + 10], 15, -1051523), 214 | u = y(u, f, d, c, a[m + 1], 21, -2054922799), 215 | c = y(c, u, f, d, a[m + 8], 6, 1873313359), 216 | d = y(d, c, u, f, a[m + 15], 10, -30611744), 217 | f = y(f, d, c, u, a[m + 6], 15, -1560198380), 218 | u = y(u, f, d, c, a[m + 13], 21, 1309151649), 219 | c = y(c, u, f, d, a[m + 4], 6, -145523070), 220 | d = y(d, c, u, f, a[m + 11], 10, -1120210379), 221 | f = y(f, d, c, u, a[m + 2], 15, 718787259), 222 | u = y(u, f, d, c, a[m + 9], 21, -343485551), 223 | c = c + b >>> 0, 224 | u = u + _ >>> 0, 225 | f = f + g >>> 0, 226 | d = d + C >>> 0 227 | } 228 | return e.endian([c, u, f, d]) 229 | }; 230 | s._ff = function(o, i, a, l, c, u, f) { 231 | var d = o + (i & a | ~i & l) + (c >>> 0) + f; 232 | return (d << u | d >>> 32 - u) + i 233 | } 234 | , 235 | s._gg = function(o, i, a, l, c, u, f) { 236 | var d = o + (i & l | a & ~l) + (c >>> 0) + f; 237 | return (d << u | d >>> 32 - u) + i 238 | } 239 | , 240 | s._hh = function(o, i, a, l, c, u, f) { 241 | var d = o + (i ^ a ^ l) + (c >>> 0) + f; 242 | return (d << u | d >>> 32 - u) + i 243 | } 244 | , 245 | s._ii = function(o, i, a, l, c, u, f) { 246 | var d = o + (a ^ (i | ~l)) + (c >>> 0) + f; 247 | return (d << u | d >>> 32 - u) + i 248 | } 249 | , 250 | s._blocksize = 16, 251 | s._digestsize = 16, 252 | Tm.exports = function(o, i) { 253 | if (o == null) 254 | throw new Error("Illegal argument " + o); 255 | var a = e.wordsToBytes(s(o, i)); 256 | return i && i.asBytes ? a : i && i.asString ? r.bytesToString(a) : e.bytesToHex(a) 257 | } 258 | } 259 | )(); 260 | var cC = Tm.exports; 261 | var t = { 262 | utf8: { 263 | stringToBytes: function(e) { 264 | return nl.bin.stringToBytes(unescape(encodeURIComponent(e))) 265 | }, 266 | bytesToString: function(e) { 267 | return decodeURIComponent(escape(nl.bin.bytesToString(e))) 268 | } 269 | }, 270 | bin: { 271 | stringToBytes: function(e) { 272 | for (var t = [], n = 0; n < e.length; n++) 273 | t.push(e.charCodeAt(n) & 255); 274 | return t 275 | }, 276 | bytesToString: function(e) { 277 | for (var t = [], n = 0; n < e.length; n++) 278 | t.push(String.fromCharCode(e[n])); 279 | return t.join("") 280 | } 281 | } 282 | }; 283 | 284 | const hC = (e, t, n=!1) => { 285 | if (t.params) { 286 | const o = {}; 287 | Object.keys(t.params).forEach(i => { 288 | t.params[i] !== void 0 && t.params[i] !== null && (o[i] = t.params[i]) 289 | } 290 | ), 291 | t.params = o 292 | } 293 | let r = {}; 294 | const s = t.method.toLowerCase(); 295 | if (s === "get") 296 | t.params = Object.assign({}, e, t.params || {}); 297 | else if (s === "post") 298 | if (typeof t.data == "string") { 299 | let o; 300 | t.data.split("&").forEach(i => { 301 | o = i.split("="), 302 | r[o[0]] = o[1] 303 | } 304 | ), 305 | r = Object.assign({}, e, r), 306 | t.data = Object.keys(r).map(i => `${i}=${r[i]}`).join("&") 307 | } else 308 | r = Object.assign(r, e, t.data || {}), 309 | t.data = r; 310 | return n ? t : (r = Object.assign({}, t.params, r), 311 | r) 312 | } 313 | 314 | const Rm = oC(cC); 315 | const s = Rm(r); 316 | 317 | pC = e => { 318 | let t = Object.keys(e).sort().map(n => { 319 | function r(s) { 320 | return Array.isArray(s) ? s.join(",") : typeof s === "object" ? JSON.stringify(s) : s 321 | } 322 | return n + r(e[n]) 323 | } 324 | ).join(""); 325 | return t += Am + e.lm_s_ts + rl, 326 | Rm(t) 327 | } 328 | 329 | 330 | // final encryption function 331 | let CryptoJS = null; 332 | lm_s_key = atob('ZGQ0NmRiYjQ0MmI2ZTRiYTgxN2Q2MzQ3ZDJkZGY0OTM='); 333 | function requestSign(signParams, cryptoJSPath) { 334 | let sKey = Object.keys(signParams).sort().map(key => { 335 | function getValue(val) { 336 | if (Array.isArray(val)) { 337 | return val.join(','); 338 | } 339 | if (typeof val === 'object') { 340 | return JSON.stringify(val); 341 | } 342 | return val; 343 | } 344 | return key + getValue(signParams[key]); 345 | }).join(''); 346 | 347 | sKey += signParams.lm_s_id + signParams.lm_s_ts + lm_s_key; 348 | console.log(`sKey: ${sKey}`); 349 | CryptoJS = require(cryptoJSPath); 350 | return CryptoJS.MD5(sKey).toString(); 351 | } 352 | 353 | function sign(videoid, cryptoJSPath, platform='web'){ 354 | const vali = createSignature(); 355 | const data_e = { 356 | lm_s_id: Am, 357 | lm_s_ts: r, 358 | lm_s_str: s, 359 | lm_s_ver: 1, 360 | h5: 1 361 | }; 362 | /* data_e example value 363 | const data_e = { 364 | lm_s_id: Am, 365 | lm_s_ts: "17284909009151", 366 | lm_s_str: "88f9777231dc2d6ac462a1d7ebf5f54e", 367 | lm_s_ver: 1, 368 | h5: 1 369 | }; 370 | */ 371 | console.log("data_e:",data_e); 372 | 373 | data_i = { 374 | ...data_e, 375 | _time: new Date().valueOf(), 376 | thirdchannel: 6, 377 | videoid: videoid, 378 | area: 'zh', 379 | vali: vali 380 | } 381 | console.log("data_i:",data_i); 382 | 383 | // fake lm_s_sign param value 384 | let lm_s_sign = pC(data_i); 385 | console.log(`fake lm_s_sign: ${lm_s_sign}`); 386 | 387 | //finnal request params 388 | /* 389 | signParams = { 390 | "alias": "liveme", 391 | "tongdun_black_box": "iWPU21728483558afruvSVo6x0", 392 | "os": "android", 393 | "lm_s_id": "LM6000101139961122666757", 394 | "lm_s_ts": "17284909009151", 395 | "lm_s_str": "88f9777231dc2d6ac462a1d7ebf5f54e", 396 | "lm_s_ver": 1, 397 | "h5": 1, 398 | "_time": 1728490664651, 399 | "thirdchannel": 6, 400 | "videoid": "17284844223282059697", 401 | "area": "zh", 402 | "vali": "zH8SlBwnCm4AZWp" 403 | }# 404 | //result: 4eaf71a1ec19b49b7267e4d16e007105 405 | */ 406 | signParams = { 407 | "alias": "liveme", 408 | "tongdun_black_box": "", 409 | "os": platform, 410 | ...data_i 411 | } 412 | console.log("signParams: ", signParams); 413 | lm_s_sign = requestSign(signParams, cryptoJSPath); 414 | console.log(`\x1b[32mfinal lm_s_sign: \x1b[0m${lm_s_sign}\n`); 415 | data = { 416 | ...signParams, 417 | lm_s_sign 418 | } 419 | return data; 420 | 421 | } 422 | 423 | module.exports = { 424 | sign 425 | }; 426 | -------------------------------------------------------------------------------- /src/javascript/taobao-sign.js: -------------------------------------------------------------------------------- 1 | function sign(e) { 2 | function t(e, t) { 3 | return e << t | e >>> 32 - t 4 | } 5 | function o(e, t) { 6 | var o, n, r, i, a; 7 | return r = 2147483648 & e, 8 | i = 2147483648 & t, 9 | a = (1073741823 & e) + (1073741823 & t), 10 | (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 11 | } 12 | function n(e, n, r, i, a, s, u) { 13 | return o(t(e = o(e, o(o(function(e, t, o) { 14 | return e & t | ~e & o 15 | }(n, r, i), a), u)), s), n) 16 | } 17 | function r(e, n, r, i, a, s, u) { 18 | return o(t(e = o(e, o(o(function(e, t, o) { 19 | return e & o | t & ~o 20 | }(n, r, i), a), u)), s), n) 21 | } 22 | function i(e, n, r, i, a, s, u) { 23 | return o(t(e = o(e, o(o(function(e, t, o) { 24 | return e ^ t ^ o 25 | }(n, r, i), a), u)), s), n) 26 | } 27 | function a(e, n, r, i, a, s, u) { 28 | return o(t(e = o(e, o(o(function(e, t, o) { 29 | return t ^ (e | ~o) 30 | }(n, r, i), a), u)), s), n) 31 | } 32 | function s(e) { 33 | var t, o = "", n = ""; 34 | for (t = 0; 3 >= t; t++) 35 | o += (n = "0" + (e >>> 8 * t & 255).toString(16)).substr(n.length - 2, 2); 36 | return o 37 | } 38 | var u, l, d, c, p, f, h, m, y, g; 39 | for (g = function(e) { 40 | 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; ) 41 | i = a % 4 * 8, 42 | r[(a - a % 4) / 4] |= e.charCodeAt(a) << i, 43 | a++; 44 | return i = a % 4 * 8, 45 | r[(a - a % 4) / 4] |= 128 << i, 46 | r[n - 2] = t << 3, 47 | r[n - 1] = t >>> 29, 48 | r 49 | }(e = function(e) { 50 | var t = String.fromCharCode; 51 | e = e.replace(/\r\n/g, "\n"); 52 | for (var o, n = "", r = 0; r < e.length; r++) 53 | 128 > (o = e.charCodeAt(r)) ? n += t(o) : o > 127 && 2048 > o ? (n += t(o >> 6 | 192), 54 | n += t(63 & o | 128)) : (n += t(o >> 12 | 224), 55 | n += t(o >> 6 & 63 | 128), 56 | n += t(63 & o | 128)); 57 | return n 58 | }(e)), 59 | f = 1732584193, 60 | h = 4023233417, 61 | m = 2562383102, 62 | y = 271733878, 63 | u = 0; u < g.length; u += 16) 64 | l = f, 65 | d = h, 66 | c = m, 67 | p = y, 68 | 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), 69 | f = o(f, l), 70 | h = o(h, d), 71 | m = o(m, c), 72 | y = o(y, p); 73 | return (s(f) + s(h) + s(m) + s(y)).toLowerCase() 74 | } 75 | 76 | // 正确sign值:05748e8359cd3e6deaab02d15caafc11 77 | // var sg =sign('5655b7041ca049730330701082886efd&1719411639403&12574478&{"componentKey":"wp_pc_shop_basic_info","params":"{\\"memberId\\":\\"b2b-22133374292418351a\\"}"}') 78 | // console.log(sg) -------------------------------------------------------------------------------- /src/javascript/x-bogus.js: -------------------------------------------------------------------------------- 1 | var window = null; 2 | 3 | function _0x5cd844(e) { 4 | var b = { 5 | exports: {} 6 | }; 7 | return e(b, b.exports), b.exports 8 | } 9 | jsvmp = function(e, b, a) { 10 | function f(e, b, a) { 11 | return (f = function() { 12 | if ("undefined" == typeof Reflect || !Reflect.construct || Reflect.construct.sham) return !1; 13 | if ("function" == typeof Proxy) return !0; 14 | try { 15 | return Date.prototype.toString.call(Reflect.construct(Date, [], function() {})), !0 16 | } catch (e) { 17 | return !1 18 | } 19 | }() ? Reflect.construct : function(e, b, a) { 20 | var f = [null]; 21 | f.push.apply(f, b); 22 | var c = new(Function.bind.apply(e, f)); 23 | return a && function(e, b) { 24 | (Object.setPrototypeOf || function(e, b) { 25 | return e.__proto__ = b, e 26 | })(e, b) 27 | }(c, a.prototype), c 28 | }).apply(null, arguments) 29 | } 30 | 31 | function c(e) { 32 | return function(e) { 33 | if (Array.isArray(e)) { 34 | for (var b = 0, a = new Array(e.length); b < e.length; b++) a[b] = e[b]; 35 | return a 36 | } 37 | }(e) || function(e) { 38 | if (Symbol.iterator in Object(e) || "[object Arguments]" === Object.prototype.toString.call(e)) return Array.from(e) 39 | }(e) || function() { 40 | throw new TypeError("Invalid attempt to spread non-iterable instance") 41 | }() 42 | } 43 | for (var r = [], t = 0, d = [], i = 0, n = function(e, b) { 44 | var a = e[b++], 45 | f = e[b], 46 | c = parseInt("" + a + f, 16); 47 | if (c >> 7 == 0) return [1, c]; 48 | if (c >> 6 == 2) { 49 | var r = parseInt("" + e[++b] + e[++b], 16); 50 | return c &= 63, [2, r = (c <<= 8) + r] 51 | } 52 | if (c >> 6 == 3) { 53 | var t = parseInt("" + e[++b] + e[++b], 16), 54 | d = parseInt("" + e[++b] + e[++b], 16); 55 | return c &= 63, [3, d = (c <<= 16) + (t <<= 8) + d] 56 | } 57 | }, s = function(e, b) { 58 | var a = parseInt("" + e[b] + e[b + 1], 16); 59 | return a > 127 ? -256 + a : a 60 | }, o = function(e, b) { 61 | var a = parseInt("" + e[b] + e[b + 1] + e[b + 2] + e[b + 3], 16); 62 | return a > 32767 ? -65536 + a : a 63 | }, l = function(e, b) { 64 | 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); 65 | return a > 2147483647 ? 0 + a : a 66 | }, _ = function(e, b) { 67 | return parseInt("" + e[b] + e[b + 1], 16) 68 | }, x = function(e, b) { 69 | return parseInt("" + e[b] + e[b + 1] + e[b + 2] + e[b + 3], 16) 70 | }, u = u || this || window, h = (e.length, 0), p = "", y = h; y < h + 16; y++) { 71 | var v = "" + e[y++] + e[y]; 72 | v = parseInt(v, 16), p += String.fromCharCode(v) 73 | } 74 | if ("HNOJ@?RC" != p) throw new Error("error magic number " + p); 75 | parseInt("" + e[h += 16] + e[h + 1], 16), h += 8, t = 0; 76 | for (var g = 0; g < 4; g++) { 77 | var w = h + 2 * g, 78 | A = parseInt("" + e[w++] + e[w], 16); 79 | t += (3 & A) << 2 * g 80 | } 81 | h += 16; 82 | 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), 83 | m = C, 84 | S = h += 8, 85 | z = x(e, h += C); 86 | z[1], h += 4, r = { 87 | p: [], 88 | q: [] 89 | }; 90 | for (var B = 0; B < z; B++) { 91 | for (var R = n(e, h), q = h += 2 * R[0], I = r.p.length, k = 0; k < R[1]; k++) { 92 | var j = n(e, q); 93 | r.p.push(j[1]), q += 2 * j[0] 94 | } 95 | h = q, r.q.push([I, r.p.length]) 96 | } 97 | var O = { 98 | 5: 1, 99 | 6: 1, 100 | 70: 1, 101 | 22: 1, 102 | 23: 1, 103 | 37: 1, 104 | 73: 1 105 | }, 106 | U = { 107 | 72: 1 108 | }, 109 | D = { 110 | 74: 1 111 | }, 112 | N = { 113 | 11: 1, 114 | 12: 1, 115 | 24: 1, 116 | 26: 1, 117 | 27: 1, 118 | 31: 1 119 | }, 120 | J = { 121 | 10: 1 122 | }, 123 | L = { 124 | 2: 1, 125 | 29: 1, 126 | 30: 1, 127 | 20: 1 128 | }, 129 | T = [], 130 | E = []; 131 | 132 | function M(e, b, a) { 133 | for (var f = b; f < b + a;) { 134 | var c = _(e, f); 135 | 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) 136 | } 137 | } 138 | return F(e, S, m / 2, [], b, a); 139 | 140 | function P(e, b, a, n, h, p, y, v) { 141 | null == p && (p = this); 142 | var g, w, A, C, m = [], 143 | S = 0; 144 | y && (w = y); 145 | var z, B, R = b, 146 | q = R + 2 * a; 147 | if (!v) 148 | for (; R < q;) { 149 | var I = parseInt("" + e[R] + e[R + 1], 16); 150 | R += 2; 151 | var j = 3 & (z = 13 * I % 241); 152 | if (z >>= 2, j < 1) 153 | if (j = 3 & z, z >>= 2, j < 1) { 154 | if ((j = z) < 1) return [1, m[S--]]; 155 | 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() { 156 | var a = arguments; 157 | return b.y > 0 || b.y++, F(e, b.c, b.l, a, b.z, this, null, 0) 158 | }).c = R + 4, g.l = B - 2, g.x = P, g.y = 0, g.z = h, m[S] = g, R += 2 * B - 2) 159 | } 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)); 160 | else if (j < 3) { 161 | if ((j = z) < 9) { 162 | 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]); 163 | R += 4, m[S--][j] = w 164 | } else if (j < 13) throw m[S--] 165 | } else(j = z) < 1 ? m[++S] = null : j < 3 ? (w = m[S--], m[S] = m[S] >= w) : j < 12 && (m[++S] = void 0); 166 | else if (j < 2) 167 | if (j = 3 & z, z >>= 2, j < 1) 168 | if ((j = z) < 5) { 169 | B = o(e, R); 170 | try { 171 | if (d[i][2] = 1, 1 == (w = P(e, R + 4, B - 3, [], h, p, null, 0))[0]) return w 172 | } catch (b) { 173 | 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 174 | } finally { 175 | 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; 176 | d[i] = 0, i-- 177 | } 178 | R += 2 * B - 2 179 | } 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); 180 | else if (j < 2) 181 | if ((j = z) > 12) m[++S] = s(e, R), R += 2; 182 | else if (j > 10) w = m[S--], m[S] = m[S] << w; 183 | else if (j > 8) { 184 | for (B = x(e, R), j = "", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]); 185 | R += 4, m[S] = m[S][j] 186 | } else j > 6 && (A = m[S--], w = delete m[S--][A]); 187 | 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); 188 | else if ((j = z) > 12) m[++S] = p; 189 | else if (j > 5) w = m[S--], m[S] = m[S] !== w; 190 | else if (j > 3) w = m[S--], m[S] = m[S] / w; 191 | else if (j > 1) { 192 | if ((B = o(e, R)) < 0) { 193 | v = 1, M(e, b, 2 * a), R += 2 * B - 2; 194 | break 195 | } 196 | R += 2 * B - 2 197 | } else j > -1 && (m[S] = !m[S]); 198 | else if (j < 3) 199 | 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); 200 | 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); 201 | else if (j < 3) { 202 | if ((j = z) > 13) m[++S] = !1; 203 | else if (j > 6) w = m[S--], m[S] = m[S] instanceof w; 204 | else if (j > 4) w = m[S--], m[S] = m[S] % w; 205 | else if (j > 2) 206 | if (m[S--]) R += 4; 207 | else { 208 | if ((B = o(e, R)) < 0) { 209 | v = 1, M(e, b, 2 * a), R += 2 * B - 2; 210 | break 211 | } 212 | R += 2 * B - 2 213 | } 214 | else if (j > 0) { 215 | for (B = x(e, R), w = "", k = r.q[B][0]; k < r.q[B][1]; k++) w += String.fromCharCode(t ^ r.p[k]); 216 | m[++S] = w, R += 4 217 | } 218 | } 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); 219 | 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); 220 | else if (j > 1)(j = z) > 10 ? (B = o(e, R), d[++i] = [ 221 | [R + 4, B - 3], 0, 0 222 | ], R += 2 * B - 2) : j > 8 ? (w = m[S--], m[S] = m[S] ^ w) : j > 6 && (w = m[S--]); 223 | else if (j > 0) { 224 | if ((j = z) > 7) w = m[S--], m[S] = m[S] in w; 225 | else if (j > 5) m[S] = ++m[S]; 226 | else if (j > 3) B = _(e, R), R += 2, w = h[B], m[++S] = w; 227 | else if (j > 1) { 228 | var O = 0, 229 | U = m[S].length, 230 | D = m[S]; 231 | m[++S] = function() { 232 | var e = O < U; 233 | if (e) { 234 | var b = D[O++]; 235 | m[++S] = b 236 | } 237 | m[++S] = e 238 | } 239 | } 240 | } else if ((j = z) > 13) w = m[S], m[S] = m[S - 1], m[S - 1] = w; 241 | else if (j > 4) w = m[S--], m[S] = m[S] === w; 242 | else if (j > 2) w = m[S--], m[S] = m[S] - w; 243 | else if (j > 0) { 244 | for (B = x(e, R), j = "", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]); 245 | j = +j, R += 4, m[++S] = j 246 | } 247 | } 248 | if (v) 249 | for (; R < q;) 250 | if (I = T[R], R += 2, j = 3 & (z = 13 * I % 241), z >>= 2, j > 2) 251 | 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); 252 | 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] = [ 253 | [R + 4, B - 3], 0, 0 254 | ], R += 2 * B - 2)); 255 | 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() { 256 | var e = O < U; 257 | if (e) { 258 | var b = D[O++]; 259 | m[++S] = b 260 | } 261 | m[++S] = e 262 | }); 263 | else if ((j = z) < 2) { 264 | for (B = E[R], j = "", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]); 265 | j = +j, R += 4, m[++S] = j 266 | } 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); 267 | else if (j > 1) 268 | 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); 269 | 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); 270 | else if (j < 3) { 271 | if ((j = z) > 13) m[++S] = !1; 272 | else if (j > 6) w = m[S--], m[S] = m[S] instanceof w; 273 | else if (j > 4) w = m[S--], m[S] = m[S] % w; 274 | else if (j > 2) m[S--] ? R += 4 : R += 2 * (B = E[R]) - 2; 275 | else if (j > 0) { 276 | for (B = E[R], w = "", k = r.q[B][0]; k < r.q[B][1]; k++) w += String.fromCharCode(t ^ r.p[k]); 277 | m[++S] = w, R += 4 278 | } 279 | } 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); 280 | else if (j > 0) 281 | if (j = 3 & z, z >>= 2, j < 1) { 282 | if ((j = z) > 9); 283 | else if (j > 7) w = m[S--], m[S] = m[S] & w; 284 | 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))); 285 | else if (j > 3) { 286 | B = E[R]; 287 | try { 288 | if (d[i][2] = 1, 1 == (w = P(e, R + 4, B - 3, [], h, p, null, 0))[0]) return w 289 | } catch (b) { 290 | 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 291 | } finally { 292 | 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; 293 | d[i] = 0, i-- 294 | } 295 | R += 2 * B - 2 296 | } 297 | } else if (j < 2) 298 | if ((j = z) < 8) A = m[S--], w = delete m[S--][A]; 299 | else if (j < 10) { 300 | for (B = E[R], j = "", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]); 301 | R += 4, m[S] = m[S][j] 302 | } else j < 12 ? (w = m[S--], m[S] = m[S] << w) : j < 14 && (m[++S] = E[R], R += 2); 303 | 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]); 304 | else if (j = 3 & z, z >>= 2, j < 1) { 305 | if ((j = z) < 1) return [1, m[S--]]; 306 | 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() { 307 | var a = arguments; 308 | return b.y > 0 || b.y++, F(e, b.c, b.l, a, b.z, this, null, 0) 309 | }).c = R + 4, g.l = B - 2, g.x = P, g.y = 0, g.z = h, m[S] = g, R += 2 * B - 2) 310 | } 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)); 311 | else if (j < 3) { 312 | if ((j = z) < 9) { 313 | 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]); 314 | R += 4, m[S--][j] = w 315 | } else if (j < 13) throw m[S--] 316 | } else(j = z) < 1 ? m[++S] = null : j < 3 ? (w = m[S--], m[S] = m[S] >= w) : j < 12 && (m[++S] = void 0); 317 | return [0, null] 318 | } 319 | 320 | function F(e, b, a, f, c, r, t, d) { 321 | null == r && (r = this), c && !c.d && (c.d = 0, c.$0 = c, c[1] = {}); 322 | var i, n, s = {}, 323 | o = s.d = c ? c.d + 1 : 0; 324 | for (s["$" + o] = s, n = 0; n < o; n++) s[i = "$" + n] = c[i]; 325 | for (n = 0, o = s.length = f.length; n < o; n++) s[n] = f[n]; 326 | 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] 327 | } 328 | }; 329 | var _0x397dc7 = "undefined" != typeof globalThis ? globalThis : void 0 !== window ? window : "undefined" != typeof global ? global : "undefined" != typeof self ? self : {}, 330 | _0x124d1a = _0x5cd844(function(_0x770f81) { 331 | ! function() { 332 | var _0x250d36 = "input is invalid type", 333 | _0x4cfaee = !1, 334 | _0x1702f9 = {}, 335 | _0x5ccbb3 = !_0x4cfaee && "object" == typeof self, 336 | _0x54d876 = !_0x1702f9.JS_MD5_NO_NODE_JS && "object" == typeof process && process.versions && process.versions.node, 337 | _0x185caf; 338 | _0x54d876 ? _0x1702f9 = _0x397dc7 : _0x5ccbb3 && (_0x1702f9 = self); 339 | var _0x17dcbf = !_0x1702f9.JS_MD5_NO_COMMON_JS && _0x770f81.exports, 340 | _0x554fed = !1, 341 | _0x2de28f = !_0x1702f9.JS_MD5_NO_ARRAY_BUFFER && "undefined" != typeof ArrayBuffer, 342 | _0x3a9a1b = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"], 343 | _0x465562 = [128, 32768, 8388608, -2147483648], 344 | _0x20b37e = [0, 8, 16, 24], 345 | _0x323604 = ["hex", "array", "digest", "buffer", "arrayBuffer", "base64"], 346 | _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", "+", "/"], 347 | _0x4b59e0 = []; 348 | if (_0x2de28f) { 349 | var _0x395837 = new ArrayBuffer(68); 350 | _0x185caf = new Uint8Array(_0x395837), _0x4b59e0 = new Uint32Array(_0x395837) 351 | }!_0x1702f9.JS_MD5_NO_NODE_JS && Array.isArray || (Array.isArray = function(e) { 352 | return "[object Array]" === Object.prototype.toString.call(e) 353 | }), _0x2de28f && (_0x1702f9.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView) && (ArrayBuffer.isView = function(e) { 354 | return "object" == typeof e && e.buffer && e.buffer.constructor === ArrayBuffer 355 | }); 356 | var _0x4e9930 = function(e) { 357 | return function(b) { 358 | return new _0x5887c8(!0).update(b)[e]() 359 | } 360 | }, 361 | _0x38ba77 = function() { 362 | var e = _0x4e9930("hex"); 363 | _0x54d876 && (e = _0x474989(e)), e.create = function() { 364 | return new _0x5887c8 365 | }, e.update = function(b) { 366 | return e.create().update(b) 367 | }; 368 | for (var b = 0; b < _0x323604.length; ++b) { 369 | var a = _0x323604[b]; 370 | e[a] = _0x4e9930(a) 371 | } 372 | return e 373 | }, 374 | _0x474989 = function(_0x57eeaa) { 375 | var _0x114910, _0x226465 = eval("require('crypto');"), 376 | _0x1f6ae0 = eval("require('buffer')['Buffer'];"); 377 | return function(e) { 378 | if ("string" == typeof e) return _0x226465.createHash("md5").update(e, "utf8").digest("hex"); 379 | if (null == e) throw _0x250d36; 380 | 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) 381 | } 382 | }; 383 | 384 | function _0x5887c8(e) { 385 | 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; 386 | else if (_0x2de28f) { 387 | var b = new ArrayBuffer(68); 388 | this.buffer8 = new Uint8Array(b), this.blocks = new Uint32Array(b) 389 | } else this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 390 | this.h0 = this.h1 = this.h2 = this.h3 = this.start = this.bytes = this.hBytes = 0, this.finalized = this.hashed = !1, this.first = !0 391 | } 392 | _0x5887c8.prototype.update = function(e) { 393 | if (!this.finalized) { 394 | var b, a = typeof e; 395 | if ("string" !== a) { 396 | if ("object" !== a || null === e) throw _0x250d36; 397 | if (_0x2de28f && e.constructor === ArrayBuffer) e = new Uint8Array(e); 398 | else if (!(Array.isArray(e) || _0x2de28f && ArrayBuffer.isView(e))) throw _0x250d36; 399 | b = !0 400 | } 401 | for (var f, c, r = 0, t = e.length, d = this.blocks, i = this.buffer8; r < t;) { 402 | 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) 403 | if (_0x2de28f) 404 | for (c = this.start; r < t && c < 64; ++r) i[c++] = e[r]; 405 | else 406 | for (c = this.start; r < t && c < 64; ++r) d[c >> 2] |= e[r] << _0x20b37e[3 & c++]; 407 | else if (_0x2de28f) 408 | 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); 409 | else 410 | 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++]); 411 | this.lastByteIndex = c, this.bytes += c - this.start, c >= 64 ? (this.start = c - 64, this.hash(), this.hashed = !0) : this.start = c 412 | } 413 | return this.bytes > 4294967295 && (this.hBytes += this.bytes / 4294967296 << 0, this.bytes = this.bytes % 4294967296), this 414 | } 415 | }, _0x5887c8.prototype.finalize = function() { 416 | if (!this.finalized) { 417 | this.finalized = !0; 418 | var e = this.blocks, 419 | b = this.lastByteIndex; 420 | 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() 421 | } 422 | }, _0x5887c8.prototype.hash = function() { 423 | var e, b, a, f, c, r, t = this.blocks; 424 | 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) 425 | }, _0x5887c8.prototype.hex = function() { 426 | this.finalize(); 427 | var e = this.h0, 428 | b = this.h1, 429 | a = this.h2, 430 | f = this.h3; 431 | 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] 432 | }, _0x5887c8.prototype.toString = _0x5887c8.prototype.hex, _0x5887c8.prototype.digest = function() { 433 | this.finalize(); 434 | var e = this.h0, 435 | b = this.h1, 436 | a = this.h2, 437 | f = this.h3; 438 | 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] 439 | }, _0x5887c8.prototype.array = _0x5887c8.prototype.digest, _0x5887c8.prototype.arrayBuffer = function() { 440 | this.finalize(); 441 | var e = new ArrayBuffer(16), 442 | b = new Uint32Array(e); 443 | return b[0] = this.h0, b[1] = this.h1, b[2] = this.h2, b[3] = this.h3, e 444 | }, _0x5887c8.prototype.buffer = _0x5887c8.prototype.arrayBuffer, _0x5887c8.prototype.base64 = function() { 445 | 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]; 446 | return f + (_0x2c185e[(e = c[r]) >>> 2] + _0x2c185e[e << 4 & 63] + "==") 447 | }; 448 | var _0x4dd781 = _0x38ba77(); 449 | _0x17dcbf ? _0x770f81.exports = _0x4dd781 : (_0x1702f9.md5 = _0x4dd781, _0x554fed && (void 0)(function() { 450 | return _0x4dd781 451 | })) 452 | }() 453 | }); 454 | 455 | function _0x178cef(e) { 456 | return jsvmp("484e4f4a403f52430038001eab0015840e8ee21a00000000000000621b000200001d000146000306000e271f001b000200021d00010500121b001b000b021b000b04041d0001071b000b0500000003000126207575757575757575757575757575757575757575757575757575757575757575", [, , void 0 !== _0x124d1a ? _0x124d1a : void 0, _0x178cef, e]) 457 | } 458 | for (var _0xb55f3e = { 459 | boe: !1, 460 | aid: 0, 461 | dfp: !1, 462 | sdi: !1, 463 | enablePathList: [], 464 | _enablePathListRegex: [], 465 | urlRewriteRules: [], 466 | _urlRewriteRules: [], 467 | initialized: !1, 468 | enableTrack: !1, 469 | track: { 470 | unitTime: 0, 471 | unitAmount: 0, 472 | fre: 0 473 | }, 474 | triggerUnload: !1, 475 | region: "", 476 | regionConf: {}, 477 | umode: 0, 478 | v: !1, 479 | perf: !1, 480 | xxbg: !0 481 | }, _0x3eaf64 = { 482 | debug: function(e, b) { 483 | let a = !1; 484 | a = !1 485 | } 486 | }, _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); 487 | var _0x2ce54d = function(e) { 488 | for (var b = e.length, a = "", f = 0; f < b;) a += _0x2e9f6d[e[f++]]; 489 | return a 490 | }, 491 | _0x5960a2 = function(e) { 492 | 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++)]; 493 | return f 494 | }, 495 | _0x4e46b6 = { 496 | encode: _0x2ce54d, 497 | decode: _0x5960a2 498 | }; 499 | 500 | function sign(e, b) { 501 | return jsvmp("484e4f4a403f5243001f240fbf2031ccf317480300000000000007181b0002012e1d00921b000b171b000b02402217000a1c1b000b1726402217000c1c1b000b170200004017002646000306000e271f001b000200021d00920500121b001b000b031b000b17041d0092071b000b041e012f17000d1b000b05260a0000101c1b000b06260a0000101c1b001b000b071e01301d00931b001b000b081e00081d00941b0048021d00951b001b000b1b1d00961b0048401d009e1b001b000b031b000b16041d009f1b001b000b09221e0131241b000b031b000b09221e0131241b000b1e0a000110040a0001101d00d51b001b000b09221e0131241b000b031b000b09221e0131241b000b180a000110040a0001101d00d71b001b000b0a1e00101d00d91b001b000b0b261b000b1a1b000b190a0002101d00db1b001b000b0c261b000b221b000b210a0002101d00dc1b001b000b0d261b000b230200200a0002101d00dd1b001b000b09221e0131241b000b031b000b24040a0001101d00df1b001b000b0e1a00221e00de240a0000104903e82b1d00e31b001b000b0f260a0000101d00e41b001b000b1d1d00e71b001b000b1a4901002b1d00e81b001b000b1a4901002c1d00ea1b001b000b191d00f21b001b000b1f480e191d00f81b001b000b1f480f191d00f91b001b000b20480e191d00fb1b001b000b20480f191d00fe1b001b000b25480e191d01001b001b000b25480f191d01011b001b000b264818344900ff2f1d01031b001b000b264810344900ff2f1d01321b001b000b264808344900ff2f1d01331b001b000b264800344900ff2f1d01341b001b000b274818344900ff2f1d01351b001b000b274810344900ff2f1d01361b001b000b274808344900ff2f1d01371b001b000b274800344900ff2f1d01381b001b000b281b000b29311b000b2a311b000b2b311b000b2c311b000b2d311b000b2e311b000b2f311b000b30311b000b31311b000b32311b000b33311b000b34311b000b35311b000b36311b000b37311b000b38311b000b39311d01391b004900ff1d013a1b001b000b10261b000b281b000b2a1b000b2c1b000b2e1b000b301b000b321b000b341b000b361b000b381b000b3a1b000b291b000b2b1b000b2d1b000b2f1b000b311b000b331b000b351b000b371b000b390a0013101d013b1b001b000b0c261b000b111b000b3b041b000b3c0a0002101d013c1b001b000b12261b000b1c1b000b3b1b000b3d0a0003101d013d1b001b000b13261b000b3e0200240a0002101d013e1b000b3f0000013f000126207575757575757575757575757575757575757575757575757575757575757575012b0e7776757a7d7643617c637661676a027a77065c717976706708777671667474766107767d65707c77760374766707707c7d607c7f7607757a61767166740a7c66677661447a77677b0a7a7d7d7661447a77677b0b7c666776615b767a747b670b7a7d7d76615b767a747b6709666076615274767d670b677c5f7c64766150726076077a7d77766b5c7508767f767067617c7d09667d7776757a7d76770963617c677c676a637608677c4067617a7d740470727f7f0763617c7076606010487c71797670673363617c707660604e067c717976706705677a677f76047d7c7776012e0125012402602341525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a383c2e0260224157787763747b2749586042512b233c5e75656420254b5a22412126384446527f567a245d5f717c624a475c4366697e5579597d616a6b2a5b45547072406750762e0260214157787763747b2749586042512b233c5e75656420254b5a224121263e4446527f567a245d5f717c624a475c4366697e5579597d616a6b2a5b45547072406750762e02602041525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a3e4c2e012a022222067f767d74677b0a707b7261507c7776526702222306707b726152670f487c717976706733447a7d777c644e08577c70667e767d6712487c7179767067335d72657a7472677c614e057960777c7e10487c7179767067335b7a60677c616a4e07637f66747a7d60084c637b727d677c7e0b70727f7f437b727d677c7e0b4c4c7d7a747b677e726176055266777a7c1850727d65726041767d7776617a7d74507c7d67766b6721570964767177617a657661137476675c647d43617c637661676a5d727e7660097f727d74667274766006707b617c7e760761667d677a7e7607707c7d7d767067144c4c64767177617a6576614c7665727f66726776134c4c60767f767d7a667e4c7665727f667267761b4c4c64767177617a6576614c6070617a63674c75667d70677a7c7d174c4c64767177617a6576614c6070617a63674c75667d70154c4c64767177617a6576614c6070617a63674c757d134c4c756b77617a6576614c7665727f66726776124c4c77617a6576614c667d64617263637677154c4c64767177617a6576614c667d64617263637677114c4c77617a6576614c7665727f66726776144c4c60767f767d7a667e4c667d64617263637677144c4c756b77617a6576614c667d64617263637677094c60767f767d7a667e0c70727f7f40767f767d7a667e164c40767f767d7a667e4c5a57564c4176707c6177766108777c70667e767d670478766a60057e7267707b06417674566b630a4f3748723e694e77704c067072707b764c04607c7e7608707675407b72616308507675407b72616305767c72637a16767c44767151617c64607661577a60637267707b76610f717a7d775c717976706752606a7d700e7a60565c44767151617c646076610120047c63767d0467766067097a7d707c747d7a677c077c7d7661617c6104707c77761242465c47524c564b5056565756574c5641410e607660607a7c7d40677c61727476076076675a67767e10607c7e7658766a5b766176516a6776770a61767e7c65765a67767e097a7d77766b767757510c437c7a7d6776615665767d670e5e40437c7a7d6776615665767d670d706176726776567f767e767d670670727d65726009677c5772677246415f076176637f727076034f603901740a7d72677a6576707c777614487c717976706733437f66747a7d526161726a4e4a4d7b676763602c294f3c4f3c3b48233e2a4e68223f206e3b4f3d48233e2a4e68223f206e3a68206e6f48723e75233e2a4e68223f276e3b2948723e75233e2a4e68223f276e3a68246e3a0127087f7c7072677a7c7d047b61767504757a7f76107b676763293c3c7f7c70727f7b7c606708637f7267757c617e02222102222007647a7d777c646002222703647a7d02222607727d77617c7a77022225057f7a7d666b022224067a637b7c7d7602222b047a63727702222a047a637c77022123037e7270022122097e72707a7d677c607b0c7e72704c637c64766163703a0470617c60036b22220570617a7c6005756b7a7c6004637a787602212102212002212702212602212502212402212b08757a6176757c6b3c067c637661723c05337c63613c05337c63673c07707b617c7e763c0867617a77767d673c047e607a7602212a0220230665767d777c6106547c7c747f760e4c637261727e40647a67707b5c7d0a777a61767067407a747d0a707c7d607a6067767d670660647a67707b03777c7e07637b727d677c7e047b7c7c7840525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a3e3d03727a77017d01750161096067726167477a7e7601670972717a7f7a677a76600a677a7e766067727e6322137b72617764726176507c7d70666161767d706a0c7776657a70765e767e7c616a087f727d74667274760a6176607c7f66677a7c7d0f7265727a7f4176607c7f66677a7c7d0960706176767d477c630a60706176767d5f767567107776657a7076437a6b767f4172677a7c0a63617c77667067406671077172676776616a016309677c66707b5a7d757c08677a7e76697c7d760a677a7e766067727e6321077463665a7d757c0b7960557c7d67605f7a60670b637f66747a7d605f7a60670a677a7e766067727e63200a76657661507c7c787a760767674c60707a77017e0b606a7d67726b5661617c610c7d72677a65765f767d74677b056167705a43097563457661607a7c7d0b4c4c657661607a7c7d4c4c08707f7a767d675a770a677a7e766067727e63270b766b67767d77557a767f77046366607b03727f7f04677b767d097172607625274c707b0c75617c7e507b7261507c7776067125274c2023022022087172607625274c23022021087172607625274c22022020087172607625274c2102202702202602202507747667477a7e760220240b777c7e5d7c6745727f7a77096066716067617a7d740863617c677c707c7f02202b02202a01230e222323232323232322222323232302272302272207757c616176727f02272104717c776a096067617a7d747a756a02686e0b717c776a45727f216067610a717c776a4c7b72607b2e01350366617f02272005626676616a0a72607c7f774c607a747d096372677b7d727e762e0967674c6476717a772e063566667a772e0227270227260e4c716a6776774c6076704c777a770227250a27212a272a2524212a25097576457661607a7c7d0227240e4c232151274925647c232323232202272b02272a05607f7a7076022623074056505a5d555c037d7c6409677a7e766067727e6305757f7c7c610661727d777c7e0f7476674747447671507c7c787a7660056767647a770867674c6476717a770767674476715a770b67674c6476717a774c65210967674476717a7745210761667d7d7a7d7405757f66607b087e7c65765f7a60670660637f7a70760671765e7c657609707f7a70785f7a6067077176507f7a70780c78766a717c7261775f7a60670a717658766a717c7261770b7270677a657640677267760b647a7d777c6440677267760360477e05676172707808667d7a67477a7e76037270700a667d7a67527e7c667d670871767b72657a7c61077e6074476a637603645a5707727a775f7a60670b63617a6572706a5e7c777606706660677c7e067260607a747d0f4456514c5756455a50564c5a5d555c0479607c7d0a6176747a7c7d507c7d75096176637c616746617f04766b7a67094b3e5e403e404746510c4b3e5e403e43524a5f5c525720232323232323232323232323232323232323232323232323232323232323232320772722772b70772a2b75232371212327762a2b23232a2a2b7670752b272124760165066671707c7776067776707c777602262202262102262002262702262602262502262402262b02262a022523022522022521022520", [, , void 0, void 0 !== _0x178cef ? _0x178cef : void 0, { 502 | boe: !1, 503 | aid: 0, 504 | dfp: !1, 505 | sdi: !1, 506 | enablePathList: [], 507 | _enablePathListRegex: [/\/web\/report/], 508 | urlRewriteRules: [], 509 | _urlRewriteRules: [], 510 | initialized: !1, 511 | enableTrack: !1, 512 | track: { 513 | unitTime: 0, 514 | unitAmount: 0, 515 | fre: 0 516 | }, 517 | triggerUnload: !1, 518 | region: "", 519 | regionConf: {}, 520 | umode: 0, 521 | v: !1, 522 | perf: !1, 523 | xxbg: !0 524 | }, () => 0, () => "03v", { 525 | ubcode: 0 526 | }, { 527 | bogusIndex: 0, 528 | msNewTokenList: [], 529 | moveList: [], 530 | clickList: [], 531 | keyboardList: [], 532 | activeState: [], 533 | aidList: [], 534 | envcode: 0, 535 | msToken: "", 536 | msStatus: 0, 537 | __ac_testid: "", 538 | ttwid: "", 539 | tt_webid: "", 540 | tt_webid_v2: "" 541 | }, void 0 !== _0x4e46b6 ? _0x4e46b6 : void 0, { 542 | userAgent: b 543 | }, (e, b) => { 544 | let a = new Uint8Array(3); 545 | return a[0] = e / 256, a[1] = e % 256, a[2] = b % 256, String.fromCharCode.apply(null, a) 546 | }, (e, b) => { 547 | let a, f = [], 548 | c = 0, 549 | r = ""; 550 | for (let e = 0; e < 256; e++) f[e] = e; 551 | 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; 552 | let t = 0; 553 | c = 0; 554 | 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]); 555 | return r 556 | }, (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) => { 557 | let v = new Uint8Array(19); 558 | 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) 559 | }, e => String.fromCharCode(e), (e, b, a) => String.fromCharCode(e) + String.fromCharCode(b) + a, (e, b) => jsvmp("484e4f4a403f524300281018f7b851f02d296e5b00000000000004a21b0002001d1d001e1b00131e00061a001d001f1b000b070200200200210d1b000b070200220200230d1b000b070200240200250d1b000b070200260200270d1b001b000b071b000b05191d00031b000200001d00281b0048001d00291b000b041e002a1b000b0b4803283b1700f11b001b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f4810331b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f480833301b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f301d002c1b00220b091b000b08221e002d241b000b0a4a00fc00002f4812340a000110281d00281b00220b091b000b08221e002d241b000b0a4a0003f0002f480c340a000110281d00281b00220b091b000b08221e002d241b000b0a490fc02f4806340a000110281d00281b00220b091b000b08221e002d241b000b0a483f2f0a000110281d002816ff031b000b041e002a1b000b0b294800391700e01b001b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f4810331b000b041e002a1b000b0b3917001e1b000b04221e002b241b000b0b0a0001104900ff2f4808331600054800301d002c1b00220b091b000b08221e002d241b000b0a4a00fc00002f4812340a000110281d00281b00220b091b000b08221e002d241b000b0a4a0003f0002f480c340a000110281d00281b00220b091b000b041e002a1b000b0b3917001e1b000b08221e002d241b000b0a490fc02f4806340a0001101600071b000b06281d00281b00220b091b000b06281d00281b000b090000002e000126207575757575757575757575757575757575757575757575757575757575757575012b0e7776757a7d7643617c637661676a027a77065c717976706708777671667474766107767d65707c77760374766707707c7d607c7f7607757a61767166740a7c66677661447a77677b0a7a7d7d7661447a77677b0b7c666776615b767a747b670b7a7d7d76615b767a747b6709666076615274767d670b677c5f7c64766150726076077a7d77766b5c7508767f767067617c7d09667d7776757a7d76770963617c677c676a637608677c4067617a7d740470727f7f0763617c7076606010487c71797670673363617c707660604e067c717976706705677a677f76047d7c7776012e0125012402602341525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a383c2e0260224157787763747b2749586042512b233c5e75656420254b5a22412126384446527f567a245d5f717c624a475c4366697e5579597d616a6b2a5b45547072406750762e0260214157787763747b2749586042512b233c5e75656420254b5a224121263e4446527f567a245d5f717c624a475c4366697e5579597d616a6b2a5b45547072406750762e02602041525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a3e4c2e012a022222067f767d74677b0a707b7261507c7776526702222306707b72615267", [, , , , e, b]), , sign, e, void 0]) 560 | } 561 | 562 | module.exports = { 563 | sign 564 | }; -------------------------------------------------------------------------------- /src/logger.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | 6 | custom_format = "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} - {message}" 7 | os.environ["LOGURU_FORMAT"] = custom_format 8 | from loguru import logger 9 | 10 | script_path = os.path.split(os.path.realpath(sys.argv[0]))[0] 11 | 12 | logger.add( 13 | f"{script_path}/logs/streamget.log", 14 | level="DEBUG", 15 | format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}", 16 | filter=lambda i: i["level"].name != "INFO", 17 | serialize=False, 18 | enqueue=True, 19 | retention=1, 20 | rotation="300 KB", 21 | encoding='utf-8' 22 | ) 23 | 24 | logger.add( 25 | f"{script_path}/logs/PlayURL.log", 26 | level="INFO", 27 | format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {message}", 28 | filter=lambda i: i["level"].name == "INFO", 29 | serialize=False, 30 | enqueue=True, 31 | retention=1, 32 | rotation="300 KB", 33 | encoding='utf-8' 34 | ) 35 | -------------------------------------------------------------------------------- /src/proxy.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from enum import Enum, auto 4 | from dataclasses import dataclass, field 5 | from .utils import logger 6 | 7 | 8 | class ProxyType(Enum): 9 | HTTP = auto() 10 | HTTPS = auto() 11 | SOCKS = auto() 12 | 13 | 14 | @dataclass(frozen=True) 15 | class ProxyInfo: 16 | ip: str = field(default="", repr=True) 17 | port: str = field(default="", repr=True) 18 | 19 | def __post_init__(self): 20 | if (self.ip and not self.port) or (not self.ip and self.port): 21 | raise ValueError("IP or port cannot be empty") 22 | 23 | if (self.ip and self.port) and (not self.port.isdigit() or not (1 <= int(self.port) <= 65535)): 24 | raise ValueError("Port must be a digit between 1 and 65535") 25 | 26 | 27 | class ProxyDetector: 28 | def __init__(self): 29 | if sys.platform.startswith('win'): 30 | import winreg 31 | self.winreg = winreg 32 | self.__path = r'Software\Microsoft\Windows\CurrentVersion\Internet Settings' 33 | with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as key_user: 34 | self.__INTERNET_SETTINGS = winreg.OpenKeyEx(key_user, self.__path, 0, winreg.KEY_ALL_ACCESS) 35 | else: 36 | self.__is_windows = False 37 | 38 | def get_proxy_info(self) -> ProxyInfo: 39 | if sys.platform.startswith('win'): 40 | ip, port = self._get_proxy_info_windows() 41 | else: 42 | ip, port = self._get_proxy_info_linux() 43 | return ProxyInfo(ip, port) 44 | 45 | def is_proxy_enabled(self) -> bool: 46 | if sys.platform.startswith('win'): 47 | return self._is_proxy_enabled_windows() 48 | else: 49 | return self._is_proxy_enabled_linux() 50 | 51 | def _get_proxy_info_windows(self) -> tuple[str, str]: 52 | ip, port = "", "" 53 | if self._is_proxy_enabled_windows(): 54 | try: 55 | ip_port = self.winreg.QueryValueEx(self.__INTERNET_SETTINGS, "ProxyServer")[0] 56 | if ip_port: 57 | ip, port = ip_port.split(":") 58 | except FileNotFoundError as err: 59 | logger.warning("No proxy information found: " + str(err)) 60 | except Exception as err: 61 | logger.error("An error occurred: " + str(err)) 62 | else: 63 | logger.debug("No proxy is enabled on the system") 64 | return ip, port 65 | 66 | def _is_proxy_enabled_windows(self) -> bool: 67 | try: 68 | if self.winreg.QueryValueEx(self.__INTERNET_SETTINGS, "ProxyEnable")[0] == 1: 69 | return True 70 | except FileNotFoundError as err: 71 | print("No proxy information found: " + str(err)) 72 | except Exception as err: 73 | print("An error occurred: " + str(err)) 74 | return False 75 | 76 | @staticmethod 77 | def _get_proxy_info_linux() -> tuple[str, str]: 78 | proxies = { 79 | 'http': os.getenv('http_proxy'), 80 | 'https': os.getenv('https_proxy'), 81 | 'ftp': os.getenv('ftp_proxy') 82 | } 83 | ip = port = "" 84 | for proto, proxy in proxies.items(): 85 | if proxy: 86 | ip, port = proxy.split(':') 87 | break 88 | return ip, port 89 | 90 | def _is_proxy_enabled_linux(self) -> bool: 91 | proxies = self._get_proxy_info_linux() 92 | return any(proxy != '' for proxy in proxies) 93 | -------------------------------------------------------------------------------- /src/room.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | """ 4 | Author: Hmily 5 | GitHub:https://github.com/ihmily 6 | Date: 2023-07-17 23:52:05 7 | Update: 2025-02-04 04:57:00 8 | Copyright (c) 2023 by Hmily, All Rights Reserved. 9 | """ 10 | import re 11 | import urllib.parse 12 | import execjs 13 | import httpx 14 | import urllib.request 15 | from . import JS_SCRIPT_PATH, utils 16 | 17 | no_proxy_handler = urllib.request.ProxyHandler({}) 18 | opener = urllib.request.build_opener(no_proxy_handler) 19 | 20 | 21 | HEADERS = { 22 | 'User-Agent': 'Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) ' 23 | 'SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36', 24 | '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', 25 | 'Cookie': 's_v_web_id=verify_lk07kv74_QZYCUApD_xhiB_405x_Ax51_GYO9bUIyZQVf' 26 | } 27 | 28 | HEADERS_PC = { 29 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 ' 30 | 'Safari/537.36 Core/1.116.438.400 QQBrowser/13.0.6070.400', 31 | 'Cookie': 'sessionid=7494ae59ae06784454373ce25761e864; __ac_nonce=0670497840077ee4c9eb2; ' 32 | '__ac_signature=_02B4Z6wo00f012DZczQAAIDCJJBb3EjnINdg-XeAAL8-db; ' 33 | 's_v_web_id=verify_m1ztgtjj_vuHnMLZD_iwZ9_4YO4_BdN1_7wLP3pyqXsf2; ', 34 | } 35 | 36 | 37 | # X-bogus算法 38 | async def get_xbogus(url: str, headers: dict | None = None) -> str: 39 | if not headers or 'user-agent' not in (k.lower() for k in headers): 40 | headers = HEADERS 41 | query = urllib.parse.urlparse(url).query 42 | xbogus = execjs.compile(open(f'{JS_SCRIPT_PATH}/x-bogus.js').read()).call( 43 | 'sign', query, headers.get("User-Agent", "user-agent")) 44 | return xbogus 45 | 46 | 47 | # 获取房间ID和用户secID 48 | async def get_sec_user_id(url: str, proxy_addr: str | None = None, headers: dict | None = None) -> tuple | None: 49 | if not headers or all(k.lower() not in ['user-agent', 'cookie'] for k in headers): 50 | headers = HEADERS 51 | 52 | try: 53 | proxy_addr = utils.handle_proxy_addr(proxy_addr) 54 | async with httpx.AsyncClient(proxy=proxy_addr, timeout=15) as client: 55 | response = await client.get(url, headers=headers, follow_redirects=True) 56 | redirect_url = response.url 57 | if 'reflow/' in str(redirect_url): 58 | match = re.search(r'sec_user_id=([\w_\-]+)&', str(redirect_url)) 59 | if match: 60 | sec_user_id = match.group(1) 61 | room_id = str(redirect_url).split('?')[0].rsplit('/', maxsplit=1)[1] 62 | return room_id, sec_user_id 63 | else: 64 | print("Could not find sec_user_id in the URL.") 65 | else: 66 | print("The redirect URL does not contain 'reflow/'.") 67 | except Exception as e: 68 | print(f"An error occurred: {e}") 69 | return None 70 | 71 | 72 | # 获取抖音号 73 | async def get_unique_id(url: str, proxy_addr: str | None = None, headers: dict | None = None) -> str | None: 74 | if not headers or all(k.lower() not in ['user-agent', 'cookie'] for k in headers): 75 | headers = HEADERS_PC 76 | 77 | try: 78 | proxy_addr = utils.handle_proxy_addr(proxy_addr) 79 | async with httpx.AsyncClient(proxy=proxy_addr, timeout=15) as client: 80 | response = await client.get(url, headers=headers, follow_redirects=True) 81 | redirect_url = str(response.url) 82 | sec_user_id = redirect_url.split('?')[0].rsplit('/', maxsplit=1)[1] 83 | 84 | user_page_response = await client.get(f'https://www.douyin.com/user/{sec_user_id}', headers=headers) 85 | matches = re.findall(r'undefined\\"},\\"uniqueId\\":\\"(.*?)\\",\\"customVerify', 86 | user_page_response.text) 87 | if matches: 88 | unique_id = matches[-1] 89 | return unique_id 90 | else: 91 | print("Could not find unique_id in the response.") 92 | return None 93 | except Exception as e: 94 | print(f"An error occurred: {e}") 95 | return None 96 | 97 | 98 | # 获取直播间webID 99 | async def get_live_room_id(room_id: str, sec_user_id: str, proxy_addr: str | None = None, params: dict | None = None, 100 | headers: dict | None = None) -> str: 101 | if not headers or all(k.lower() not in ['user-agent', 'cookie'] for k in headers): 102 | headers = HEADERS 103 | 104 | if not params: 105 | params = { 106 | "verifyFp": "verify_lk07kv74_QZYCUApD_xhiB_405x_Ax51_GYO9bUIyZQVf", 107 | "type_id": "0", 108 | "live_id": "1", 109 | "room_id": room_id, 110 | "sec_user_id": sec_user_id, 111 | "app_id": "1128", 112 | "msToken": "wrqzbEaTlsxt52-vxyZo_mIoL0RjNi1ZdDe7gzEGMUTVh_HvmbLLkQrA_1HKVOa2C6gkxb6IiY6TY2z8enAkPEwGq--gM" 113 | "-me3Yudck2ailla5Q4osnYIHxd9dI4WtQ==", 114 | } 115 | 116 | api = f'https://webcast.amemv.com/webcast/room/reflow/info/?{urllib.parse.urlencode(params)}' 117 | xbogus = await get_xbogus(api) 118 | api = api + "&X-Bogus=" + xbogus 119 | 120 | try: 121 | proxy_addr = utils.handle_proxy_addr(proxy_addr) 122 | async with httpx.AsyncClient(proxy=proxy_addr, 123 | timeout=15) as client: 124 | response = await client.get(api, headers=headers) 125 | response.raise_for_status() 126 | json_data = response.json() 127 | return json_data['data']['room']['owner']['web_rid'] 128 | except httpx.HTTPStatusError as e: 129 | print(f"HTTP status error occurred: {e.response.status_code}") 130 | raise 131 | except Exception as e: 132 | print(f"An exception occurred during get_live_room_id: {e}") 133 | raise 134 | 135 | 136 | if __name__ == '__main__': 137 | room_url = "https://v.douyin.com/iQLgKSj/" 138 | _room_id, sec_uid = get_sec_user_id(room_url) 139 | web_rid = get_live_room_id(_room_id, sec_uid) 140 | print("return web_rid:", web_rid) 141 | -------------------------------------------------------------------------------- /src/stream.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | """ 4 | Author: Hmily 5 | GitHub: https://github.com/ihmily 6 | Date: 2023-07-15 23:15:00 7 | Update: 2025-02-06 02:28:00 8 | Copyright (c) 2023-2025 by Hmily, All Rights Reserved. 9 | Function: Get live stream data. 10 | """ 11 | import base64 12 | import hashlib 13 | import json 14 | import time 15 | import random 16 | import re 17 | from operator import itemgetter 18 | import urllib.parse 19 | import urllib.request 20 | from .utils import trace_error_decorator 21 | from .spider import ( 22 | get_douyu_stream_data, get_bilibili_stream_data 23 | ) 24 | 25 | QUALITY_MAPPING = {"OD": 0, "BD": 0, "UHD": 1, "HD": 2, "SD": 3, "LD": 4} 26 | 27 | 28 | def get_quality_index(quality) -> tuple: 29 | if not quality: 30 | return list(QUALITY_MAPPING.items())[0] 31 | 32 | quality_str = str(quality).upper() 33 | if quality_str.isdigit(): 34 | quality_int = int(quality_str[0]) 35 | quality_str = list(QUALITY_MAPPING.keys())[quality_int] 36 | return quality_str, QUALITY_MAPPING.get(quality_str, 0) 37 | 38 | 39 | @trace_error_decorator 40 | async def get_douyin_stream_url(json_data: dict, video_quality: str) -> dict: 41 | anchor_name = json_data.get('anchor_name') 42 | 43 | result = { 44 | "anchor_name": anchor_name, 45 | "is_live": False, 46 | } 47 | 48 | status = json_data.get("status", 4) 49 | 50 | if status == 2: 51 | stream_url = json_data['stream_url'] 52 | flv_url_dict = stream_url['flv_pull_url'] 53 | flv_url_list: list = list(flv_url_dict.values()) 54 | m3u8_url_dict = stream_url['hls_pull_url_map'] 55 | m3u8_url_list: list = list(m3u8_url_dict.values()) 56 | 57 | while len(flv_url_list) < 5: 58 | flv_url_list.append(flv_url_list[-1]) 59 | m3u8_url_list.append(m3u8_url_list[-1]) 60 | 61 | video_quality, quality_index = get_quality_index(video_quality) 62 | m3u8_url = m3u8_url_list[quality_index] 63 | flv_url = flv_url_list[quality_index] 64 | result |= { 65 | 'is_live': True, 66 | 'title': json_data['title'], 67 | 'quality': video_quality, 68 | 'm3u8_url': m3u8_url, 69 | 'flv_url': flv_url, 70 | 'record_url': m3u8_url or flv_url, 71 | } 72 | return result 73 | 74 | 75 | @trace_error_decorator 76 | async def get_tiktok_stream_url(json_data: dict, video_quality: str) -> dict: 77 | if not json_data: 78 | return {"anchor_name": None, "is_live": False} 79 | 80 | def get_video_quality_url(stream, q_key) -> list: 81 | play_list = [] 82 | for key in stream: 83 | url_info = stream[key]['main'] 84 | play_url = url_info[q_key] 85 | sdk_params = url_info['sdk_params'] 86 | sdk_params = json.loads(sdk_params) 87 | vbitrate = int(sdk_params['vbitrate']) 88 | resolution = sdk_params['resolution'] 89 | if vbitrate != 0 and resolution: 90 | width, height = map(int, resolution.split('x')) 91 | play_list.append({'url': play_url, 'vbitrate': vbitrate, 'resolution': (width, height)}) 92 | 93 | play_list.sort(key=itemgetter('vbitrate'), reverse=True) 94 | play_list.sort(key=lambda x: (-x['vbitrate'], -x['resolution'][0], -x['resolution'][1])) 95 | return play_list 96 | 97 | live_room = json_data['LiveRoom']['liveRoomUserInfo'] 98 | user = live_room['user'] 99 | anchor_name = f"{user['nickname']}-{user['uniqueId']}" 100 | status = user.get("status", 4) 101 | 102 | result = { 103 | "anchor_name": anchor_name, 104 | "is_live": False, 105 | } 106 | 107 | if status == 2: 108 | stream_data = live_room['liveRoom']['streamData']['pull_data']['stream_data'] 109 | stream_data = json.loads(stream_data).get('data', {}) 110 | flv_url_list = get_video_quality_url(stream_data, 'flv') 111 | m3u8_url_list = get_video_quality_url(stream_data, 'hls') 112 | 113 | while len(flv_url_list) < 5: 114 | flv_url_list.append(flv_url_list[-1]) 115 | while len(m3u8_url_list) < 5: 116 | m3u8_url_list.append(m3u8_url_list[-1]) 117 | video_quality, quality_index = get_quality_index(video_quality) 118 | flv_url = flv_url_list[quality_index]['url'].replace("https://", "http://") 119 | m3u8_url = m3u8_url_list[quality_index]['url'].replace("https://", "http://") 120 | result |= { 121 | 'is_live': True, 122 | 'title': live_room['liveRoom']['title'], 123 | 'quality': video_quality, 124 | 'm3u8_url': m3u8_url, 125 | 'flv_url': flv_url, 126 | 'record_url': m3u8_url or flv_url, 127 | } 128 | return result 129 | 130 | 131 | @trace_error_decorator 132 | async def get_kuaishou_stream_url(json_data: dict, video_quality: str) -> dict: 133 | if json_data['type'] == 1 and not json_data["is_live"]: 134 | return json_data 135 | live_status = json_data['is_live'] 136 | 137 | result = { 138 | "type": 2, 139 | "anchor_name": json_data['anchor_name'], 140 | "is_live": live_status, 141 | } 142 | 143 | if live_status: 144 | quality_mapping_bit = {'OD': 99999, 'BD': 4000, 'UHD': 2000, 'HD': 1000, 'SD': 800, 'LD': 600} 145 | if video_quality in QUALITY_MAPPING: 146 | 147 | quality, quality_index = get_quality_index(video_quality) 148 | if 'm3u8_url_list' in json_data: 149 | m3u8_url_list = json_data['m3u8_url_list'][::-1] 150 | while len(m3u8_url_list) < 5: 151 | m3u8_url_list.append(m3u8_url_list[-1]) 152 | m3u8_url = m3u8_url_list[quality_index]['url'] 153 | result['m3u8_url'] = m3u8_url 154 | 155 | if 'flv_url_list' in json_data: 156 | if 'bitrate' in json_data['flv_url_list'][0]: 157 | flv_url_list = json_data['flv_url_list'] 158 | flv_url_list = sorted(flv_url_list, key=lambda x: x['bitrate'], reverse=True) 159 | quality_str = str(video_quality).upper() 160 | if quality_str.isdigit(): 161 | video_quality, quality_index_bitrate_value = list(quality_mapping_bit.items())[int(quality_str)] 162 | else: 163 | quality_index_bitrate_value = quality_mapping_bit.get(quality_str, 99999) 164 | video_quality = quality_str 165 | quality_index = next( 166 | (i for i, x in enumerate(flv_url_list) if x['bitrate'] <= quality_index_bitrate_value), None) 167 | if quality_index is None: 168 | quality_index = len(flv_url_list) - 1 169 | flv_url = flv_url_list[quality_index]['url'] 170 | 171 | result['flv_url'] = flv_url 172 | result['record_url'] = flv_url 173 | else: 174 | flv_url_list = json_data['flv_url_list'][::-1] 175 | while len(flv_url_list) < 5: 176 | flv_url_list.append(flv_url_list[-1]) 177 | flv_url = flv_url_list[quality_index]['url'] 178 | result |= {'flv_url': flv_url, 'record_url': flv_url} 179 | result['is_live'] = True 180 | result['quality'] = video_quality 181 | return result 182 | 183 | 184 | @trace_error_decorator 185 | async def get_huya_stream_url(json_data: dict, video_quality: str) -> dict: 186 | game_live_info = json_data['data'][0]['gameLiveInfo'] 187 | live_title = game_live_info['introduction'] 188 | stream_info_list = json_data['data'][0]['gameStreamInfoList'] 189 | anchor_name = game_live_info.get('nick', '') 190 | 191 | result = { 192 | "anchor_name": anchor_name, 193 | "is_live": False, 194 | } 195 | 196 | if stream_info_list: 197 | select_cdn = stream_info_list[0] 198 | flv_url = select_cdn.get('sFlvUrl') 199 | stream_name = select_cdn.get('sStreamName') 200 | flv_url_suffix = select_cdn.get('sFlvUrlSuffix') 201 | hls_url = select_cdn.get('sHlsUrl') 202 | hls_url_suffix = select_cdn.get('sHlsUrlSuffix') 203 | flv_anti_code = select_cdn.get('sFlvAntiCode') 204 | 205 | def get_anti_code(old_anti_code: str) -> str: 206 | 207 | # js地址:https://hd.huya.com/cdn_libs/mobile/hysdk-m-202402211431.js 208 | 209 | params_t = 100 210 | sdk_version = 2403051612 211 | 212 | # sdk_id是13位数毫秒级时间戳 213 | t13 = int(time.time()) * 1000 214 | sdk_sid = t13 215 | 216 | # 计算uuid和uid参数值 217 | init_uuid = (int(t13 % 10 ** 10 * 1000) + int(1000 * random.random())) % 4294967295 # 直接初始化 218 | uid = random.randint(1400000000000, 1400009999999) # 经过测试uid也可以使用init_uuid代替 219 | seq_id = uid + sdk_sid # 移动端请求的直播流地址中包含seqId参数 220 | 221 | # 计算ws_time参数值(16进制) 可以是当前毫秒时间戳,当然也可以直接使用url_query['wsTime'][0] 222 | # 原始最大误差不得慢240000毫秒 223 | target_unix_time = (t13 + 110624) // 1000 224 | ws_time = f"{target_unix_time:x}".lower() 225 | 226 | # fm参数值是经过url编码然后base64编码得到的,解码结果类似 DWq8BcJ3h6DJt6TY_$0_$1_$2_$3 227 | # 具体细节在上面js中查看,大概在32657行代码开始,有base64混淆代码请自行替换 228 | url_query = urllib.parse.parse_qs(old_anti_code) 229 | ws_secret_pf = base64.b64decode(urllib.parse.unquote(url_query['fm'][0]).encode()).decode().split("_")[0] 230 | ws_secret_hash = hashlib.md5(f'{seq_id}|{url_query["ctype"][0]}|{params_t}'.encode()).hexdigest() 231 | ws_secret = f'{ws_secret_pf}_{uid}_{stream_name}_{ws_secret_hash}_{ws_time}' 232 | ws_secret_md5 = hashlib.md5(ws_secret.encode()).hexdigest() 233 | 234 | anti_code = ( 235 | f'wsSecret={ws_secret_md5}&wsTime={ws_time}&seqid={seq_id}&ctype={url_query["ctype"][0]}&ver=1' 236 | f'&fs={url_query["fs"][0]}&uuid={init_uuid}&u={uid}&t={params_t}&sv={sdk_version}' 237 | f'&sdk_sid={sdk_sid}&codec=264' 238 | ) 239 | return anti_code 240 | 241 | new_anti_code = get_anti_code(flv_anti_code) 242 | flv_url = f'{flv_url}/{stream_name}.{flv_url_suffix}?{new_anti_code}&ratio=' 243 | m3u8_url = f'{hls_url}/{stream_name}.{hls_url_suffix}?{new_anti_code}&ratio=' 244 | 245 | quality_list = flv_anti_code.split('&exsphd=') 246 | if len(quality_list) > 1 and video_quality not in ["OD", "BD"]: 247 | pattern = r"(?<=264_)\d+" 248 | quality_list = list(re.findall(pattern, quality_list[1]))[::-1] 249 | while len(quality_list) < 5: 250 | quality_list.append(quality_list[-1]) 251 | 252 | video_quality_options = { 253 | "UHD": quality_list[0], 254 | "HD": quality_list[1], 255 | "SD": quality_list[2], 256 | "LD": quality_list[3] 257 | } 258 | 259 | if video_quality not in video_quality_options: 260 | raise ValueError( 261 | f"Invalid video quality. Available options are: {', '.join(video_quality_options.keys())}") 262 | 263 | flv_url = flv_url + str(video_quality_options[video_quality]) 264 | m3u8_url = m3u8_url + str(video_quality_options[video_quality]) 265 | 266 | result |= { 267 | 'is_live': True, 268 | 'title': live_title, 269 | 'quality': video_quality, 270 | 'm3u8_url': m3u8_url, 271 | 'flv_url': flv_url, 272 | 'record_url': flv_url or m3u8_url 273 | } 274 | return result 275 | 276 | 277 | @trace_error_decorator 278 | async def get_douyu_stream_url(json_data: dict, video_quality: str, cookies: str, proxy_addr: str) -> dict: 279 | if not json_data["is_live"]: 280 | return json_data 281 | 282 | video_quality_options = { 283 | "OD": '0', 284 | "BD": '0', 285 | "UHD": '3', 286 | "HD": '2', 287 | "SD": '1', 288 | "LD": '1' 289 | } 290 | 291 | rid = str(json_data["room_id"]) 292 | json_data.pop("room_id") 293 | rate = video_quality_options.get(video_quality, '0') 294 | flv_data = await get_douyu_stream_data(rid, rate, cookies=cookies, proxy_addr=proxy_addr) 295 | rtmp_url = flv_data['data'].get('rtmp_url') 296 | rtmp_live = flv_data['data'].get('rtmp_live') 297 | if rtmp_live: 298 | flv_url = f'{rtmp_url}/{rtmp_live}' 299 | json_data |= {'quality': video_quality, 'flv_url': flv_url, 'record_url': flv_url} 300 | return json_data 301 | 302 | 303 | @trace_error_decorator 304 | async def get_yy_stream_url(json_data: dict) -> dict: 305 | anchor_name = json_data.get('anchor_name', '') 306 | result = { 307 | "anchor_name": anchor_name, 308 | "is_live": False, 309 | } 310 | if 'avp_info_res' in json_data: 311 | stream_line_addr = json_data['avp_info_res']['stream_line_addr'] 312 | cdn_info = list(stream_line_addr.values())[0] 313 | flv_url = cdn_info['cdn_info']['url'] 314 | result |= { 315 | 'is_live': True, 316 | 'title': json_data['title'], 317 | 'quality': 'OD', 318 | 'flv_url': flv_url, 319 | 'record_url': flv_url 320 | } 321 | return result 322 | 323 | 324 | @trace_error_decorator 325 | async def get_bilibili_stream_url(json_data: dict, video_quality: str, proxy_addr: str, cookies: str) -> dict: 326 | anchor_name = json_data["anchor_name"] 327 | if not json_data["live_status"]: 328 | return { 329 | "anchor_name": anchor_name, 330 | "is_live": False 331 | } 332 | 333 | room_url = json_data['room_url'] 334 | 335 | video_quality_options = { 336 | "OD": '10000', 337 | "BD": '400', 338 | "UHD": '250', 339 | "HD": '150', 340 | "SD": '80', 341 | "LD": '80' 342 | } 343 | 344 | select_quality = video_quality_options[video_quality] 345 | play_url = await get_bilibili_stream_data( 346 | room_url, qn=select_quality, platform='web', proxy_addr=proxy_addr, cookies=cookies) 347 | return { 348 | 'anchor_name': json_data['anchor_name'], 349 | 'is_live': True, 350 | 'title': json_data['title'], 351 | 'quality': video_quality, 352 | 'record_url': play_url 353 | } 354 | 355 | 356 | @trace_error_decorator 357 | async def get_netease_stream_url(json_data: dict, video_quality: str) -> dict: 358 | if not json_data['is_live']: 359 | return json_data 360 | 361 | m3u8_url = json_data['m3u8_url'] 362 | flv_url = None 363 | if json_data.get('stream_list'): 364 | stream_list = json_data['stream_list']['resolution'] 365 | order = ['blueray', 'ultra', 'high', 'standard'] 366 | sorted_keys = [key for key in order if key in stream_list] 367 | while len(sorted_keys) < 5: 368 | sorted_keys.append(sorted_keys[-1]) 369 | video_quality, quality_index = get_quality_index(video_quality) 370 | selected_quality = sorted_keys[quality_index] 371 | flv_url_list = stream_list[selected_quality]['cdn'] 372 | selected_cdn = list(flv_url_list.keys())[0] 373 | flv_url = flv_url_list[selected_cdn] 374 | 375 | return { 376 | "is_live": True, 377 | "anchor_name": json_data['anchor_name'], 378 | "title": json_data['title'], 379 | 'quality': video_quality, 380 | "m3u8_url": m3u8_url, 381 | "flv_url": flv_url, 382 | "record_url": flv_url or m3u8_url 383 | } 384 | 385 | 386 | async def get_stream_url(json_data: dict, video_quality: str, url_type: str = 'm3u8', spec: bool = False, 387 | hls_extra_key: str | int = None, flv_extra_key: str | int = None) -> dict: 388 | if not json_data['is_live']: 389 | return json_data 390 | 391 | play_url_list = json_data['play_url_list'] 392 | while len(play_url_list) < 5: 393 | play_url_list.append(play_url_list[-1]) 394 | 395 | video_quality, selected_quality = get_quality_index(video_quality) 396 | data = { 397 | "anchor_name": json_data['anchor_name'], 398 | "is_live": True 399 | } 400 | 401 | def get_url(key): 402 | play_url = play_url_list[selected_quality] 403 | return play_url[key] if key else play_url 404 | 405 | if url_type == 'all': 406 | m3u8_url = get_url(hls_extra_key) 407 | flv_url = get_url(flv_extra_key) 408 | data |= { 409 | "m3u8_url": json_data['m3u8_url'] if spec else m3u8_url, 410 | "flv_url": json_data['flv_url'] if spec else flv_url, 411 | "record_url": m3u8_url 412 | } 413 | elif url_type == 'm3u8': 414 | m3u8_url = get_url(hls_extra_key) 415 | data |= {"m3u8_url": json_data['m3u8_url'] if spec else m3u8_url, "record_url": m3u8_url} 416 | else: 417 | flv_url = get_url(flv_extra_key) 418 | data |= {"flv_url": flv_url, "record_url": flv_url} 419 | data['title'] = json_data.get('title') 420 | data['quality'] = video_quality 421 | return data -------------------------------------------------------------------------------- /src/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import json 3 | import os 4 | import random 5 | import shutil 6 | import string 7 | from pathlib import Path 8 | import functools 9 | import hashlib 10 | import re 11 | import traceback 12 | from typing import Any 13 | from collections import OrderedDict 14 | import execjs 15 | from .logger import logger 16 | import configparser 17 | 18 | OptionalStr = str | None 19 | OptionalDict = dict | None 20 | 21 | 22 | class Color: 23 | RED = "\033[31m" 24 | GREEN = "\033[32m" 25 | YELLOW = "\033[33m" 26 | BLUE = "\033[34m" 27 | MAGENTA = "\033[35m" 28 | CYAN = "\033[36m" 29 | WHITE = "\033[37m" 30 | RESET = "\033[0m" 31 | 32 | @staticmethod 33 | def print_colored(text, color): 34 | print(f"{color}{text}{Color.RESET}") 35 | 36 | 37 | def trace_error_decorator(func: callable) -> callable: 38 | @functools.wraps(func) 39 | def wrapper(*args: list, **kwargs: dict) -> Any: 40 | try: 41 | return func(*args, **kwargs) 42 | except execjs.ProgramError: 43 | logger.warning('Failed to execute JS code. Please check if the Node.js environment') 44 | except Exception as e: 45 | error_line = traceback.extract_tb(e.__traceback__)[-1].lineno 46 | error_info = f"message: type: {type(e).__name__}, {str(e)} in function {func.__name__} at line: {error_line}" 47 | logger.error(error_info) 48 | return [] 49 | 50 | return wrapper 51 | 52 | 53 | def check_md5(file_path: str | Path) -> str: 54 | with open(file_path, 'rb') as fp: 55 | file_md5 = hashlib.md5(fp.read()).hexdigest() 56 | return file_md5 57 | 58 | 59 | def dict_to_cookie_str(cookies_dict: dict) -> str: 60 | cookie_str = '; '.join([f"{key}={value}" for key, value in cookies_dict.items()]) 61 | return cookie_str 62 | 63 | 64 | def read_config_value(file_path: str | Path, section: str, key: str) -> str | None: 65 | config = configparser.ConfigParser() 66 | 67 | try: 68 | config.read(file_path, encoding='utf-8-sig') 69 | except Exception as e: 70 | print(f"Error occurred while reading the configuration file: {e}") 71 | return None 72 | 73 | if section in config: 74 | if key in config[section]: 75 | return config[section][key] 76 | else: 77 | print(f"Key [{key}] does not exist in section [{section}].") 78 | else: 79 | print(f"Section [{section}] does not exist in the file.") 80 | 81 | return None 82 | 83 | 84 | def update_config(file_path: str | Path, section: str, key: str, new_value: str) -> None: 85 | config = configparser.ConfigParser() 86 | 87 | try: 88 | config.read(file_path, encoding='utf-8-sig') 89 | except Exception as e: 90 | print(f"An error occurred while reading the configuration file: {e}") 91 | return 92 | 93 | if section not in config: 94 | print(f"Section [{section}] does not exist in the file.") 95 | return 96 | 97 | # 转义%字符 98 | escaped_value = new_value.replace('%', '%%') 99 | config[section][key] = escaped_value 100 | 101 | try: 102 | with open(file_path, 'w', encoding='utf-8-sig') as configfile: 103 | config.write(configfile) 104 | print(f"The value of {key} under [{section}] in the configuration file has been updated.") 105 | except Exception as e: 106 | print(f"Error occurred while writing to the configuration file: {e}") 107 | 108 | 109 | def get_file_paths(directory: str) -> list: 110 | file_paths = [] 111 | for root, dirs, files in os.walk(directory): 112 | for file in files: 113 | file_paths.append(os.path.join(root, file)) 114 | return file_paths 115 | 116 | 117 | def remove_emojis(text: str, replace_text: str = '') -> str: 118 | emoji_pattern = re.compile( 119 | "[" 120 | "\U0001F1E0-\U0001F1FF" # flags (iOS) 121 | "\U0001F300-\U0001F5FF" # symbols & pictographs 122 | "\U0001F600-\U0001F64F" # emoticons 123 | "\U0001F680-\U0001F6FF" # transport & map symbols 124 | "\U0001F700-\U0001F77F" # alchemical symbols 125 | "\U0001F780-\U0001F7FF" # Geometric Shapes Extended 126 | "\U0001F800-\U0001F8FF" # Supplemental Arrows-C 127 | "\U0001F900-\U0001F9FF" # Supplemental Symbols and Pictographs 128 | "\U0001FA00-\U0001FA6F" # Chess Symbols 129 | "\U0001FA70-\U0001FAFF" # Symbols and Pictographs Extended-A 130 | "\U00002702-\U000027B0" # Dingbats 131 | "]+", 132 | flags=re.UNICODE 133 | ) 134 | return emoji_pattern.sub(replace_text, text) 135 | 136 | 137 | def remove_duplicate_lines(file_path: str | Path) -> None: 138 | unique_lines = OrderedDict() 139 | text_encoding = 'utf-8-sig' 140 | with open(file_path, 'r', encoding=text_encoding) as input_file: 141 | for line in input_file: 142 | unique_lines[line.strip()] = None 143 | with open(file_path, 'w', encoding=text_encoding) as output_file: 144 | for line in unique_lines: 145 | output_file.write(line + '\n') 146 | 147 | 148 | def check_disk_capacity(file_path: str | Path, show: bool = False) -> float: 149 | absolute_path = os.path.abspath(file_path) 150 | directory = os.path.dirname(absolute_path) 151 | disk_usage = shutil.disk_usage(directory) 152 | disk_root = Path(directory).anchor 153 | free_space_gb = disk_usage.free / (1024 ** 3) 154 | if show: 155 | print(f"{disk_root} Total: {disk_usage.total / (1024 ** 3):.2f} GB " 156 | f"Used: {disk_usage.used / (1024 ** 3):.2f} GB " 157 | f"Free: {free_space_gb:.2f} GB\n") 158 | return free_space_gb 159 | 160 | 161 | def handle_proxy_addr(proxy_addr): 162 | if proxy_addr: 163 | if not proxy_addr.startswith('http'): 164 | proxy_addr = 'http://' + proxy_addr 165 | else: 166 | proxy_addr = None 167 | return proxy_addr 168 | 169 | 170 | def generate_random_string(length: int) -> str: 171 | characters = string.ascii_uppercase + string.digits 172 | random_string = ''.join(random.choices(characters, k=length)) 173 | return random_string 174 | 175 | 176 | def jsonp_to_json(jsonp_str: str) -> OptionalDict: 177 | pattern = r'(\w+)\((.*)\);?$' 178 | match = re.search(pattern, jsonp_str) 179 | 180 | if match: 181 | _, json_str = match.groups() 182 | json_obj = json.loads(json_str) 183 | return json_obj 184 | else: 185 | raise Exception("No JSON data found in JSONP response.") 186 | 187 | 188 | def replace_url(file_path: str | Path, old: str, new: str) -> None: 189 | with open(file_path, 'r', encoding='utf-8-sig') as f: 190 | content = f.read() 191 | if old in content: 192 | with open(file_path, 'w', encoding='utf-8-sig') as f: 193 | f.write(content.replace(old, new)) 194 | --------------------------------------------------------------------------------