Full Code of NanmiCoder/MediaCrawler for AI

main 2b049d05a3aa cached
212 files
3.0 MB
806.3k tokens
1825 symbols
1 requests
Download .txt
Showing preview only (3,221K chars total). Download the full file or copy to clipboard to get everything.
Repository: NanmiCoder/MediaCrawler
Branch: main
Commit: 2b049d05a3aa
Files: 212
Total size: 3.0 MB

Directory structure:
gitextract_6yb3d2nb/

├── .gitattributes
├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── quesiton.md
│   └── workflows/
│       └── deploy.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .python-version
├── LICENSE
├── README.md
├── README_en.md
├── README_es.md
├── api/
│   ├── __init__.py
│   ├── main.py
│   ├── routers/
│   │   ├── __init__.py
│   │   ├── crawler.py
│   │   ├── data.py
│   │   └── websocket.py
│   ├── schemas/
│   │   ├── __init__.py
│   │   └── crawler.py
│   ├── services/
│   │   ├── __init__.py
│   │   └── crawler_manager.py
│   └── webui/
│       ├── assets/
│       │   ├── index-DvClRayq.js
│       │   └── index-OiBmsgXF.css
│       └── index.html
├── base/
│   ├── __init__.py
│   └── base_crawler.py
├── cache/
│   ├── __init__.py
│   ├── abs_cache.py
│   ├── cache_factory.py
│   ├── local_cache.py
│   └── redis_cache.py
├── cmd_arg/
│   ├── __init__.py
│   └── arg.py
├── config/
│   ├── __init__.py
│   ├── base_config.py
│   ├── bilibili_config.py
│   ├── db_config.py
│   ├── dy_config.py
│   ├── ks_config.py
│   ├── tieba_config.py
│   ├── weibo_config.py
│   ├── xhs_config.py
│   └── zhihu_config.py
├── constant/
│   ├── __init__.py
│   ├── baidu_tieba.py
│   └── zhihu.py
├── database/
│   ├── __init__.py
│   ├── db.py
│   ├── db_session.py
│   ├── models.py
│   └── mongodb_store_base.py
├── docs/
│   ├── .vitepress/
│   │   ├── config.mjs
│   │   └── theme/
│   │       ├── DynamicAds.vue
│   │       ├── MyLayout.vue
│   │       ├── custom.css
│   │       └── index.js
│   ├── CDP模式使用指南.md
│   ├── data_storage_guide.md
│   ├── excel_export_guide.md
│   ├── hit_stopwords.txt
│   ├── index.md
│   ├── mediacrawlerpro订阅.md
│   ├── 代理使用.md
│   ├── 作者介绍.md
│   ├── 原生环境管理文档.md
│   ├── 常见问题.md
│   ├── 开发者咨询.md
│   ├── 微信交流群.md
│   ├── 快代理使用文档.md
│   ├── 手机号登录说明.md
│   ├── 捐赠名单.md
│   ├── 知识付费介绍.md
│   ├── 词云图使用配置.md
│   ├── 豌豆HTTP使用文档.md
│   ├── 项目代码结构.md
│   └── 项目架构文档.md
├── libs/
│   ├── douyin.js
│   └── zhihu.js
├── main.py
├── media_platform/
│   ├── __init__.py
│   ├── bilibili/
│   │   ├── __init__.py
│   │   ├── client.py
│   │   ├── core.py
│   │   ├── exception.py
│   │   ├── field.py
│   │   ├── help.py
│   │   └── login.py
│   ├── douyin/
│   │   ├── __init__.py
│   │   ├── client.py
│   │   ├── core.py
│   │   ├── exception.py
│   │   ├── field.py
│   │   ├── help.py
│   │   └── login.py
│   ├── kuaishou/
│   │   ├── __init__.py
│   │   ├── client.py
│   │   ├── core.py
│   │   ├── exception.py
│   │   ├── field.py
│   │   ├── graphql/
│   │   │   ├── comment_list.graphql
│   │   │   ├── search_query.graphql
│   │   │   ├── video_detail.graphql
│   │   │   ├── vision_profile.graphql
│   │   │   ├── vision_profile_photo_list.graphql
│   │   │   ├── vision_profile_user_list.graphql
│   │   │   └── vision_sub_comment_list.graphql
│   │   ├── graphql.py
│   │   ├── help.py
│   │   └── login.py
│   ├── tieba/
│   │   ├── __init__.py
│   │   ├── client.py
│   │   ├── core.py
│   │   ├── field.py
│   │   ├── help.py
│   │   ├── login.py
│   │   └── test_data/
│   │       ├── note_comments.html
│   │       ├── note_detail.html
│   │       ├── note_sub_comments.html
│   │       ├── search_keyword_notes.html
│   │       └── tieba_note_list.html
│   ├── weibo/
│   │   ├── __init__.py
│   │   ├── client.py
│   │   ├── core.py
│   │   ├── exception.py
│   │   ├── field.py
│   │   ├── help.py
│   │   └── login.py
│   ├── xhs/
│   │   ├── __init__.py
│   │   ├── client.py
│   │   ├── core.py
│   │   ├── exception.py
│   │   ├── extractor.py
│   │   ├── field.py
│   │   ├── help.py
│   │   ├── login.py
│   │   ├── playwright_sign.py
│   │   └── xhs_sign.py
│   └── zhihu/
│       ├── __init__.py
│       ├── client.py
│       ├── core.py
│       ├── exception.py
│       ├── field.py
│       ├── help.py
│       └── login.py
├── model/
│   ├── __init__.py
│   ├── m_baidu_tieba.py
│   ├── m_bilibili.py
│   ├── m_douyin.py
│   ├── m_kuaishou.py
│   ├── m_weibo.py
│   ├── m_xiaohongshu.py
│   └── m_zhihu.py
├── mypy.ini
├── package.json
├── proxy/
│   ├── __init__.py
│   ├── base_proxy.py
│   ├── providers/
│   │   ├── __init__.py
│   │   ├── jishu_http_proxy.py
│   │   ├── kuaidl_proxy.py
│   │   └── wandou_http_proxy.py
│   ├── proxy_ip_pool.py
│   ├── proxy_mixin.py
│   └── types.py
├── pyproject.toml
├── recv_sms.py
├── requirements.txt
├── store/
│   ├── __init__.py
│   ├── bilibili/
│   │   ├── __init__.py
│   │   ├── _store_impl.py
│   │   └── bilibilli_store_media.py
│   ├── douyin/
│   │   ├── __init__.py
│   │   ├── _store_impl.py
│   │   └── douyin_store_media.py
│   ├── excel_store_base.py
│   ├── kuaishou/
│   │   ├── __init__.py
│   │   └── _store_impl.py
│   ├── tieba/
│   │   ├── __init__.py
│   │   └── _store_impl.py
│   ├── weibo/
│   │   ├── __init__.py
│   │   ├── _store_impl.py
│   │   └── weibo_store_media.py
│   ├── xhs/
│   │   ├── __init__.py
│   │   ├── _store_impl.py
│   │   └── xhs_store_media.py
│   └── zhihu/
│       ├── __init__.py
│       └── _store_impl.py
├── test/
│   ├── __init__.py
│   ├── test_db_sync.py
│   ├── test_expiring_local_cache.py
│   ├── test_mongodb_integration.py
│   ├── test_proxy_ip_pool.py
│   ├── test_redis_cache.py
│   └── test_utils.py
├── tests/
│   ├── __init__.py
│   ├── conftest.py
│   ├── test_excel_store.py
│   └── test_store_factory.py
├── tools/
│   ├── __init__.py
│   ├── app_runner.py
│   ├── async_file_writer.py
│   ├── browser_launcher.py
│   ├── cdp_browser.py
│   ├── crawler_util.py
│   ├── easing.py
│   ├── file_header_manager.py
│   ├── httpx_util.py
│   ├── slider_util.py
│   ├── time_util.py
│   ├── utils.py
│   └── words.py
└── var.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
*.js linguist-language=python
*.css linguist-language=python
*.html linguist-language=python


================================================
FILE: .github/CODEOWNERS
================================================
# 默认:仓库所有文件都需要 @NanmiCoder 审核
*                                   @NanmiCoder


.github/workflows/**               @NanmiCoder


requirements.txt                   @NanmiCoder
pyproject.toml                     @NanmiCoder
Pipfile                            @NanmiCoder
package.json                       @NanmiCoder
package-lock.json                  @NanmiCoder
pnpm-lock.yaml                     @NanmiCoder


Dockerfile                         @NanmiCoder
docker/**                          @NanmiCoder
scripts/deploy/**                  @NanmiCoder


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---  
name: MediaCrawler Bug反馈  
about: 创建一个问题Bug以帮助MediaCrawler开源项目改进 
title: '[BUG] '  
labels: bug  
assignees: ''  
---  

## 🔍 问题检查清单  
<!-- 请在提交issue前确认以下事项 -->  

- [ ] 我已经仔细阅读了项目使用过程中的[常见问题汇总](https://nanmicoder.github.io/MediaCrawler/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.html) 
- [ ] 我已经搜索并查看了[已关闭的issues](https://github.com/NanmiCoder/MediaCrawler/issues?q=is%3Aissue+is%3Aclosed)  
- [ ] 我确认这不是由于滑块验证码、Cookie过期、Cookie提取错误、平台风控等常见原因导致的问题  

## 🐛 问题描述  
<!-- 请详细描述你遇到的问题 -->  


## 📝 复现步骤  
1.   
2.   
3.   

## 💻 运行环境  
- 操作系统:   
- Python版本:  
- 是否使用IP代理:  
- 是否使用VPN翻墙软件:
- 目标平台(抖音/小红书/微博等):  

## 📋 错误日志  
<!-- 请提供完整的错误日志信息 -->  
```shell  
在此粘贴错误日志
```

## 📷 错误截图
<!-- 请提供错误截图 -->


================================================
FILE: .github/ISSUE_TEMPLATE/quesiton.md
================================================
---  
name: MediaCrawler使用问题咨询  
about: 提交使用过程中遇到的问题  
title: '[问题] '  
labels: question  
assignees: ''  
---  

## ⚠️ 提交前确认  
<!-- 请确认以下事项 -->  
- [ ] 我已经仔细阅读了项目使用过程中的[常见问题汇总](https://nanmicoder.github.io/MediaCrawler/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98.html) 
- [ ] 我已经搜索并查看了[已关闭的issues](https://github.com/NanmiCoder/MediaCrawler/issues?q=is%3Aissue+is%3Aclosed)  
- [ ] 我确认这不是由于滑块验证码、Cookie过期、Cookie提取错误、平台风控等常见原因导致的问题  

## ❓ 问题描述  
<!-- 清晰简洁地描述你遇到的问题 -->  

## 🔍 使用场景  
<!-- 描述你在使用哪个功能时遇到的问题 -->  
- 目标平台: (如:小红书/抖音/微博等)  
- 使用功能: (如:关键词搜索/用户主页爬取等)  

## 💻 环境信息  
- 操作系统:   
- Python版本:  
- 是否使用IP代理:  
- 是否使用VPN翻墙软件:
- 目标平台(抖音/小红书/微博等):  

## 📋 错误日志  
```shell  
在此粘贴完整的错误日志
```

## 📷 错误截图
<!-- 请提供错误截图 -->


================================================
FILE: .github/workflows/deploy.yml
================================================
# 构建 VitePress 站点并将其部署到 GitHub Pages 的示例工作流程
#
name: Deploy VitePress site to Pages

on:
  # 在针对 `main` 分支的推送上运行。如果你
  # 使用 `master` 分支作为默认分支,请将其更改为 `master`
  push:
    branches: [main]

  # 允许你从 Actions 选项卡手动运行此工作流程
  workflow_dispatch:

# 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages
permissions:
  contents: read
  pages: write
  id-token: write

# 只允许同时进行一次部署,跳过正在运行和最新队列之间的运行队列
# 但是,不要取消正在进行的运行,因为我们希望允许这些生产部署完成
concurrency:
  group: pages
  cancel-in-progress: false

jobs:
  # 构建工作
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0 # 如果未启用 lastUpdated,则不需要
      # - uses: pnpm/action-setup@v3 # 如果使用 pnpm,请取消注释
      # - uses: oven-sh/setup-bun@v1 # 如果使用 Bun,请取消注释
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm # 或 pnpm / yarn
      - name: Setup Pages
        uses: actions/configure-pages@v4
      - name: Install dependencies
        run: npm ci # 或 pnpm install / yarn install / bun install
      - name: Build with VitePress
        run: npm run docs:build # 或 pnpm docs:build / yarn docs:build / bun run docs:build
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: docs/.vitepress/dist

  # 部署工作
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    needs: build
    runs-on: ubuntu-latest
    name: Deploy
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/#use-with-ide
.pdm.toml

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

*.xml
*.iml
.idea
/temp_image/
/browser_data/
/data/

*/.DS_Store
.vscode
/node_modules
docs/.vitepress/cache

# other gitignore
.venv
.refer

agent_zone
debug_tools

database/*.db


================================================
FILE: .pre-commit-config.yaml
================================================
# Pre-commit hooks configuration for MediaCrawler project
# See https://pre-commit.com for more information

repos:
  # Local hooks
  - repo: local
    hooks:
      # Python file header copyright check
      - id: check-file-headers
        name: Check Python file headers
        entry: python tools/file_header_manager.py --check
        language: system
        types: [python]
        pass_filenames: true
        stages: [pre-commit]

      # Auto-fix Python file headers
      - id: add-file-headers
        name: Add copyright headers to Python files
        entry: python tools/file_header_manager.py
        language: system
        types: [python]
        pass_filenames: true
        stages: [pre-commit]

  # Standard pre-commit hooks (optional, can be enabled later)
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
        exclude: ^(.*\.md|.*\.txt)$
      - id: end-of-file-fixer
        exclude: ^(.*\.md|.*\.txt)$
      - id: check-yaml
      - id: check-added-large-files
        args: ['--maxkb=10240']  # 10MB limit
      - id: check-merge-conflict
      - id: check-case-conflict
      - id: mixed-line-ending

# Global configuration
default_language_version:
  python: python3

# Run hooks on all files during manual run
# Usage: pre-commit run --all-files


================================================
FILE: .python-version
================================================
3.11


================================================
FILE: LICENSE
================================================
NON-COMMERCIAL LEARNING LICENSE 1.1

Copyright (c) [2024] [relakkes@gmail.com]

WHEREAS:
1. The copyright owner owns and controls the copyright of this software and related documentation files (hereinafter referred to as the "Software");
2. The user wishes to use the Software for learning purposes;
3. The copyright owner is willing to authorize the user to use the Software under the conditions stated in this license;

NOW, THEREFORE, the parties, in compliance with relevant laws and regulations, agree to the following terms:

SCOPE OF AUTHORIZATION:
1. The copyright owner hereby grants any natural person or legal entity (hereinafter referred to as the "User") accepting this license a free, non-exclusive, non-transferable right to use, copy, modify, and merge the Software for non-commercial learning purposes, subject to the following conditions.

CONDITIONS:
1. The User must include the above copyright notice and this license statement in all reasonably prominent locations of the Software and its copies.
2. The Software is limited to learning and research purposes only, and may not be used for large-scale crawling or activities that disrupt platform operations.
3. Without the written consent of the copyright owner, the Software may not be used for any commercial purposes or to cause improper influence on third parties.

DISCLAIMER:
1. The Software is provided "AS IS," without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement.
2. In no event shall the copyright owner be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this Software, even if advised of the possibility of such damage.

APPLICABLE LAW:
1. The interpretation and enforcement of this license shall comply with local laws and regulations.
2. Any disputes arising from or related to this license shall be resolved through friendly negotiation between the parties; if negotiation fails, either party may submit the dispute to the people's court where the copyright owner is located for resolution.

This license constitutes the entire agreement between the parties regarding the Software, superseding and merging all prior discussions, communications, and agreements, whether oral or written.


非商业学习使用许可证 1.1

版权所有 (c) [2024] [relakkes@gmail.com]

鉴于:
1. 版权所有者拥有和控制本软件和相关文档文件(以下简称“软件”)的版权;
2. 使用者希望使用该软件进行学习;
3. 版权所有者愿意在本许可证所述的条件下授权使用者使用该软件;

现因此,双方遵循相关法律法规,同意如下条款:

授权范围:
1. 版权所有者特此免费授予接受本许可证的任何自然人或法人(以下简称“使用者”)非独占的、不可转让的权利,在非商业学习目的下使用、复制、修改、合并本软件,前提是遵守以下条件。

条件:
1. 使用者必须在软件及其副本的所有合理显著位置包含上述版权声明和本许可证声明。
2. 本软件仅限用于学习和研究目的,不得用于大规模爬虫或对平台造成运营干扰的行为。
3. 未经版权所有者书面同意,不得将本软件用于任何商业用途或对第三方造成不当影响。

免责声明:
1. 本软件按“现状”提供,不提供任何形式的明示或暗示保证,包括但不限于对适销性、特定用途的适用性和非侵权的保证。
2. 在任何情况下,版权所有者均不对因使用本软件而产生的,或在任何方式上与本软件有关的任何直接、间接、偶然、特殊、示例性或后果性损害负责(包括但不限于采购替代品或服务;使用、数据或利润的损失;或业务中断),无论这些损害是如何引起的,以及无论是通过合同、严格责任还是侵权行为(包括疏忽或其他方式)产生的,即使已被告知此类损害的可能性。

适用法律:
1. 本许可证的解释和执行应遵循当地法律法规。
2. 因本许可证引起的或与之相关的任何争议,双方应友好协商解决;协商不成时,任何一方可将争议提交至版权所有者所在地的人民法院诉讼解决。

本许可证构成双方之间关于本软件的完整协议,取代并合并以前的讨论、交流和协议,无论是口头还是书面的。


================================================
FILE: README.md
================================================
# 🔥 MediaCrawler - 自媒体平台爬虫 🕷️

<div align="center">

<a href="https://trendshift.io/repositories/8291" target="_blank">
  <img src="https://trendshift.io/api/badge/repositories/8291" alt="NanmiCoder%2FMediaCrawler | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/>
</a>

[![GitHub Stars](https://img.shields.io/github/stars/NanmiCoder/MediaCrawler?style=social)](https://github.com/NanmiCoder/MediaCrawler/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/NanmiCoder/MediaCrawler?style=social)](https://github.com/NanmiCoder/MediaCrawler/network/members)
[![GitHub Issues](https://img.shields.io/github/issues/NanmiCoder/MediaCrawler)](https://github.com/NanmiCoder/MediaCrawler/issues)
[![GitHub Pull Requests](https://img.shields.io/github/issues-pr/NanmiCoder/MediaCrawler)](https://github.com/NanmiCoder/MediaCrawler/pulls)
[![License](https://img.shields.io/github/license/NanmiCoder/MediaCrawler)](https://github.com/NanmiCoder/MediaCrawler/blob/main/LICENSE)
[![中文](https://img.shields.io/badge/🇨🇳_中文-当前-blue)](README.md)
[![English](https://img.shields.io/badge/🇺🇸_English-Available-green)](README_en.md)
[![Español](https://img.shields.io/badge/🇪🇸_Español-Available-green)](README_es.md)
</div>



> **免责声明:**
> 
> 大家请以学习为目的使用本仓库⚠️⚠️⚠️⚠️,[爬虫违法违规的案件](https://github.com/HiddenStrawberry/Crawler_Illegal_Cases_In_China)  <br>
>
>本仓库的所有内容仅供学习和参考之用,禁止用于商业用途。任何人或组织不得将本仓库的内容用于非法用途或侵犯他人合法权益。本仓库所涉及的爬虫技术仅用于学习和研究,不得用于对其他平台进行大规模爬虫或其他非法行为。对于因使用本仓库内容而引起的任何法律责任,本仓库不承担任何责任。使用本仓库的内容即表示您同意本免责声明的所有条款和条件。
>
> 点击查看更为详细的免责声明。[点击跳转](#disclaimer)




## 📖 项目简介

一个功能强大的**多平台自媒体数据采集工具**,支持小红书、抖音、快手、B站、微博、贴吧、知乎等主流平台的公开信息抓取。

### 🔧 技术原理

- **核心技术**:基于 [Playwright](https://playwright.dev/) 浏览器自动化框架登录保存登录态
- **无需JS逆向**:利用保留登录态的浏览器上下文环境,通过 JS 表达式获取签名参数
- **优势特点**:无需逆向复杂的加密算法,大幅降低技术门槛


## ✨ 功能特性
| 平台   | 关键词搜索 | 指定帖子ID爬取 | 二级评论 | 指定创作者主页 | 登录态缓存 | IP代理池 | 生成评论词云图 |
| ------ | ---------- | -------------- | -------- | -------------- | ---------- | -------- | -------------- |
| 小红书 | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| 抖音   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| 快手   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| B 站   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| 微博   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| 贴吧   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| 知乎   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |



<strong>MediaCrawlerPro 重磅发布!开源不易,欢迎订阅支持</strong>

> 专注于学习成熟项目的架构设计,不仅仅是爬虫技术,Pro 版本的代码设计思路同样值得深入学习!

[MediaCrawlerPro](https://github.com/MediaCrawlerPro) 相较于开源版本的核心优势:

#### 🎯 核心功能升级
- ✅ **自媒体内容拆解Agent**(新增功能)
- ✅ **断点续爬功能**(重点特性)
- ✅ **多账号 + IP代理池支持**(重点特性)
- ✅ **去除 Playwright 依赖**,使用更简单
- ✅ **完整 Linux 环境支持**

#### 🏗️ 架构设计优化
- ✅ **代码重构优化**,更易读易维护(解耦 JS 签名逻辑)
- ✅ **企业级代码质量**,适合构建大型爬虫项目
- ✅ **完美架构设计**,高扩展性,源码学习价值更大

#### 🎁 额外功能
- ✅ **自媒体视频下载器桌面端**(适合学习全栈开发)
- ✅ **多平台首页信息流推荐**(HomeFeed)
- ✅ **AI Agent Skill 支持**([OpenClaw](https://openclaw.ai/) 🦞 / Claude Code / Cursor 一键安装,让 Agent 自动爬取数据)
- [ ] **基于评论分析AI Agent正在开发中 🚀🚀**

点击查看:[MediaCrawlerPro 项目主页](https://github.com/MediaCrawlerPro) 更多介绍



## 🚀 快速开始

> 💡 **如果这个项目对您有帮助,请给个 ⭐ Star 支持一下!**

## 📋 前置依赖

### 🚀 uv 安装(推荐)

在进行下一步操作之前,请确保电脑上已经安装了 uv:

- **安装地址**:[uv 官方安装指南](https://docs.astral.sh/uv/getting-started/installation)
- **验证安装**:终端输入命令 `uv --version`,如果正常显示版本号,证明已经安装成功
- **推荐理由**:uv 是目前最强的 Python 包管理工具,速度快、依赖解析准确

### 🟢 Node.js 安装

项目依赖 Node.js,请前往官网下载安装:

- **下载地址**:https://nodejs.org/en/download/
- **版本要求**:>= 16.0.0

### 📦 Python 包安装

```shell
# 进入项目目录
cd MediaCrawler

# 使用 uv sync 命令来保证 python 版本和相关依赖包的一致性
uv sync
```

### 🌐 浏览器驱动安装

```shell
# 安装浏览器驱动
uv run playwright install
```

## 🚀 运行爬虫程序

```shell
# 在 config/base_config.py 查看配置项目功能,写的有中文注释

# 从配置文件中读取关键词搜索相关的帖子并爬取帖子信息与评论
uv run main.py --platform xhs --lt qrcode --type search

# 从配置文件中读取指定的帖子ID列表获取指定帖子的信息与评论信息
uv run main.py --platform xhs --lt qrcode --type detail

# 打开对应APP扫二维码登录

# 其他平台爬虫使用示例,执行下面的命令查看
uv run main.py --help
```

<details>
<summary>🖥️ <strong>WebUI 可视化操作界面</strong></summary>

MediaCrawler 提供了基于 Web 的可视化操作界面,无需命令行也能轻松使用爬虫功能。

#### 启动 WebUI 服务

```shell
# 启动 API 服务器(默认端口 8080)
uv run uvicorn api.main:app --port 8080 --reload

# 或者使用模块方式启动
uv run python -m api.main
```

启动成功后,访问 `http://localhost:8080` 即可打开 WebUI 界面。

#### WebUI 功能特性

- 可视化配置爬虫参数(平台、登录方式、爬取类型等)
- 实时查看爬虫运行状态和日志
- 数据预览和导出

#### 界面预览

<img src="docs/static/images/img_8.png" alt="WebUI 界面预览">

</details>

<details>
<summary>🔗 <strong>使用 Python 原生 venv 管理环境(不推荐)</strong></summary>

#### 创建并激活 Python 虚拟环境

> 如果是爬取抖音和知乎,需要提前安装 nodejs 环境,版本大于等于:`16` 即可

```shell
# 进入项目根目录
cd MediaCrawler

# 创建虚拟环境
# 我的 python 版本是:3.11 requirements.txt 中的库是基于这个版本的
# 如果是其他 python 版本,可能 requirements.txt 中的库不兼容,需自行解决
python -m venv venv

# macOS & Linux 激活虚拟环境
source venv/bin/activate

# Windows 激活虚拟环境
venv\Scripts\activate
```

#### 安装依赖库

```shell
pip install -r requirements.txt
```

#### 安装 playwright 浏览器驱动

```shell
playwright install
```

#### 运行爬虫程序(原生环境)

```shell
# 项目默认是没有开启评论爬取模式,如需评论请在 config/base_config.py 中的 ENABLE_GET_COMMENTS 变量修改
# 一些其他支持项,也可以在 config/base_config.py 查看功能,写的有中文注释

# 从配置文件中读取关键词搜索相关的帖子并爬取帖子信息与评论
python main.py --platform xhs --lt qrcode --type search

# 从配置文件中读取指定的帖子ID列表获取指定帖子的信息与评论信息
python main.py --platform xhs --lt qrcode --type detail

# 打开对应APP扫二维码登录

# 其他平台爬虫使用示例,执行下面的命令查看
python main.py --help
```

</details>


## 💾 数据保存

MediaCrawler 支持多种数据存储方式,包括 CSV、JSON、JSONL、Excel、SQLite 和 MySQL 数据库。

📖 **详细使用说明请查看:[数据存储指南](docs/data_storage_guide.md)**


[🚀 MediaCrawlerPro 重磅发布 🚀!更多的功能,更好的架构设计!开源不易,欢迎订阅支持!](https://github.com/MediaCrawlerPro)


## 💬 交流群组
- **微信交流群**:[点击加入](https://nanmicoder.github.io/MediaCrawler/%E5%BE%AE%E4%BF%A1%E4%BA%A4%E6%B5%81%E7%BE%A4.html)
- **B站账号**:[关注我](https://space.bilibili.com/434377496),分享AI与爬虫技术知识


## 💰 赞助商展示

<a href="https://tikhub.io/?utm_source=github.com/NanmiCoder/MediaCrawler&utm_medium=marketing_social&utm_campaign=retargeting&utm_content=carousel_ad">
<img width="500" src="docs/static/images/tikhub_banner_zh.png">
<br>
TikHub.io 提供 900+ 高稳定性数据接口,覆盖 TK、DY、XHS、Y2B、Ins、X 等 14+ 海内外主流平台,支持用户、内容、商品、评论等多维度公开数据 API,并配套 4000 万+ 已清洗结构化数据集,使用邀请码 <code>cfzyejV9</code> 注册并充值,即可额外获得 $2 赠送额度。
</a>

---

## 🤝 成为赞助者

成为赞助者,可以将您的产品展示在这里,每天获得大量曝光!

**联系方式**:
- 微信:`relakkes`
- 邮箱:`relakkes@gmail.com`
---

## 📚 其他
- **常见问题**:[MediaCrawler 完整文档](https://nanmicoder.github.io/MediaCrawler/)
- **爬虫入门教程**:[CrawlerTutorial 免费教程](https://github.com/NanmiCoder/CrawlerTutorial)
- **新闻爬虫开源项目**:[NewsCrawlerCollection](https://github.com/NanmiCoder/NewsCrawlerCollection)


## ⭐ Star 趋势图

如果这个项目对您有帮助,请给个 ⭐ Star 支持一下,让更多的人看到 MediaCrawler!

[![Star History Chart](https://api.star-history.com/svg?repos=NanmiCoder/MediaCrawler&type=Date)](https://star-history.com/#NanmiCoder/MediaCrawler&Date)


## 📚 参考

- **小红书签名仓库**:[Cloxl 的 xhs 签名仓库](https://github.com/Cloxl/xhshow)
- **小红书客户端**:[ReaJason 的 xhs 仓库](https://github.com/ReaJason/xhs)
- **短信转发**:[SmsForwarder 参考仓库](https://github.com/pppscn/SmsForwarder)
- **内网穿透工具**:[ngrok 官方文档](https://ngrok.com/docs/)


# 免责声明
<div id="disclaimer"> 

## 1. 项目目的与性质
本项目(以下简称“本项目”)是作为一个技术研究与学习工具而创建的,旨在探索和学习网络数据采集技术。本项目专注于自媒体平台的数据爬取技术研究,旨在提供给学习者和研究者作为技术交流之用。

## 2. 法律合规性声明
本项目开发者(以下简称“开发者”)郑重提醒用户在下载、安装和使用本项目时,严格遵守中华人民共和国相关法律法规,包括但不限于《中华人民共和国网络安全法》、《中华人民共和国反间谍法》等所有适用的国家法律和政策。用户应自行承担一切因使用本项目而可能引起的法律责任。

## 3. 使用目的限制
本项目严禁用于任何非法目的或非学习、非研究的商业行为。本项目不得用于任何形式的非法侵入他人计算机系统,不得用于任何侵犯他人知识产权或其他合法权益的行为。用户应保证其使用本项目的目的纯属个人学习和技术研究,不得用于任何形式的非法活动。

## 4. 免责声明
开发者已尽最大努力确保本项目的正当性及安全性,但不对用户使用本项目可能引起的任何形式的直接或间接损失承担责任。包括但不限于由于使用本项目而导致的任何数据丢失、设备损坏、法律诉讼等。

## 5. 知识产权声明
本项目的知识产权归开发者所有。本项目受到著作权法和国际著作权条约以及其他知识产权法律和条约的保护。用户在遵守本声明及相关法律法规的前提下,可以下载和使用本项目。

## 6. 最终解释权
关于本项目的最终解释权归开发者所有。开发者保留随时更改或更新本免责声明的权利,恕不另行通知。
</div>


================================================
FILE: README_en.md
================================================
# 🔥 MediaCrawler - Social Media Platform Crawler 🕷️

<div align="center">

<a href="https://trendshift.io/repositories/8291" target="_blank">
  <img src="https://trendshift.io/api/badge/repositories/8291" alt="NanmiCoder%2FMediaCrawler | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/>
</a>

[![GitHub Stars](https://img.shields.io/github/stars/NanmiCoder/MediaCrawler?style=social)](https://github.com/NanmiCoder/MediaCrawler/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/NanmiCoder/MediaCrawler?style=social)](https://github.com/NanmiCoder/MediaCrawler/network/members)
[![GitHub Issues](https://img.shields.io/github/issues/NanmiCoder/MediaCrawler)](https://github.com/NanmiCoder/MediaCrawler/issues)
[![GitHub Pull Requests](https://img.shields.io/github/issues-pr/NanmiCoder/MediaCrawler)](https://github.com/NanmiCoder/MediaCrawler/pulls)
[![License](https://img.shields.io/github/license/NanmiCoder/MediaCrawler)](https://github.com/NanmiCoder/MediaCrawler/blob/main/LICENSE)
[![中文](https://img.shields.io/badge/🇨🇳_中文-Available-blue)](README.md)
[![English](https://img.shields.io/badge/🇺🇸_English-Current-green)](README_en.md)
[![Español](https://img.shields.io/badge/🇪🇸_Español-Available-green)](README_es.md)

</div>

> **Disclaimer:**
> 
> Please use this repository for learning purposes only ⚠️⚠️⚠️⚠️, [Web scraping illegal cases](https://github.com/HiddenStrawberry/Crawler_Illegal_Cases_In_China)  <br>
>
>All content in this repository is for learning and reference purposes only, and commercial use is prohibited. No person or organization may use the content of this repository for illegal purposes or infringe upon the legitimate rights and interests of others. The web scraping technology involved in this repository is only for learning and research, and may not be used for large-scale crawling of other platforms or other illegal activities. This repository assumes no legal responsibility for any legal liability arising from the use of the content of this repository. By using the content of this repository, you agree to all terms and conditions of this disclaimer.
>
> Click to view a more detailed disclaimer. [Click to jump](#disclaimer)

## 📖 Project Introduction

A powerful **multi-platform social media data collection tool** that supports crawling public information from mainstream platforms including Xiaohongshu, Douyin, Kuaishou, Bilibili, Weibo, Tieba, Zhihu, and more.

### 🔧 Technical Principles

- **Core Technology**: Based on [Playwright](https://playwright.dev/) browser automation framework for login and maintaining login state
- **No JS Reverse Engineering Required**: Uses browser context environment with preserved login state to obtain signature parameters through JS expressions
- **Advantages**: No need to reverse complex encryption algorithms, significantly lowering the technical barrier

## ✨ Features
| Platform | Keyword Search | Specific Post ID Crawling | Secondary Comments | Specific Creator Homepage | Login State Cache | IP Proxy Pool | Generate Comment Word Cloud |
| ------ | ---------- | -------------- | -------- | -------------- | ---------- | -------- | -------------- |
| Xiaohongshu | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| Douyin   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| Kuaishou   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| Bilibili   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| Weibo   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| Tieba   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| Zhihu   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |


<strong>MediaCrawlerPro Major Release! Open source is not easy, welcome to subscribe and support!</strong>

> Focus on learning mature project architectural design, not just crawling technology. The code design philosophy of the Pro version is equally worth in-depth study!

[MediaCrawlerPro](https://github.com/MediaCrawlerPro) core advantages over the open-source version:

#### 🎯 Core Feature Upgrades
- ✅ **Content Deconstruction Agent** (New feature)
- ✅ **Resume crawling functionality** (Key feature)
- ✅ **Multi-account + IP proxy pool support** (Key feature)
- ✅ **Remove Playwright dependency**, easier to use
- ✅ **Complete Linux environment support**

#### 🏗️ Architectural Design Optimization
- ✅ **Code refactoring optimization**, more readable and maintainable (decoupled JS signature logic)
- ✅ **Enterprise-level code quality**, suitable for building large-scale crawler projects
- ✅ **Perfect architectural design**, high scalability, greater source code learning value

#### 🎁 Additional Features
- ✅ **Social media video downloader desktop app** (suitable for learning full-stack development)
- ✅ **Multi-platform homepage feed recommendations** (HomeFeed)
- [ ] **AI Agent based on comment analysis is under development 🚀🚀**

Click to view: [MediaCrawlerPro Project Homepage](https://github.com/MediaCrawlerPro) for more information

## 🚀 Quick Start

> 💡 **Open source is not easy, if this project helps you, please give a ⭐ Star to support!**

## 📋 Prerequisites

### 🚀 uv Installation (Recommended)

Before proceeding with the next steps, please ensure that uv is installed on your computer:

- **Installation Guide**: [uv Official Installation Guide](https://docs.astral.sh/uv/getting-started/installation)
- **Verify Installation**: Enter the command `uv --version` in the terminal. If the version number is displayed normally, the installation was successful
- **Recommendation Reason**: uv is currently the most powerful Python package management tool, with fast speed and accurate dependency resolution

### 🟢 Node.js Installation

The project depends on Node.js, please download and install from the official website:

- **Download Link**: https://nodejs.org/en/download/
- **Version Requirement**: >= 16.0.0

### 📦 Python Package Installation

```shell
# Enter project directory
cd MediaCrawler

# Use uv sync command to ensure consistency of python version and related dependency packages
uv sync
```

### 🌐 Browser Driver Installation

```shell
# Install browser driver
uv run playwright install
```

> **💡 Tip**: MediaCrawler now supports using playwright to connect to your local Chrome browser, solving some issues caused by Webdriver.
>
> Currently, `xhs` and `dy` are available using CDP mode to connect to local browsers. If needed, check the configuration items in `config/base_config.py`.

## 🚀 Run Crawler Program

```shell
# The project does not enable comment crawling mode by default. If you need comments, please modify the ENABLE_GET_COMMENTS variable in config/base_config.py
# Other supported options can also be viewed in config/base_config.py with Chinese comments

# Read keywords from configuration file to search related posts and crawl post information and comments
uv run main.py --platform xhs --lt qrcode --type search

# Read specified post ID list from configuration file to get information and comment information of specified posts
uv run main.py --platform xhs --lt qrcode --type detail

# Open corresponding APP to scan QR code for login

# For other platform crawler usage examples, execute the following command to view
uv run main.py --help
```

## WebUI Support

<details>
<summary>🖥️ <strong>WebUI Visual Operation Interface</strong></summary>

MediaCrawler provides a web-based visual operation interface, allowing you to easily use crawler features without command line.

#### Start WebUI Service

```shell
# Start API server (default port 8080)
uv run uvicorn api.main:app --port 8080 --reload

# Or start using module method
uv run python -m api.main
```

After successful startup, visit `http://localhost:8080` to open the WebUI interface.

#### WebUI Features

- Visualize crawler parameter configuration (platform, login method, crawling type, etc.)
- Real-time view of crawler running status and logs
- Data preview and export

#### Interface Preview

<img src="docs/static/images/img_8.png" alt="WebUI Interface Preview">

</details>

<details>
<summary>🔗 <strong>Using Python native venv environment management (Not recommended)</strong></summary>

#### Create and activate Python virtual environment

> If crawling Douyin and Zhihu, you need to install nodejs environment in advance, version greater than or equal to: `16`

```shell
# Enter project root directory
cd MediaCrawler

# Create virtual environment
# My python version is: 3.9.6, the libraries in requirements.txt are based on this version
# If using other python versions, the libraries in requirements.txt may not be compatible, please resolve on your own
python -m venv venv

# macOS & Linux activate virtual environment
source venv/bin/activate

# Windows activate virtual environment
venv\Scripts\activate
```

#### Install dependency libraries

```shell
pip install -r requirements.txt
```

#### Install playwright browser driver

```shell
playwright install
```

#### Run crawler program (native environment)

```shell
# The project does not enable comment crawling mode by default. If you need comments, please modify the ENABLE_GET_COMMENTS variable in config/base_config.py
# Other supported options can also be viewed in config/base_config.py with Chinese comments

# Read keywords from configuration file to search related posts and crawl post information and comments
python main.py --platform xhs --lt qrcode --type search

# Read specified post ID list from configuration file to get information and comment information of specified posts
python main.py --platform xhs --lt qrcode --type detail

# Open corresponding APP to scan QR code for login

# For other platform crawler usage examples, execute the following command to view
python main.py --help
```

</details>


## 💾 Data Storage

MediaCrawler supports multiple data storage methods, including CSV, JSON, JSONL, Excel, SQLite, and MySQL databases.

📖 **For detailed usage instructions, please see: [Data Storage Guide](docs/data_storage_guide.md)**

---

[🚀 MediaCrawlerPro Major Release 🚀! More features, better architectural design!](https://github.com/MediaCrawlerPro)

### 💬 Discussion Groups
- **WeChat Discussion Group**: [Click to join](https://nanmicoder.github.io/MediaCrawler/%E5%BE%AE%E4%BF%A1%E4%BA%A4%E6%B5%81%E7%BE%A4.html)
- **Bilibili Account**: [Follow me](https://space.bilibili.com/434377496), sharing AI and crawler technology knowledge


### 💰 Sponsor Display

<a href="https://tikhub.io/?utm_source=github.com/NanmiCoder/MediaCrawler&utm_medium=marketing_social&utm_campaign=retargeting&utm_content=carousel_ad">
<img width="500" src="docs/static/images/tikhub_banner_zh.png">
<br>
TikHub.io provides 900+ highly stable data interfaces, covering 14+ mainstream domestic and international platforms including TK, DY, XHS, Y2B, Ins, X, etc. Supports multi-dimensional public data APIs for users, content, products, comments, etc., with 40M+ cleaned structured datasets. Use invitation code <code>cfzyejV9</code> to register and recharge, and get an additional $2 bonus.
</a>

---

### 🤝 Become a Sponsor

Become a sponsor and showcase your product here, getting massive exposure daily!

**Contact Information**:
- WeChat: `relakkes`
- Email: `relakkes@gmail.com`
---

### 📚 Other
- **FAQ**: [MediaCrawler Complete Documentation](https://nanmicoder.github.io/MediaCrawler/)
- **Crawler Beginner Tutorial**: [CrawlerTutorial Free Tutorial](https://github.com/NanmiCoder/CrawlerTutorial)
- **News Crawler Open Source Project**: [NewsCrawlerCollection](https://github.com/NanmiCoder/NewsCrawlerCollection)


## ⭐ Star Trend Chart

If this project helps you, please give a ⭐ Star to support and let more people see MediaCrawler!

[![Star History Chart](https://api.star-history.com/svg?repos=NanmiCoder/MediaCrawler&type=Date)](https://star-history.com/#NanmiCoder/MediaCrawler&Date)


## 📚 References

- **Xiaohongshu Signature Repository**: [Cloxl's xhs signature repository](https://github.com/Cloxl/xhshow)
- **Xiaohongshu Client**: [ReaJason's xhs repository](https://github.com/ReaJason/xhs)
- **SMS Forwarding**: [SmsForwarder reference repository](https://github.com/pppscn/SmsForwarder)
- **Intranet Penetration Tool**: [ngrok official documentation](https://ngrok.com/docs/)


# Disclaimer
<div id="disclaimer">

## 1. Project Purpose and Nature
This project (hereinafter referred to as "this project") was created as a technical research and learning tool, aimed at exploring and learning network data collection technologies. This project focuses on research of data crawling technologies for social media platforms, intended to provide learners and researchers with technical exchange purposes.

## 2. Legal Compliance Statement
The project developer (hereinafter referred to as "developer") solemnly reminds users to strictly comply with relevant laws and regulations of the People's Republic of China when downloading, installing and using this project, including but not limited to the "Cybersecurity Law of the People's Republic of China", "Counter-Espionage Law of the People's Republic of China" and all applicable national laws and policies. Users shall bear all legal responsibilities that may arise from using this project.

## 3. Usage Purpose Restrictions
This project is strictly prohibited from being used for any illegal purposes or non-learning, non-research commercial activities. This project may not be used for any form of illegal intrusion into other people's computer systems, nor may it be used for any activities that infringe upon others' intellectual property rights or other legitimate rights and interests. Users should ensure that their use of this project is purely for personal learning and technical research, and may not be used for any form of illegal activities.

## 4. Disclaimer
The developer has made every effort to ensure the legitimacy and security of this project, but assumes no responsibility for any form of direct or indirect losses that may arise from users' use of this project. Including but not limited to any data loss, equipment damage, legal litigation, etc. caused by using this project.

## 5. Intellectual Property Statement
The intellectual property rights of this project belong to the developer. This project is protected by copyright law and international copyright treaties as well as other intellectual property laws and treaties. Users may download and use this project under the premise of complying with this statement and relevant laws and regulations.

## 6. Final Interpretation Rights
The developer has the final interpretation rights regarding this project. The developer reserves the right to change or update this disclaimer at any time without further notice.
</div>


## 🙏 Acknowledgments

### JetBrains Open Source License Support

Thanks to JetBrains for providing free open source license support for this project!

<a href="https://www.jetbrains.com/?from=MediaCrawler">
    <img src="https://www.jetbrains.com/company/brand/img/jetbrains_logo.png" width="100" alt="JetBrains" />
</a>


================================================
FILE: README_es.md
================================================
# 🔥 MediaCrawler - Rastreador de Plataformas de Redes Sociales 🕷️

<div align="center">

<a href="https://trendshift.io/repositories/8291" target="_blank">
  <img src="https://trendshift.io/api/badge/repositories/8291" alt="NanmiCoder%2FMediaCrawler | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/>
</a>

[![GitHub Stars](https://img.shields.io/github/stars/NanmiCoder/MediaCrawler?style=social)](https://github.com/NanmiCoder/MediaCrawler/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/NanmiCoder/MediaCrawler?style=social)](https://github.com/NanmiCoder/MediaCrawler/network/members)
[![GitHub Issues](https://img.shields.io/github/issues/NanmiCoder/MediaCrawler)](https://github.com/NanmiCoder/MediaCrawler/issues)
[![GitHub Pull Requests](https://img.shields.io/github/issues-pr/NanmiCoder/MediaCrawler)](https://github.com/NanmiCoder/MediaCrawler/pulls)
[![License](https://img.shields.io/github/license/NanmiCoder/MediaCrawler)](https://github.com/NanmiCoder/MediaCrawler/blob/main/LICENSE)
[![中文](https://img.shields.io/badge/🇨🇳_中文-Available-blue)](README.md)
[![English](https://img.shields.io/badge/🇺🇸_English-Available-green)](README_en.md)
[![Español](https://img.shields.io/badge/🇪🇸_Español-Current-green)](README_es.md)

</div>

> **Descargo de responsabilidad:**
> 
> Por favor, utilice este repositorio únicamente con fines de aprendizaje ⚠️⚠️⚠️⚠️, [Casos ilegales de web scraping](https://github.com/HiddenStrawberry/Crawler_Illegal_Cases_In_China)  <br>
>
>Todo el contenido de este repositorio es únicamente para fines de aprendizaje y referencia, y está prohibido el uso comercial. Ninguna persona u organización puede usar el contenido de este repositorio para propósitos ilegales o infringir los derechos e intereses legítimos de otros. La tecnología de web scraping involucrada en este repositorio es solo para aprendizaje e investigación, y no puede ser utilizada para rastreo a gran escala de otras plataformas u otras actividades ilegales. Este repositorio no asume ninguna responsabilidad legal por cualquier responsabilidad legal que surja del uso del contenido de este repositorio. Al usar el contenido de este repositorio, usted acepta todos los términos y condiciones de este descargo de responsabilidad.
>
> Haga clic para ver un descargo de responsabilidad más detallado. [Haga clic para saltar](#disclaimer)

## 📖 Introducción del Proyecto

Una poderosa **herramienta de recolección de datos de redes sociales multiplataforma** que soporta el rastreo de información pública de plataformas principales incluyendo Xiaohongshu, Douyin, Kuaishou, Bilibili, Weibo, Tieba, Zhihu, y más.

### 🔧 Principios Técnicos

- **Tecnología Central**: Basado en el framework de automatización de navegador [Playwright](https://playwright.dev/) para login y mantenimiento del estado de login
- **No Requiere Ingeniería Inversa de JS**: Utiliza el entorno de contexto del navegador con estado de login preservado para obtener parámetros de firma a través de expresiones JS
- **Ventajas**: No necesita hacer ingeniería inversa de algoritmos de encriptación complejos, reduciendo significativamente la barrera técnica

## ✨ Características
| Plataforma | Búsqueda por Palabras Clave | Rastreo de ID de Publicación Específica | Comentarios Secundarios | Página de Inicio de Creador Específico | Caché de Estado de Login | Pool de Proxy IP | Generar Nube de Palabras de Comentarios |
| ------ | ---------- | -------------- | -------- | -------------- | ---------- | -------- | -------------- |
| Xiaohongshu | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| Douyin   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| Kuaishou   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| Bilibili   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| Weibo   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| Tieba   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |
| Zhihu   | ✅          | ✅              | ✅        | ✅              | ✅          | ✅        | ✅              |


<strong>¡Lanzamiento Mayor de MediaCrawlerPro! ¡El código abierto no es fácil, bienvenido a suscribirse y apoyar!</strong>

> Enfócate en aprender el diseño arquitectónico de proyectos maduros, no solo tecnología de rastreo. ¡La filosofía de diseño de código de la versión Pro también vale la pena estudiar en profundidad!

[MediaCrawlerPro](https://github.com/MediaCrawlerPro) ventajas principales sobre la versión de código abierto:

#### 🎯 Actualizaciones de Características Principales
- ✅ **Agente de Deconstrucción de Contenido** (Nueva función)
- ✅ **Funcionalidad de reanudación de rastreo** (Característica clave)
- ✅ **Soporte de múltiples cuentas + pool de proxy IP** (Característica clave)
- ✅ **Eliminar dependencia de Playwright**, más fácil de usar
- ✅ **Soporte completo de entorno Linux**

#### 🏗️ Optimización de Diseño Arquitectónico
- ✅ **Optimización de refactorización de código**, más legible y mantenible (lógica de firma JS desacoplada)
- ✅ **Calidad de código de nivel empresarial**, adecuado para construir proyectos de rastreo a gran escala
- ✅ **Diseño arquitectónico perfecto**, alta escalabilidad, mayor valor de aprendizaje del código fuente

#### 🎁 Características Adicionales
- ✅ **Aplicación de escritorio descargadora de videos de redes sociales** (adecuada para aprender desarrollo full-stack)
- ✅ **Recomendaciones de feed de página de inicio multiplataforma** (HomeFeed)
- [ ] **Agente AI basado en análisis de comentarios está en desarrollo 🚀🚀**

Haga clic para ver: [Página de Inicio del Proyecto MediaCrawlerPro](https://github.com/MediaCrawlerPro) para más información

## 🚀 Inicio Rápido

> 💡 **¡El código abierto no es fácil, si este proyecto te ayuda, por favor da una ⭐ Estrella para apoyar!**

## 📋 Prerrequisitos

### 🚀 Instalación de uv (Recomendado)

Antes de proceder con los siguientes pasos, por favor asegúrese de que uv esté instalado en su computadora:

- **Guía de Instalación**: [Guía Oficial de Instalación de uv](https://docs.astral.sh/uv/getting-started/installation)
- **Verificar Instalación**: Ingrese el comando `uv --version` en la terminal. Si el número de versión se muestra normalmente, la instalación fue exitosa
- **Razón de Recomendación**: uv es actualmente la herramienta de gestión de paquetes Python más poderosa, con velocidad rápida y resolución de dependencias precisa

### 🟢 Instalación de Node.js

El proyecto depende de Node.js, por favor descargue e instale desde el sitio web oficial:

- **Enlace de Descarga**: https://nodejs.org/en/download/
- **Requisito de Versión**: >= 16.0.0

### 📦 Instalación de Paquetes Python

```shell
# Entrar al directorio del proyecto
cd MediaCrawler

# Usar el comando uv sync para asegurar la consistencia de la versión de python y paquetes de dependencias relacionados
uv sync
```

### 🌐 Instalación de Controlador de Navegador

```shell
# Instalar controlador de navegador
uv run playwright install
```

> **💡 Consejo**: MediaCrawler ahora soporta usar playwright para conectarse a su navegador Chrome local, resolviendo algunos problemas causados por Webdriver.
>
> Actualmente, `xhs` y `dy` están disponibles usando el modo CDP para conectarse a navegadores locales. Si es necesario, verifique los elementos de configuración en `config/base_config.py`.

## 🚀 Ejecutar Programa Rastreador

```shell
# El proyecto no habilita el modo de rastreo de comentarios por defecto. Si necesita comentarios, por favor modifique la variable ENABLE_GET_COMMENTS en config/base_config.py
# Otras opciones soportadas también pueden verse en config/base_config.py con comentarios en chino

# Leer palabras clave del archivo de configuración para buscar publicaciones relacionadas y rastrear información de publicaciones y comentarios
uv run main.py --platform xhs --lt qrcode --type search

# Leer lista de ID de publicaciones específicas del archivo de configuración para obtener información e información de comentarios de publicaciones específicas
uv run main.py --platform xhs --lt qrcode --type detail

# Abrir la APP correspondiente para escanear código QR para login

# Para ejemplos de uso de rastreador de otras plataformas, ejecute el siguiente comando para ver
uv run main.py --help
```

## Soporte WebUI

<details>
<summary>🖥️ <strong>Interfaz de Operación Visual WebUI</strong></summary>

MediaCrawler proporciona una interfaz de operación visual basada en web, permitiéndole usar fácilmente las funciones del rastreador sin línea de comandos.

#### Iniciar Servicio WebUI

```shell
# Iniciar servidor API (puerto predeterminado 8080)
uv run uvicorn api.main:app --port 8080 --reload

# O iniciar usando método de módulo
uv run python -m api.main
```

Después de iniciar exitosamente, visite `http://localhost:8080` para abrir la interfaz WebUI.

#### Características de WebUI

- Configuración visual de parámetros del rastreador (plataforma, método de login, tipo de rastreo, etc.)
- Vista en tiempo real del estado de ejecución del rastreador y logs
- Vista previa y exportación de datos

#### Vista Previa de la Interfaz

<img src="docs/static/images/img_8.png" alt="Vista Previa de Interfaz WebUI">

</details>

<details>
<summary>🔗 <strong>Usando gestión de entorno venv nativo de Python (No recomendado)</strong></summary>

#### Crear y activar entorno virtual de Python

> Si rastrea Douyin y Zhihu, necesita instalar el entorno nodejs con anticipación, versión mayor o igual a: `16`

```shell
# Entrar al directorio raíz del proyecto
cd MediaCrawler

# Crear entorno virtual
# Mi versión de python es: 3.9.6, las librerías en requirements.txt están basadas en esta versión
# Si usa otras versiones de python, las librerías en requirements.txt pueden no ser compatibles, por favor resuelva por su cuenta
python -m venv venv

# macOS & Linux activar entorno virtual
source venv/bin/activate

# Windows activar entorno virtual
venv\Scripts\activate
```

#### Instalar librerías de dependencias

```shell
pip install -r requirements.txt
```

#### Instalar controlador de navegador playwright

```shell
playwright install
```

#### Ejecutar programa rastreador (entorno nativo)

```shell
# El proyecto no habilita el modo de rastreo de comentarios por defecto. Si necesita comentarios, por favor modifique la variable ENABLE_GET_COMMENTS en config/base_config.py
# Otras opciones soportadas también pueden verse en config/base_config.py con comentarios en chino

# Leer palabras clave del archivo de configuración para buscar publicaciones relacionadas y rastrear información de publicaciones y comentarios
python main.py --platform xhs --lt qrcode --type search

# Leer lista de ID de publicaciones específicas del archivo de configuración para obtener información e información de comentarios de publicaciones específicas
python main.py --platform xhs --lt qrcode --type detail

# Abrir la APP correspondiente para escanear código QR para login

# Para ejemplos de uso de rastreador de otras plataformas, ejecute el siguiente comando para ver
python main.py --help
```

</details>


## 💾 Almacenamiento de Datos

MediaCrawler soporta múltiples métodos de almacenamiento de datos, incluyendo CSV, JSON, JSONL, Excel, SQLite y bases de datos MySQL.

📖 **Para instrucciones de uso detalladas, por favor vea: [Guía de Almacenamiento de Datos](docs/data_storage_guide.md)**


[🚀 ¡Lanzamiento Mayor de MediaCrawlerPro 🚀! ¡Más características, mejor diseño arquitectónico!](https://github.com/MediaCrawlerPro)


### 💬 Grupos de Discusión
- **Grupo de Discusión WeChat**: [Haga clic para unirse](https://nanmicoder.github.io/MediaCrawler/%E5%BE%AE%E4%BF%A1%E4%BA%A4%E6%B5%81%E7%BE%A4.html)
- **Cuenta de Bilibili**: [Sígueme](https://space.bilibili.com/434377496), compartiendo conocimientos de tecnología de IA y rastreo


### 💰 Exhibición de Patrocinadores

<a href="https://tikhub.io/?utm_source=github.com/NanmiCoder/MediaCrawler&utm_medium=marketing_social&utm_campaign=retargeting&utm_content=carousel_ad">
<img width="500" src="docs/static/images/tikhub_banner_zh.png">
<br>
TikHub.io proporciona 900+ interfaces de datos altamente estables, cubriendo 14+ plataformas principales nacionales e internacionales incluyendo TK, DY, XHS, Y2B, Ins, X, etc. Soporta APIs de datos públicos multidimensionales para usuarios, contenido, productos, comentarios, etc., con 40M+ conjuntos de datos estructurados limpios. Use el código de invitación <code>cfzyejV9</code> para registrarse y recargar, y obtenga $2 adicionales de bonificación.
</a>

---

### 🤝 Conviértase en Patrocinador

¡Conviértase en patrocinador y muestre su producto aquí, obteniendo exposición masiva diariamente!

**Información de Contacto**:
- WeChat: `relakkes`
- Email: `relakkes@gmail.com`
---

### 📚 Otros
- **Preguntas Frecuentes**: [Documentación Completa de MediaCrawler](https://nanmicoder.github.io/MediaCrawler/)
- **Tutorial de Rastreador para Principiantes**: [Tutorial Gratuito CrawlerTutorial](https://github.com/NanmiCoder/CrawlerTutorial)
- **Proyecto de Código Abierto de Rastreador de Noticias**: [NewsCrawlerCollection](https://github.com/NanmiCoder/NewsCrawlerCollection)


## ⭐ Gráfico de Tendencia de Estrellas

¡Si este proyecto te ayuda, por favor da una ⭐ Estrella para apoyar y que más personas vean MediaCrawler!

[![Star History Chart](https://api.star-history.com/svg?repos=NanmiCoder/MediaCrawler&type=Date)](https://star-history.com/#NanmiCoder/MediaCrawler&Date)


## 📚 Referencias

- **Repositorio de Firma Xiaohongshu**: [Repositorio de firma xhs de Cloxl](https://github.com/Cloxl/xhshow)
- **Cliente Xiaohongshu**: [Repositorio xhs de ReaJason](https://github.com/ReaJason/xhs)
- **Reenvío de SMS**: [Repositorio de referencia SmsForwarder](https://github.com/pppscn/SmsForwarder)
- **Herramienta de Penetración de Intranet**: [Documentación oficial de ngrok](https://ngrok.com/docs/)


# Descargo de Responsabilidad
<div id="disclaimer">

## 1. Propósito y Naturaleza del Proyecto
Este proyecto (en adelante denominado "este proyecto") fue creado como una herramienta de investigación técnica y aprendizaje, con el objetivo de explorar y aprender tecnologías de recolección de datos de red. Este proyecto se enfoca en la investigación de tecnologías de rastreo de datos para plataformas de redes sociales, destinado a proporcionar a estudiantes e investigadores propósitos de intercambio técnico.

## 2. Declaración de Cumplimiento Legal
El desarrollador del proyecto (en adelante denominado "desarrollador") recuerda solemnemente a los usuarios que cumplan estrictamente con las leyes y regulaciones relevantes de la República Popular China al descargar, instalar y usar este proyecto, incluyendo pero no limitado a la "Ley de Ciberseguridad de la República Popular China", "Ley de Contraespionaje de la República Popular China" y todas las leyes y políticas nacionales aplicables. Los usuarios deberán asumir todas las responsabilidades legales que puedan surgir del uso de este proyecto.

## 3. Restricciones de Propósito de Uso
Este proyecto está estrictamente prohibido de ser utilizado para cualquier propósito ilegal o actividades comerciales que no sean de aprendizaje o investigación. Este proyecto no puede ser utilizado para ninguna forma de intrusión ilegal en sistemas informáticos de otras personas, ni puede ser utilizado para cualquier actividad que infrinja los derechos de propiedad intelectual de otros u otros derechos e intereses legítimos. Los usuarios deben asegurar que su uso de este proyecto sea puramente para aprendizaje personal e investigación técnica, y no puede ser utilizado para ninguna forma de actividades ilegales.

## 4. Descargo de Responsabilidad
El desarrollador ha hecho todos los esfuerzos para asegurar la legitimidad y seguridad de este proyecto, pero no asume responsabilidad por ninguna forma de pérdidas directas o indirectas que puedan surgir del uso de este proyecto por parte de los usuarios. Incluyendo pero no limitado a cualquier pérdida de datos, daño de equipos, litigios legales, etc. causados por el uso de este proyecto.

## 5. Declaración de Propiedad Intelectual
Los derechos de propiedad intelectual de este proyecto pertenecen al desarrollador. Este proyecto está protegido por la ley de derechos de autor y tratados internacionales de derechos de autor, así como otras leyes y tratados de propiedad intelectual. Los usuarios pueden descargar y usar este proyecto bajo la premisa de cumplir con esta declaración y las leyes y regulaciones relevantes.

## 6. Derechos de Interpretación Final
El desarrollador tiene los derechos de interpretación final con respecto a este proyecto. El desarrollador se reserva el derecho de cambiar o actualizar este descargo de responsabilidad en cualquier momento sin previo aviso.
</div>


## 🙏 Agradecimientos

### Soporte de Licencia de Código Abierto de JetBrains

¡Gracias a JetBrains por proporcionar soporte de licencia de código abierto gratuito para este proyecto!

<a href="https://www.jetbrains.com/?from=MediaCrawler">
    <img src="https://www.jetbrains.com/company/brand/img/jetbrains_logo.png" width="100" alt="JetBrains" />
</a>


================================================
FILE: api/__init__.py
================================================
# -*- coding: utf-8 -*-
# Copyright (c) 2025 relakkes@gmail.com
#
# This file is part of MediaCrawler project.
# Repository: https://github.com/NanmiCoder/MediaCrawler/blob/main/api/__init__.py
# GitHub: https://github.com/NanmiCoder
# Licensed under NON-COMMERCIAL LEARNING LICENSE 1.1
#
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
# 1. 不得用于任何商业用途。
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
# 3. 不得进行大规模爬取或对平台造成运营干扰。
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
# 5. 不得用于任何非法或不当的用途。
#
# 详细许可条款请参阅项目根目录下的LICENSE文件。
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。

# WebUI API Module for MediaCrawler


================================================
FILE: api/main.py
================================================
# -*- coding: utf-8 -*-
# Copyright (c) 2025 relakkes@gmail.com
#
# This file is part of MediaCrawler project.
# Repository: https://github.com/NanmiCoder/MediaCrawler/blob/main/api/main.py
# GitHub: https://github.com/NanmiCoder
# Licensed under NON-COMMERCIAL LEARNING LICENSE 1.1
#
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
# 1. 不得用于任何商业用途。
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
# 3. 不得进行大规模爬取或对平台造成运营干扰。
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
# 5. 不得用于任何非法或不当的用途。
#
# 详细许可条款请参阅项目根目录下的LICENSE文件。
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。

"""
MediaCrawler WebUI API Server
Start command: uvicorn api.main:app --port 8080 --reload
Or: python -m api.main
"""
import asyncio
import os
import subprocess
import uvicorn
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse

from .routers import crawler_router, data_router, websocket_router

app = FastAPI(
    title="MediaCrawler WebUI API",
    description="API for controlling MediaCrawler from WebUI",
    version="1.0.0"
)

# Get webui static files directory
WEBUI_DIR = os.path.join(os.path.dirname(__file__), "webui")

# CORS configuration - allow frontend dev server access
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "http://localhost:5173",  # Vite dev server
        "http://localhost:3000",  # Backup port
        "http://127.0.0.1:5173",
        "http://127.0.0.1:3000",
    ],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Register routers
app.include_router(crawler_router, prefix="/api")
app.include_router(data_router, prefix="/api")
app.include_router(websocket_router, prefix="/api")


@app.get("/")
async def serve_frontend():
    """Return frontend page"""
    index_path = os.path.join(WEBUI_DIR, "index.html")
    if os.path.exists(index_path):
        return FileResponse(index_path)
    return {
        "message": "MediaCrawler WebUI API",
        "version": "1.0.0",
        "docs": "/docs",
        "note": "WebUI not found, please build it first: cd webui && npm run build"
    }


@app.get("/api/health")
async def health_check():
    return {"status": "ok"}


@app.get("/api/env/check")
async def check_environment():
    """Check if MediaCrawler environment is configured correctly"""
    try:
        # Run uv run main.py --help command to check environment
        process = await asyncio.create_subprocess_exec(
            "uv", "run", "main.py", "--help",
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            cwd="."  # Project root directory
        )
        stdout, stderr = await asyncio.wait_for(
            process.communicate(),
            timeout=30.0  # 30 seconds timeout
        )

        if process.returncode == 0:
            return {
                "success": True,
                "message": "MediaCrawler environment configured correctly",
                "output": stdout.decode("utf-8", errors="ignore")[:500]  # Truncate to first 500 characters
            }
        else:
            error_msg = stderr.decode("utf-8", errors="ignore") or stdout.decode("utf-8", errors="ignore")
            return {
                "success": False,
                "message": "Environment check failed",
                "error": error_msg[:500]
            }
    except asyncio.TimeoutError:
        return {
            "success": False,
            "message": "Environment check timeout",
            "error": "Command execution exceeded 30 seconds"
        }
    except FileNotFoundError:
        return {
            "success": False,
            "message": "uv command not found",
            "error": "Please ensure uv is installed and configured in system PATH"
        }
    except Exception as e:
        return {
            "success": False,
            "message": "Environment check error",
            "error": str(e)
        }


@app.get("/api/config/platforms")
async def get_platforms():
    """Get list of supported platforms"""
    return {
        "platforms": [
            {"value": "xhs", "label": "Xiaohongshu", "icon": "book-open"},
            {"value": "dy", "label": "Douyin", "icon": "music"},
            {"value": "ks", "label": "Kuaishou", "icon": "video"},
            {"value": "bili", "label": "Bilibili", "icon": "tv"},
            {"value": "wb", "label": "Weibo", "icon": "message-circle"},
            {"value": "tieba", "label": "Baidu Tieba", "icon": "messages-square"},
            {"value": "zhihu", "label": "Zhihu", "icon": "help-circle"},
        ]
    }


@app.get("/api/config/options")
async def get_config_options():
    """Get all configuration options"""
    return {
        "login_types": [
            {"value": "qrcode", "label": "QR Code Login"},
            {"value": "cookie", "label": "Cookie Login"},
        ],
        "crawler_types": [
            {"value": "search", "label": "Search Mode"},
            {"value": "detail", "label": "Detail Mode"},
            {"value": "creator", "label": "Creator Mode"},
        ],
        "save_options": [
            {"value": "jsonl", "label": "JSONL File"},
            {"value": "json", "label": "JSON File"},
            {"value": "csv", "label": "CSV File"},
            {"value": "excel", "label": "Excel File"},
            {"value": "sqlite", "label": "SQLite Database"},
            {"value": "db", "label": "MySQL Database"},
            {"value": "mongodb", "label": "MongoDB Database"},
        ],
    }


# Mount static resources - must be placed after all routes
if os.path.exists(WEBUI_DIR):
    assets_dir = os.path.join(WEBUI_DIR, "assets")
    if os.path.exists(assets_dir):
        app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
    # Mount logos directory
    logos_dir = os.path.join(WEBUI_DIR, "logos")
    if os.path.exists(logos_dir):
        app.mount("/logos", StaticFiles(directory=logos_dir), name="logos")
    # Mount other static files (e.g., vite.svg)
    app.mount("/static", StaticFiles(directory=WEBUI_DIR), name="webui-static")


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8080)


================================================
FILE: api/routers/__init__.py
================================================
# -*- coding: utf-8 -*-
# Copyright (c) 2025 relakkes@gmail.com
#
# This file is part of MediaCrawler project.
# Repository: https://github.com/NanmiCoder/MediaCrawler/blob/main/api/routers/__init__.py
# GitHub: https://github.com/NanmiCoder
# Licensed under NON-COMMERCIAL LEARNING LICENSE 1.1
#
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
# 1. 不得用于任何商业用途。
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
# 3. 不得进行大规模爬取或对平台造成运营干扰。
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
# 5. 不得用于任何非法或不当的用途。
#
# 详细许可条款请参阅项目根目录下的LICENSE文件。
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。

from .crawler import router as crawler_router
from .data import router as data_router
from .websocket import router as websocket_router

__all__ = ["crawler_router", "data_router", "websocket_router"]


================================================
FILE: api/routers/crawler.py
================================================
# -*- coding: utf-8 -*-
# Copyright (c) 2025 relakkes@gmail.com
#
# This file is part of MediaCrawler project.
# Repository: https://github.com/NanmiCoder/MediaCrawler/blob/main/api/routers/crawler.py
# GitHub: https://github.com/NanmiCoder
# Licensed under NON-COMMERCIAL LEARNING LICENSE 1.1
#
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
# 1. 不得用于任何商业用途。
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
# 3. 不得进行大规模爬取或对平台造成运营干扰。
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
# 5. 不得用于任何非法或不当的用途。
#
# 详细许可条款请参阅项目根目录下的LICENSE文件。
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。

from fastapi import APIRouter, HTTPException

from ..schemas import CrawlerStartRequest, CrawlerStatusResponse
from ..services import crawler_manager

router = APIRouter(prefix="/crawler", tags=["crawler"])


@router.post("/start")
async def start_crawler(request: CrawlerStartRequest):
    """Start crawler task"""
    success = await crawler_manager.start(request)
    if not success:
        # Handle concurrent/duplicate requests: if process is already running, return 400 instead of 500
        if crawler_manager.process and crawler_manager.process.poll() is None:
            raise HTTPException(status_code=400, detail="Crawler is already running")
        raise HTTPException(status_code=500, detail="Failed to start crawler")

    return {"status": "ok", "message": "Crawler started successfully"}


@router.post("/stop")
async def stop_crawler():
    """Stop crawler task"""
    success = await crawler_manager.stop()
    if not success:
        # Handle concurrent/duplicate requests: if process already exited/doesn't exist, return 400 instead of 500
        if not crawler_manager.process or crawler_manager.process.poll() is not None:
            raise HTTPException(status_code=400, detail="No crawler is running")
        raise HTTPException(status_code=500, detail="Failed to stop crawler")

    return {"status": "ok", "message": "Crawler stopped successfully"}


@router.get("/status", response_model=CrawlerStatusResponse)
async def get_crawler_status():
    """Get crawler status"""
    return crawler_manager.get_status()


@router.get("/logs")
async def get_logs(limit: int = 100):
    """Get recent logs"""
    logs = crawler_manager.logs[-limit:] if limit > 0 else crawler_manager.logs
    return {"logs": [log.model_dump() for log in logs]}


================================================
FILE: api/routers/data.py
================================================
# -*- coding: utf-8 -*-
# Copyright (c) 2025 relakkes@gmail.com
#
# This file is part of MediaCrawler project.
# Repository: https://github.com/NanmiCoder/MediaCrawler/blob/main/api/routers/data.py
# GitHub: https://github.com/NanmiCoder
# Licensed under NON-COMMERCIAL LEARNING LICENSE 1.1
#
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
# 1. 不得用于任何商业用途。
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
# 3. 不得进行大规模爬取或对平台造成运营干扰。
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
# 5. 不得用于任何非法或不当的用途。
#
# 详细许可条款请参阅项目根目录下的LICENSE文件。
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。

import os
import json
from pathlib import Path
from typing import Optional

from fastapi import APIRouter, HTTPException
from fastapi.responses import FileResponse

router = APIRouter(prefix="/data", tags=["data"])

# Data directory
DATA_DIR = Path(__file__).parent.parent.parent / "data"


def get_file_info(file_path: Path) -> dict:
    """Get file information"""
    stat = file_path.stat()
    record_count = None

    # Try to get record count
    try:
        if file_path.suffix == ".json":
            with open(file_path, "r", encoding="utf-8") as f:
                data = json.load(f)
                if isinstance(data, list):
                    record_count = len(data)
        elif file_path.suffix == ".csv":
            with open(file_path, "r", encoding="utf-8") as f:
                record_count = sum(1 for _ in f) - 1  # Subtract header row
    except Exception:
        pass

    return {
        "name": file_path.name,
        "path": str(file_path.relative_to(DATA_DIR)),
        "size": stat.st_size,
        "modified_at": stat.st_mtime,
        "record_count": record_count,
        "type": file_path.suffix[1:] if file_path.suffix else "unknown"
    }


@router.get("/files")
async def list_data_files(platform: Optional[str] = None, file_type: Optional[str] = None):
    """Get data file list"""
    if not DATA_DIR.exists():
        return {"files": []}

    files = []
    supported_extensions = {".json", ".csv", ".xlsx", ".xls"}

    for root, dirs, filenames in os.walk(DATA_DIR):
        root_path = Path(root)
        for filename in filenames:
            file_path = root_path / filename
            if file_path.suffix.lower() not in supported_extensions:
                continue

            # Platform filter
            if platform:
                rel_path = str(file_path.relative_to(DATA_DIR))
                if platform.lower() not in rel_path.lower():
                    continue

            # Type filter
            if file_type and file_path.suffix[1:].lower() != file_type.lower():
                continue

            try:
                files.append(get_file_info(file_path))
            except Exception:
                continue

    # Sort by modification time (newest first)
    files.sort(key=lambda x: x["modified_at"], reverse=True)

    return {"files": files}


@router.get("/files/{file_path:path}")
async def get_file_content(file_path: str, preview: bool = True, limit: int = 100):
    """Get file content or preview"""
    full_path = DATA_DIR / file_path

    if not full_path.exists():
        raise HTTPException(status_code=404, detail="File not found")

    if not full_path.is_file():
        raise HTTPException(status_code=400, detail="Not a file")

    # Security check: ensure within DATA_DIR
    try:
        full_path.resolve().relative_to(DATA_DIR.resolve())
    except ValueError:
        raise HTTPException(status_code=403, detail="Access denied")

    if preview:
        # Return preview data
        try:
            if full_path.suffix == ".json":
                with open(full_path, "r", encoding="utf-8") as f:
                    data = json.load(f)
                    if isinstance(data, list):
                        return {"data": data[:limit], "total": len(data)}
                    return {"data": data, "total": 1}
            elif full_path.suffix == ".csv":
                import csv
                with open(full_path, "r", encoding="utf-8") as f:
                    reader = csv.DictReader(f)
                    rows = []
                    for i, row in enumerate(reader):
                        if i >= limit:
                            break
                        rows.append(row)
                    # Re-read to get total count
                    f.seek(0)
                    total = sum(1 for _ in f) - 1
                    return {"data": rows, "total": total}
            elif full_path.suffix.lower() in (".xlsx", ".xls"):
                import pandas as pd
                # Read first limit rows
                df = pd.read_excel(full_path, nrows=limit)
                # Get total row count (only read first column to save memory)
                df_count = pd.read_excel(full_path, usecols=[0])
                total = len(df_count)
                # Convert to list of dictionaries, handle NaN values
                rows = df.where(pd.notnull(df), None).to_dict(orient='records')
                return {
                    "data": rows,
                    "total": total,
                    "columns": list(df.columns)
                }
            else:
                raise HTTPException(status_code=400, detail="Unsupported file type for preview")
        except json.JSONDecodeError:
            raise HTTPException(status_code=400, detail="Invalid JSON file")
        except Exception as e:
            raise HTTPException(status_code=500, detail=str(e))
    else:
        # Return file download
        return FileResponse(
            path=full_path,
            filename=full_path.name,
            media_type="application/octet-stream"
        )


@router.get("/download/{file_path:path}")
async def download_file(file_path: str):
    """Download file"""
    full_path = DATA_DIR / file_path

    if not full_path.exists():
        raise HTTPException(status_code=404, detail="File not found")

    if not full_path.is_file():
        raise HTTPException(status_code=400, detail="Not a file")

    # Security check
    try:
        full_path.resolve().relative_to(DATA_DIR.resolve())
    except ValueError:
        raise HTTPException(status_code=403, detail="Access denied")

    return FileResponse(
        path=full_path,
        filename=full_path.name,
        media_type="application/octet-stream"
    )


@router.get("/stats")
async def get_data_stats():
    """Get data statistics"""
    if not DATA_DIR.exists():
        return {"total_files": 0, "total_size": 0, "by_platform": {}, "by_type": {}}

    stats = {
        "total_files": 0,
        "total_size": 0,
        "by_platform": {},
        "by_type": {}
    }

    supported_extensions = {".json", ".csv", ".xlsx", ".xls"}

    for root, dirs, filenames in os.walk(DATA_DIR):
        root_path = Path(root)
        for filename in filenames:
            file_path = root_path / filename
            if file_path.suffix.lower() not in supported_extensions:
                continue

            try:
                stat = file_path.stat()
                stats["total_files"] += 1
                stats["total_size"] += stat.st_size

                # Statistics by type
                file_type = file_path.suffix[1:].lower()
                stats["by_type"][file_type] = stats["by_type"].get(file_type, 0) + 1

                # Statistics by platform (inferred from path)
                rel_path = str(file_path.relative_to(DATA_DIR))
                for platform in ["xhs", "dy", "ks", "bili", "wb", "tieba", "zhihu"]:
                    if platform in rel_path.lower():
                        stats["by_platform"][platform] = stats["by_platform"].get(platform, 0) + 1
                        break
            except Exception:
                continue

    return stats


================================================
FILE: api/routers/websocket.py
================================================
# -*- coding: utf-8 -*-
# Copyright (c) 2025 relakkes@gmail.com
#
# This file is part of MediaCrawler project.
# Repository: https://github.com/NanmiCoder/MediaCrawler/blob/main/api/routers/websocket.py
# GitHub: https://github.com/NanmiCoder
# Licensed under NON-COMMERCIAL LEARNING LICENSE 1.1
#
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
# 1. 不得用于任何商业用途。
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
# 3. 不得进行大规模爬取或对平台造成运营干扰。
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
# 5. 不得用于任何非法或不当的用途。
#
# 详细许可条款请参阅项目根目录下的LICENSE文件。
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。

import asyncio
from typing import Set, Optional

from fastapi import APIRouter, WebSocket, WebSocketDisconnect

from ..services import crawler_manager

router = APIRouter(tags=["websocket"])


class ConnectionManager:
    """WebSocket connection manager"""

    def __init__(self):
        self.active_connections: Set[WebSocket] = set()

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.add(websocket)

    def disconnect(self, websocket: WebSocket):
        self.active_connections.discard(websocket)

    async def broadcast(self, message: dict):
        """Broadcast message to all connections"""
        if not self.active_connections:
            return

        disconnected = []
        for connection in list(self.active_connections):
            try:
                await connection.send_json(message)
            except Exception:
                disconnected.append(connection)

        # Clean up disconnected connections
        for conn in disconnected:
            self.disconnect(conn)


manager = ConnectionManager()


async def log_broadcaster():
    """Background task: read logs from queue and broadcast"""
    queue = crawler_manager.get_log_queue()
    while True:
        try:
            # Get log entry from queue
            entry = await queue.get()
            # Broadcast to all WebSocket connections
            await manager.broadcast(entry.model_dump())
        except asyncio.CancelledError:
            break
        except Exception as e:
            print(f"Log broadcaster error: {e}")
            await asyncio.sleep(0.1)


# Global broadcast task
_broadcaster_task: Optional[asyncio.Task] = None


def start_broadcaster():
    """Start broadcast task"""
    global _broadcaster_task
    if _broadcaster_task is None or _broadcaster_task.done():
        _broadcaster_task = asyncio.create_task(log_broadcaster())


@router.websocket("/ws/logs")
async def websocket_logs(websocket: WebSocket):
    """WebSocket log stream"""
    print("[WS] New connection attempt")

    try:
        # Ensure broadcast task is running
        start_broadcaster()

        await manager.connect(websocket)
        print(f"[WS] Connected, active connections: {len(manager.active_connections)}")

        # Send existing logs
        for log in crawler_manager.logs:
            try:
                await websocket.send_json(log.model_dump())
            except Exception as e:
                print(f"[WS] Error sending existing log: {e}")
                break

        print(f"[WS] Sent {len(crawler_manager.logs)} existing logs, entering main loop")

        while True:
            # Keep connection alive, receive heartbeat or any message
            try:
                data = await asyncio.wait_for(
                    websocket.receive_text(),
                    timeout=30.0
                )
                if data == "ping":
                    await websocket.send_text("pong")
            except asyncio.TimeoutError:
                # Send ping to keep connection alive
                try:
                    await websocket.send_text("ping")
                except Exception as e:
                    print(f"[WS] Error sending ping: {e}")
                    break

    except WebSocketDisconnect:
        print("[WS] Client disconnected")
    except Exception as e:
        print(f"[WS] Error: {type(e).__name__}: {e}")
    finally:
        manager.disconnect(websocket)
        print(f"[WS] Cleanup done, active connections: {len(manager.active_connections)}")


@router.websocket("/ws/status")
async def websocket_status(websocket: WebSocket):
    """WebSocket status stream"""
    await websocket.accept()

    try:
        while True:
            # Send status every second
            status = crawler_manager.get_status()
            await websocket.send_json(status)
            await asyncio.sleep(1)
    except WebSocketDisconnect:
        pass
    except Exception:
        pass


================================================
FILE: api/schemas/__init__.py
================================================
# -*- coding: utf-8 -*-
# Copyright (c) 2025 relakkes@gmail.com
#
# This file is part of MediaCrawler project.
# Repository: https://github.com/NanmiCoder/MediaCrawler/blob/main/api/schemas/__init__.py
# GitHub: https://github.com/NanmiCoder
# Licensed under NON-COMMERCIAL LEARNING LICENSE 1.1
#
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
# 1. 不得用于任何商业用途。
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
# 3. 不得进行大规模爬取或对平台造成运营干扰。
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
# 5. 不得用于任何非法或不当的用途。
#
# 详细许可条款请参阅项目根目录下的LICENSE文件。
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。

from .crawler import (
    PlatformEnum,
    LoginTypeEnum,
    CrawlerTypeEnum,
    SaveDataOptionEnum,
    CrawlerStartRequest,
    CrawlerStatusResponse,
    LogEntry,
)

__all__ = [
    "PlatformEnum",
    "LoginTypeEnum",
    "CrawlerTypeEnum",
    "SaveDataOptionEnum",
    "CrawlerStartRequest",
    "CrawlerStatusResponse",
    "LogEntry",
]


================================================
FILE: api/schemas/crawler.py
================================================
# -*- coding: utf-8 -*-
# Copyright (c) 2025 relakkes@gmail.com
#
# This file is part of MediaCrawler project.
# Repository: https://github.com/NanmiCoder/MediaCrawler/blob/main/api/schemas/crawler.py
# GitHub: https://github.com/NanmiCoder
# Licensed under NON-COMMERCIAL LEARNING LICENSE 1.1
#
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
# 1. 不得用于任何商业用途。
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
# 3. 不得进行大规模爬取或对平台造成运营干扰。
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
# 5. 不得用于任何非法或不当的用途。
#
# 详细许可条款请参阅项目根目录下的LICENSE文件。
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。

from enum import Enum
from typing import Optional, Literal
from pydantic import BaseModel


class PlatformEnum(str, Enum):
    """Supported media platforms"""
    XHS = "xhs"
    DOUYIN = "dy"
    KUAISHOU = "ks"
    BILIBILI = "bili"
    WEIBO = "wb"
    TIEBA = "tieba"
    ZHIHU = "zhihu"


class LoginTypeEnum(str, Enum):
    """Login method"""
    QRCODE = "qrcode"
    PHONE = "phone"
    COOKIE = "cookie"


class CrawlerTypeEnum(str, Enum):
    """Crawler type"""
    SEARCH = "search"
    DETAIL = "detail"
    CREATOR = "creator"


class SaveDataOptionEnum(str, Enum):
    """Data save option"""
    CSV = "csv"
    DB = "db"
    JSON = "json"
    JSONL = "jsonl"
    SQLITE = "sqlite"
    MONGODB = "mongodb"
    EXCEL = "excel"


class CrawlerStartRequest(BaseModel):
    """Crawler start request"""
    platform: PlatformEnum
    login_type: LoginTypeEnum = LoginTypeEnum.QRCODE
    crawler_type: CrawlerTypeEnum = CrawlerTypeEnum.SEARCH
    keywords: str = ""  # Keywords for search mode
    specified_ids: str = ""  # Post/video ID list for detail mode, comma-separated
    creator_ids: str = ""  # Creator ID list for creator mode, comma-separated
    start_page: int = 1
    enable_comments: bool = True
    enable_sub_comments: bool = False
    save_option: SaveDataOptionEnum = SaveDataOptionEnum.JSONL
    cookies: str = ""
    headless: bool = False


class CrawlerStatusResponse(BaseModel):
    """Crawler status response"""
    status: Literal["idle", "running", "stopping", "error"]
    platform: Optional[str] = None
    crawler_type: Optional[str] = None
    started_at: Optional[str] = None
    error_message: Optional[str] = None


class LogEntry(BaseModel):
    """Log entry"""
    id: int
    timestamp: str
    level: Literal["info", "warning", "error", "success", "debug"]
    message: str


class DataFileInfo(BaseModel):
    """Data file information"""
    name: str
    path: str
    size: int
    modified_at: str
    record_count: Optional[int] = None


================================================
FILE: api/services/__init__.py
================================================
# -*- coding: utf-8 -*-
# Copyright (c) 2025 relakkes@gmail.com
#
# This file is part of MediaCrawler project.
# Repository: https://github.com/NanmiCoder/MediaCrawler/blob/main/api/services/__init__.py
# GitHub: https://github.com/NanmiCoder
# Licensed under NON-COMMERCIAL LEARNING LICENSE 1.1
#
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
# 1. 不得用于任何商业用途。
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
# 3. 不得进行大规模爬取或对平台造成运营干扰。
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
# 5. 不得用于任何非法或不当的用途。
#
# 详细许可条款请参阅项目根目录下的LICENSE文件。
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。

from .crawler_manager import CrawlerManager, crawler_manager

__all__ = ["CrawlerManager", "crawler_manager"]


================================================
FILE: api/services/crawler_manager.py
================================================
# -*- coding: utf-8 -*-
# Copyright (c) 2025 relakkes@gmail.com
#
# This file is part of MediaCrawler project.
# Repository: https://github.com/NanmiCoder/MediaCrawler/blob/main/api/services/crawler_manager.py
# GitHub: https://github.com/NanmiCoder
# Licensed under NON-COMMERCIAL LEARNING LICENSE 1.1
#
# 声明:本代码仅供学习和研究目的使用。使用者应遵守以下原则:
# 1. 不得用于任何商业用途。
# 2. 使用时应遵守目标平台的使用条款和robots.txt规则。
# 3. 不得进行大规模爬取或对平台造成运营干扰。
# 4. 应合理控制请求频率,避免给目标平台带来不必要的负担。
# 5. 不得用于任何非法或不当的用途。
#
# 详细许可条款请参阅项目根目录下的LICENSE文件。
# 使用本代码即表示您同意遵守上述原则和LICENSE中的所有条款。

import asyncio
import subprocess
import signal
import os
from typing import Optional, List
from datetime import datetime
from pathlib import Path

from ..schemas import CrawlerStartRequest, LogEntry


class CrawlerManager:
    """Crawler process manager"""

    def __init__(self):
        self._lock = asyncio.Lock()
        self.process: Optional[subprocess.Popen] = None
        self.status = "idle"
        self.started_at: Optional[datetime] = None
        self.current_config: Optional[CrawlerStartRequest] = None
        self._log_id = 0
        self._logs: List[LogEntry] = []
        self._read_task: Optional[asyncio.Task] = None
        # Project root directory
        self._project_root = Path(__file__).parent.parent.parent
        # Log queue - for pushing to WebSocket
        self._log_queue: Optional[asyncio.Queue] = None

    @property
    def logs(self) -> List[LogEntry]:
        return self._logs

    def get_log_queue(self) -> asyncio.Queue:
        """Get or create log queue"""
        if self._log_queue is None:
            self._log_queue = asyncio.Queue()
        return self._log_queue

    def _create_log_entry(self, message: str, level: str = "info") -> LogEntry:
        """Create log entry"""
        self._log_id += 1
        entry = LogEntry(
            id=self._log_id,
            timestamp=datetime.now().strftime("%H:%M:%S"),
            level=level,
            message=message
        )
        self._logs.append(entry)
        # Keep last 500 logs
        if len(self._logs) > 500:
            self._logs = self._logs[-500:]
        return entry

    async def _push_log(self, entry: LogEntry):
        """Push log to queue"""
        if self._log_queue is not None:
            try:
                self._log_queue.put_nowait(entry)
            except asyncio.QueueFull:
                pass

    def _parse_log_level(self, line: str) -> str:
        """Parse log level"""
        line_upper = line.upper()
        if "ERROR" in line_upper or "FAILED" in line_upper:
            return "error"
        elif "WARNING" in line_upper or "WARN" in line_upper:
            return "warning"
        elif "SUCCESS" in line_upper or "完成" in line or "成功" in line:
            return "success"
        elif "DEBUG" in line_upper:
            return "debug"
        return "info"

    async def start(self, config: CrawlerStartRequest) -> bool:
        """Start crawler process"""
        async with self._lock:
            if self.process and self.process.poll() is None:
                return False

            # Clear old logs
            self._logs = []
            self._log_id = 0

            # Clear pending queue (don't replace object to avoid WebSocket broadcast coroutine holding old queue reference)
            if self._log_queue is None:
                self._log_queue = asyncio.Queue()
            else:
                try:
                    while True:
                        self._log_queue.get_nowait()
                except asyncio.QueueEmpty:
                    pass

            # Build command line arguments
            cmd = self._build_command(config)

            # Log start information
            entry = self._create_log_entry(f"Starting crawler: {' '.join(cmd)}", "info")
            await self._push_log(entry)

            try:
                # Start subprocess
                self.process = subprocess.Popen(
                    cmd,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.STDOUT,
                    text=True,
                    encoding='utf-8',
                    bufsize=1,
                    cwd=str(self._project_root),
                    env={**os.environ, "PYTHONUNBUFFERED": "1"}
                )

                self.status = "running"
                self.started_at = datetime.now()
                self.current_config = config

                entry = self._create_log_entry(
                    f"Crawler started on platform: {config.platform.value}, type: {config.crawler_type.value}",
                    "success"
                )
                await self._push_log(entry)

                # Start log reading task
                self._read_task = asyncio.create_task(self._read_output())

                return True
            except Exception as e:
                self.status = "error"
                entry = self._create_log_entry(f"Failed to start crawler: {str(e)}", "error")
                await self._push_log(entry)
                return False

    async def stop(self) -> bool:
        """Stop crawler process"""
        async with self._lock:
            if not self.process or self.process.poll() is not None:
                return False

            self.status = "stopping"
            entry = self._create_log_entry("Sending SIGTERM to crawler process...", "warning")
            await self._push_log(entry)

            try:
                self.process.send_signal(signal.SIGTERM)

                # Wait for graceful exit (up to 15 seconds)
                for _ in range(30):
                    if self.process.poll() is not None:
                        break
                    await asyncio.sleep(0.5)

                # If still not exited, force kill
                if self.process.poll() is None:
                    entry = self._create_log_entry("Process not responding, sending SIGKILL...", "warning")
                    await self._push_log(entry)
                    self.process.kill()

                entry = self._create_log_entry("Crawler process terminated", "info")
                await self._push_log(entry)

            except Exception as e:
                entry = self._create_log_entry(f"Error stopping crawler: {str(e)}", "error")
                await self._push_log(entry)

            self.status = "idle"
            self.current_config = None

            # Cancel log reading task
            if self._read_task:
                self._read_task.cancel()
                self._read_task = None

            return True

    def get_status(self) -> dict:
        """Get current status"""
        return {
            "status": self.status,
            "platform": self.current_config.platform.value if self.current_config else None,
            "crawler_type": self.current_config.crawler_type.value if self.current_config else None,
            "started_at": self.started_at.isoformat() if self.started_at else None,
            "error_message": None
        }

    def _build_command(self, config: CrawlerStartRequest) -> list:
        """Build main.py command line arguments"""
        cmd = ["uv", "run", "python", "main.py"]

        cmd.extend(["--platform", config.platform.value])
        cmd.extend(["--lt", config.login_type.value])
        cmd.extend(["--type", config.crawler_type.value])
        cmd.extend(["--save_data_option", config.save_option.value])

        # Pass different arguments based on crawler type
        if config.crawler_type.value == "search" and config.keywords:
            cmd.extend(["--keywords", config.keywords])
        elif config.crawler_type.value == "detail" and config.specified_ids:
            cmd.extend(["--specified_id", config.specified_ids])
        elif config.crawler_type.value == "creator" and config.creator_ids:
            cmd.extend(["--creator_id", config.creator_ids])

        if config.start_page != 1:
            cmd.extend(["--start", str(config.start_page)])

        cmd.extend(["--get_comment", "true" if config.enable_comments else "false"])
        cmd.extend(["--get_sub_comment", "true" if config.enable_sub_comments else "false"])

        if config.cookies:
            cmd.extend(["--cookies", config.cookies])

        cmd.extend(["--headless", "true" if config.headless else "false"])

        return cmd

    async def _read_output(self):
        """Asynchronously read process output"""
        loop = asyncio.get_event_loop()

        try:
            while self.process and self.process.poll() is None:
                # Read a line in thread pool
                line = await loop.run_in_executor(
                    None, self.process.stdout.readline
                )
                if line:
                    line = line.strip()
                    if line:
                        level = self._parse_log_level(line)
                        entry = self._create_log_entry(line, level)
                        await self._push_log(entry)

            # Read remaining output
            if self.process and self.process.stdout:
                remaining = await loop.run_in_executor(
                    None, self.process.stdout.read
                )
                if remaining:
                    for line in remaining.strip().split('\n'):
                        if line.strip():
                            level = self._parse_log_level(line)
                            entry = self._create_log_entry(line.strip(), level)
                            await self._push_log(entry)

            # Process ended
            if self.status == "running":
                exit_code = self.process.returncode if self.process else -1
                if exit_code == 0:
                    entry = self._create_log_entry("Crawler completed successfully", "success")
                else:
                    entry = self._create_log_entry(f"Crawler exited with code: {exit_code}", "warning")
                await self._push_log(entry)
                self.status = "idle"

        except asyncio.CancelledError:
            pass
        except Exception as e:
            entry = self._create_log_entry(f"Error reading output: {str(e)}", "error")
            await self._push_log(entry)


# Global singleton
crawler_manager = CrawlerManager()


================================================
FILE: api/webui/assets/index-DvClRayq.js
================================================
var $m=t=>{throw TypeError(t)};var ld=(t,e,r)=>e.has(t)||$m("Cannot "+r);var R=(t,e,r)=>(ld(t,e,"read from private field"),r?r.call(t):e.get(t)),ve=(t,e,r)=>e.has(t)?$m("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,r),ce=(t,e,r,s)=>(ld(t,e,"write to private field"),s?s.call(t,r):e.set(t,r),r),Te=(t,e,r)=>(ld(t,e,"access private method"),r);var Sl=(t,e,r,s)=>({set _(i){ce(t,e,i,r)},get _(){return R(t,e,s)}});function t1(t,e){for(var r=0;r<e.length;r++){const s=e[r];if(typeof s!="string"&&!Array.isArray(s)){for(const i in s)if(i!=="default"&&!(i in t)){const l=Object.getOwnPropertyDescriptor(s,i);l&&Object.defineProperty(t,i,l.get?l:{enumerable:!0,get:()=>s[i]})}}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}(function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))s(i);new MutationObserver(i=>{for(const l of i)if(l.type==="childList")for(const u of l.addedNodes)u.tagName==="LINK"&&u.rel==="modulepreload"&&s(u)}).observe(document,{childList:!0,subtree:!0});function r(i){const l={};return i.integrity&&(l.integrity=i.integrity),i.referrerPolicy&&(l.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?l.credentials="include":i.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function s(i){if(i.ep)return;i.ep=!0;const l=r(i);fetch(i.href,l)}})();function bf(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var cd={exports:{}},Di={},ud={exports:{}},je={};/**
 * @license React
 * react.production.min.js
 *
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */var Um;function n1(){if(Um)return je;Um=1;var t=Symbol.for("react.element"),e=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),s=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),l=Symbol.for("react.provider"),u=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),h=Symbol.for("react.suspense"),p=Symbol.for("react.memo"),y=Symbol.for("react.lazy"),v=Symbol.iterator;function C(P){return P===null||typeof P!="object"?null:(P=v&&P[v]||P["@@iterator"],typeof P=="function"?P:null)}var w={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},E=Object.assign,b={};function k(P,M,ie){this.props=P,this.context=M,this.refs=b,this.updater=ie||w}k.prototype.isReactComponent={},k.prototype.setState=function(P,M){if(typeof P!="object"&&typeof P!="function"&&P!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,P,M,"setState")},k.prototype.forceUpdate=function(P){this.updater.enqueueForceUpdate(this,P,"forceUpdate")};function T(){}T.prototype=k.prototype;function j(P,M,ie){this.props=P,this.context=M,this.refs=b,this.updater=ie||w}var _=j.prototype=new T;_.constructor=j,E(_,k.prototype),_.isPureReactComponent=!0;var A=Array.isArray,F=Object.prototype.hasOwnProperty,V={current:null},B={key:!0,ref:!0,__self:!0,__source:!0};function te(P,M,ie){var ae,me={},be=null,ee=null;if(M!=null)for(ae in M.ref!==void 0&&(ee=M.ref),M.key!==void 0&&(be=""+M.key),M)F.call(M,ae)&&!B.hasOwnProperty(ae)&&(me[ae]=M[ae]);var ye=arguments.length-2;if(ye===1)me.children=ie;else if(1<ye){for(var Se=Array(ye),Ne=0;Ne<ye;Ne++)Se[Ne]=arguments[Ne+2];me.children=Se}if(P&&P.defaultProps)for(ae in ye=P.defaultProps,ye)me[ae]===void 0&&(me[ae]=ye[ae]);return{$$typeof:t,type:P,key:be,ref:ee,props:me,_owner:V.current}}function G(P,M){return{$$typeof:t,type:P.type,key:M,ref:P.ref,props:P.props,_owner:P._owner}}function W(P){return typeof P=="object"&&P!==null&&P.$$typeof===t}function le(P){var M={"=":"=0",":":"=2"};return"$"+P.replace(/[=:]/g,function(ie){return M[ie]})}var K=/\/+/g;function Z(P,M){return typeof P=="object"&&P!==null&&P.key!=null?le(""+P.key):M.toString(36)}function J(P,M,ie,ae,me){var be=typeof P;(be==="undefined"||be==="boolean")&&(P=null);var ee=!1;if(P===null)ee=!0;else switch(be){case"string":case"number":ee=!0;break;case"object":switch(P.$$typeof){case t:case e:ee=!0}}if(ee)return ee=P,me=me(ee),P=ae===""?"."+Z(ee,0):ae,A(me)?(ie="",P!=null&&(ie=P.replace(K,"$&/")+"/"),J(me,M,ie,"",function(Ne){return Ne})):me!=null&&(W(me)&&(me=G(me,ie+(!me.key||ee&&ee.key===me.key?"":(""+me.key).replace(K,"$&/")+"/")+P)),M.push(me)),1;if(ee=0,ae=ae===""?".":ae+":",A(P))for(var ye=0;ye<P.length;ye++){be=P[ye];var Se=ae+Z(be,ye);ee+=J(be,M,ie,Se,me)}else if(Se=C(P),typeof Se=="function")for(P=Se.call(P),ye=0;!(be=P.next()).done;)be=be.value,Se=ae+Z(be,ye++),ee+=J(be,M,ie,Se,me);else if(be==="object")throw M=String(P),Error("Objects are not valid as a React child (found: "+(M==="[object Object]"?"object with keys {"+Object.keys(P).join(", ")+"}":M)+"). If you meant to render a collection of children, use an array instead.");return ee}function de(P,M,ie){if(P==null)return P;var ae=[],me=0;return J(P,ae,"","",function(be){return M.call(ie,be,me++)}),ae}function ne(P){if(P._status===-1){var M=P._result;M=M(),M.then(function(ie){(P._status===0||P._status===-1)&&(P._status=1,P._result=ie)},function(ie){(P._status===0||P._status===-1)&&(P._status=2,P._result=ie)}),P._status===-1&&(P._status=0,P._result=M)}if(P._status===1)return P._result.default;throw P._result}var se={current:null},$={transition:null},H={ReactCurrentDispatcher:se,ReactCurrentBatchConfig:$,ReactCurrentOwner:V};function Q(){throw Error("act(...) is not supported in production builds of React.")}return je.Children={map:de,forEach:function(P,M,ie){de(P,function(){M.apply(this,arguments)},ie)},count:function(P){var M=0;return de(P,function(){M++}),M},toArray:function(P){return de(P,function(M){return M})||[]},only:function(P){if(!W(P))throw Error("React.Children.only expected to receive a single React element child.");return P}},je.Component=k,je.Fragment=r,je.Profiler=i,je.PureComponent=j,je.StrictMode=s,je.Suspense=h,je.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=H,je.act=Q,je.cloneElement=function(P,M,ie){if(P==null)throw Error("React.cloneElement(...): The argument must be a React element, but you passed "+P+".");var ae=E({},P.props),me=P.key,be=P.ref,ee=P._owner;if(M!=null){if(M.ref!==void 0&&(be=M.ref,ee=V.current),M.key!==void 0&&(me=""+M.key),P.type&&P.type.defaultProps)var ye=P.type.defaultProps;for(Se in M)F.call(M,Se)&&!B.hasOwnProperty(Se)&&(ae[Se]=M[Se]===void 0&&ye!==void 0?ye[Se]:M[Se])}var Se=arguments.length-2;if(Se===1)ae.children=ie;else if(1<Se){ye=Array(Se);for(var Ne=0;Ne<Se;Ne++)ye[Ne]=arguments[Ne+2];ae.children=ye}return{$$typeof:t,type:P.type,key:me,ref:be,props:ae,_owner:ee}},je.createContext=function(P){return P={$$typeof:u,_currentValue:P,_currentValue2:P,_threadCount:0,Provider:null,Consumer:null,_defaultValue:null,_globalName:null},P.Provider={$$typeof:l,_context:P},P.Consumer=P},je.createElement=te,je.createFactory=function(P){var M=te.bind(null,P);return M.type=P,M},je.createRef=function(){return{current:null}},je.forwardRef=function(P){return{$$typeof:d,render:P}},je.isValidElement=W,je.lazy=function(P){return{$$typeof:y,_payload:{_status:-1,_result:P},_init:ne}},je.memo=function(P,M){return{$$typeof:p,type:P,compare:M===void 0?null:M}},je.startTransition=function(P){var M=$.transition;$.transition={};try{P()}finally{$.transition=M}},je.unstable_act=Q,je.useCallback=function(P,M){return se.current.useCallback(P,M)},je.useContext=function(P){return se.current.useContext(P)},je.useDebugValue=function(){},je.useDeferredValue=function(P){return se.current.useDeferredValue(P)},je.useEffect=function(P,M){return se.current.useEffect(P,M)},je.useId=function(){return se.current.useId()},je.useImperativeHandle=function(P,M,ie){return se.current.useImperativeHandle(P,M,ie)},je.useInsertionEffect=function(P,M){return se.current.useInsertionEffect(P,M)},je.useLayoutEffect=function(P,M){return se.current.useLayoutEffect(P,M)},je.useMemo=function(P,M){return se.current.useMemo(P,M)},je.useReducer=function(P,M,ie){return se.current.useReducer(P,M,ie)},je.useRef=function(P){return se.current.useRef(P)},je.useState=function(P){return se.current.useState(P)},je.useSyncExternalStore=function(P,M,ie){return se.current.useSyncExternalStore(P,M,ie)},je.useTransition=function(){return se.current.useTransition()},je.version="18.3.1",je}var Bm;function ac(){return Bm||(Bm=1,ud.exports=n1()),ud.exports}/**
 * @license React
 * react-jsx-runtime.production.min.js
 *
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */var Hm;function r1(){if(Hm)return Di;Hm=1;var t=ac(),e=Symbol.for("react.element"),r=Symbol.for("react.fragment"),s=Object.prototype.hasOwnProperty,i=t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,l={key:!0,ref:!0,__self:!0,__source:!0};function u(d,h,p){var y,v={},C=null,w=null;p!==void 0&&(C=""+p),h.key!==void 0&&(C=""+h.key),h.ref!==void 0&&(w=h.ref);for(y in h)s.call(h,y)&&!l.hasOwnProperty(y)&&(v[y]=h[y]);if(d&&d.defaultProps)for(y in h=d.defaultProps,h)v[y]===void 0&&(v[y]=h[y]);return{$$typeof:e,type:d,key:C,ref:w,props:v,_owner:i.current}}return Di.Fragment=r,Di.jsx=u,Di.jsxs=u,Di}var Vm;function o1(){return Vm||(Vm=1,cd.exports=r1()),cd.exports}var g=o1(),x=ac();const oe=bf(x),Sf=t1({__proto__:null,default:oe},[x]);var Cl={},dd={exports:{}},$t={},fd={exports:{}},hd={};/**
 * @license React
 * scheduler.production.min.js
 *
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */var Wm;function s1(){return Wm||(Wm=1,(function(t){function e($,H){var Q=$.length;$.push(H);e:for(;0<Q;){var P=Q-1>>>1,M=$[P];if(0<i(M,H))$[P]=H,$[Q]=M,Q=P;else break e}}function r($){return $.length===0?null:$[0]}function s($){if($.length===0)return null;var H=$[0],Q=$.pop();if(Q!==H){$[0]=Q;e:for(var P=0,M=$.length,ie=M>>>1;P<ie;){var ae=2*(P+1)-1,me=$[ae],be=ae+1,ee=$[be];if(0>i(me,Q))be<M&&0>i(ee,me)?($[P]=ee,$[be]=Q,P=be):($[P]=me,$[ae]=Q,P=ae);else if(be<M&&0>i(ee,Q))$[P]=ee,$[be]=Q,P=be;else break e}}return H}function i($,H){var Q=$.sortIndex-H.sortIndex;return Q!==0?Q:$.id-H.id}if(typeof performance=="object"&&typeof performance.now=="function"){var l=performance;t.unstable_now=function(){return l.now()}}else{var u=Date,d=u.now();t.unstable_now=function(){return u.now()-d}}var h=[],p=[],y=1,v=null,C=3,w=!1,E=!1,b=!1,k=typeof setTimeout=="function"?setTimeout:null,T=typeof clearTimeout=="function"?clearTimeout:null,j=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function _($){for(var H=r(p);H!==null;){if(H.callback===null)s(p);else if(H.startTime<=$)s(p),H.sortIndex=H.expirationTime,e(h,H);else break;H=r(p)}}function A($){if(b=!1,_($),!E)if(r(h)!==null)E=!0,ne(F);else{var H=r(p);H!==null&&se(A,H.startTime-$)}}function F($,H){E=!1,b&&(b=!1,T(te),te=-1),w=!0;var Q=C;try{for(_(H),v=r(h);v!==null&&(!(v.expirationTime>H)||$&&!le());){var P=v.callback;if(typeof P=="function"){v.callback=null,C=v.priorityLevel;var M=P(v.expirationTime<=H);H=t.unstable_now(),typeof M=="function"?v.callback=M:v===r(h)&&s(h),_(H)}else s(h);v=r(h)}if(v!==null)var ie=!0;else{var ae=r(p);ae!==null&&se(A,ae.startTime-H),ie=!1}return ie}finally{v=null,C=Q,w=!1}}var V=!1,B=null,te=-1,G=5,W=-1;function le(){return!(t.unstable_now()-W<G)}function K(){if(B!==null){var $=t.unstable_now();W=$;var H=!0;try{H=B(!0,$)}finally{H?Z():(V=!1,B=null)}}else V=!1}var Z;if(typeof j=="function")Z=function(){j(K)};else if(typeof MessageChannel<"u"){var J=new MessageChannel,de=J.port2;J.port1.onmessage=K,Z=function(){de.postMessage(null)}}else Z=function(){k(K,0)};function ne($){B=$,V||(V=!0,Z())}function se($,H){te=k(function(){$(t.unstable_now())},H)}t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function($){$.callback=null},t.unstable_continueExecution=function(){E||w||(E=!0,ne(F))},t.unstable_forceFrameRate=function($){0>$||125<$?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):G=0<$?Math.floor(1e3/$):5},t.unstable_getCurrentPriorityLevel=function(){return C},t.unstable_getFirstCallbackNode=function(){return r(h)},t.unstable_next=function($){switch(C){case 1:case 2:case 3:var H=3;break;default:H=C}var Q=C;C=H;try{return $()}finally{C=Q}},t.unstable_pauseExecution=function(){},t.unstable_requestPaint=function(){},t.unstable_runWithPriority=function($,H){switch($){case 1:case 2:case 3:case 4:case 5:break;default:$=3}var Q=C;C=$;try{return H()}finally{C=Q}},t.unstable_scheduleCallback=function($,H,Q){var P=t.unstable_now();switch(typeof Q=="object"&&Q!==null?(Q=Q.delay,Q=typeof Q=="number"&&0<Q?P+Q:P):Q=P,$){case 1:var M=-1;break;case 2:M=250;break;case 5:M=1073741823;break;case 4:M=1e4;break;default:M=5e3}return M=Q+M,$={id:y++,callback:H,priorityLevel:$,startTime:Q,expirationTime:M,sortIndex:-1},Q>P?($.sortIndex=Q,e(p,$),r(h)===null&&$===r(p)&&(b?(T(te),te=-1):b=!0,se(A,Q-P))):($.sortIndex=M,e(h,$),E||w||(E=!0,ne(F))),$},t.unstable_shouldYield=le,t.unstable_wrapCallback=function($){var H=C;return function(){var Q=C;C=H;try{return $.apply(this,arguments)}finally{C=Q}}}})(hd)),hd}var Km;function i1(){return Km||(Km=1,fd.exports=s1()),fd.exports}/**
 * @license React
 * react-dom.production.min.js
 *
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */var qm;function a1(){if(qm)return $t;qm=1;var t=ac(),e=i1();function r(n){for(var o="https://reactjs.org/docs/error-decoder.html?invariant="+n,a=1;a<arguments.length;a++)o+="&args[]="+encodeURIComponent(arguments[a]);return"Minified React error #"+n+"; visit "+o+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}var s=new Set,i={};function l(n,o){u(n,o),u(n+"Capture",o)}function u(n,o){for(i[n]=o,n=0;n<o.length;n++)s.add(o[n])}var d=!(typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),h=Object.prototype.hasOwnProperty,p=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,y={},v={};function C(n){return h.call(v,n)?!0:h.call(y,n)?!1:p.test(n)?v[n]=!0:(y[n]=!0,!1)}function w(n,o,a,c){if(a!==null&&a.type===0)return!1;switch(typeof o){case"function":case"symbol":return!0;case"boolean":return c?!1:a!==null?!a.acceptsBooleans:(n=n.toLowerCase().slice(0,5),n!=="data-"&&n!=="aria-");default:return!1}}function E(n,o,a,c){if(o===null||typeof o>"u"||w(n,o,a,c))return!0;if(c)return!1;if(a!==null)switch(a.type){case 3:return!o;case 4:return o===!1;case 5:return isNaN(o);case 6:return isNaN(o)||1>o}return!1}function b(n,o,a,c,f,m,S){this.acceptsBooleans=o===2||o===3||o===4,this.attributeName=c,this.attributeNamespace=f,this.mustUseProperty=a,this.propertyName=n,this.type=o,this.sanitizeURL=m,this.removeEmptyString=S}var k={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(n){k[n]=new b(n,0,!1,n,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(n){var o=n[0];k[o]=new b(o,1,!1,n[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(n){k[n]=new b(n,2,!1,n.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(n){k[n]=new b(n,2,!1,n,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(n){k[n]=new b(n,3,!1,n.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(n){k[n]=new b(n,3,!0,n,null,!1,!1)}),["capture","download"].forEach(function(n){k[n]=new b(n,4,!1,n,null,!1,!1)}),["cols","rows","size","span"].forEach(function(n){k[n]=new b(n,6,!1,n,null,!1,!1)}),["rowSpan","start"].forEach(function(n){k[n]=new b(n,5,!1,n.toLowerCase(),null,!1,!1)});var T=/[\-:]([a-z])/g;function j(n){return n[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(n){var o=n.replace(T,j);k[o]=new b(o,1,!1,n,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(n){var o=n.replace(T,j);k[o]=new b(o,1,!1,n,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(n){var o=n.replace(T,j);k[o]=new b(o,1,!1,n,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(n){k[n]=new b(n,1,!1,n.toLowerCase(),null,!1,!1)}),k.xlinkHref=new b("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(n){k[n]=new b(n,1,!1,n.toLowerCase(),null,!0,!0)});function _(n,o,a,c){var f=k.hasOwnProperty(o)?k[o]:null;(f!==null?f.type!==0:c||!(2<o.length)||o[0]!=="o"&&o[0]!=="O"||o[1]!=="n"&&o[1]!=="N")&&(E(o,a,f,c)&&(a=null),c||f===null?C(o)&&(a===null?n.removeAttribute(o):n.setAttribute(o,""+a)):f.mustUseProperty?n[f.propertyName]=a===null?f.type===3?!1:"":a:(o=f.attributeName,c=f.attributeNamespace,a===null?n.removeAttribute(o):(f=f.type,a=f===3||f===4&&a===!0?"":""+a,c?n.setAttributeNS(c,o,a):n.setAttribute(o,a))))}var A=t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,F=Symbol.for("react.element"),V=Symbol.for("react.portal"),B=Symbol.for("react.fragment"),te=Symbol.for("react.strict_mode"),G=Symbol.for("react.profiler"),W=Symbol.for("react.provider"),le=Symbol.for("react.context"),K=Symbol.for("react.forward_ref"),Z=Symbol.for("react.suspense"),J=Symbol.for("react.suspense_list"),de=Symbol.for("react.memo"),ne=Symbol.for("react.lazy"),se=Symbol.for("react.offscreen"),$=Symbol.iterator;function H(n){return n===null||typeof n!="object"?null:(n=$&&n[$]||n["@@iterator"],typeof n=="function"?n:null)}var Q=Object.assign,P;function M(n){if(P===void 0)try{throw Error()}catch(a){var o=a.stack.trim().match(/\n( *(at )?)/);P=o&&o[1]||""}return`
`+P+n}var ie=!1;function ae(n,o){if(!n||ie)return"";ie=!0;var a=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(o)if(o=function(){throw Error()},Object.defineProperty(o.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(o,[])}catch(z){var c=z}Reflect.construct(n,[],o)}else{try{o.call()}catch(z){c=z}n.call(o.prototype)}else{try{throw Error()}catch(z){c=z}n()}}catch(z){if(z&&c&&typeof z.stack=="string"){for(var f=z.stack.split(`
`),m=c.stack.split(`
`),S=f.length-1,N=m.length-1;1<=S&&0<=N&&f[S]!==m[N];)N--;for(;1<=S&&0<=N;S--,N--)if(f[S]!==m[N]){if(S!==1||N!==1)do if(S--,N--,0>N||f[S]!==m[N]){var O=`
`+f[S].replace(" at new "," at ");return n.displayName&&O.includes("<anonymous>")&&(O=O.replace("<anonymous>",n.displayName)),O}while(1<=S&&0<=N);break}}}finally{ie=!1,Error.prepareStackTrace=a}return(n=n?n.displayName||n.name:"")?M(n):""}function me(n){switch(n.tag){case 5:return M(n.type);case 16:return M("Lazy");case 13:return M("Suspense");case 19:return M("SuspenseList");case 0:case 2:case 15:return n=ae(n.type,!1),n;case 11:return n=ae(n.type.render,!1),n;case 1:return n=ae(n.type,!0),n;default:return""}}function be(n){if(n==null)return null;if(typeof n=="function")return n.displayName||n.name||null;if(typeof n=="string")return n;switch(n){case B:return"Fragment";case V:return"Portal";case G:return"Profiler";case te:return"StrictMode";case Z:return"Suspense";case J:return"SuspenseList"}if(typeof n=="object")switch(n.$$typeof){case le:return(n.displayName||"Context")+".Consumer";case W:return(n._context.displayName||"Context")+".Provider";case K:var o=n.render;return n=n.displayName,n||(n=o.displayName||o.name||"",n=n!==""?"ForwardRef("+n+")":"ForwardRef"),n;case de:return o=n.displayName||null,o!==null?o:be(n.type)||"Memo";case ne:o=n._payload,n=n._init;try{return be(n(o))}catch{}}return null}function ee(n){var o=n.type;switch(n.tag){case 24:return"Cache";case 9:return(o.displayName||"Context")+".Consumer";case 10:return(o._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return n=o.render,n=n.displayName||n.name||"",o.displayName||(n!==""?"ForwardRef("+n+")":"ForwardRef");case 7:return"Fragment";case 5:return o;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return be(o);case 8:return o===te?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof o=="function")return o.displayName||o.name||null;if(typeof o=="string")return o}return null}function ye(n){switch(typeof n){case"boolean":case"number":case"string":case"undefined":return n;case"object":return n;default:return""}}function Se(n){var o=n.type;return(n=n.nodeName)&&n.toLowerCase()==="input"&&(o==="checkbox"||o==="radio")}function Ne(n){var o=Se(n)?"checked":"value",a=Object.getOwnPropertyDescriptor(n.constructor.prototype,o),c=""+n[o];if(!n.hasOwnProperty(o)&&typeof a<"u"&&typeof a.get=="function"&&typeof a.set=="function"){var f=a.get,m=a.set;return Object.defineProperty(n,o,{configurable:!0,get:function(){return f.call(this)},set:function(S){c=""+S,m.call(this,S)}}),Object.defineProperty(n,o,{enumerable:a.enumerable}),{getValue:function(){return c},setValue:function(S){c=""+S},stopTracking:function(){n._valueTracker=null,delete n[o]}}}}function Oe(n){n._valueTracker||(n._valueTracker=Ne(n))}function _e(n){if(!n)return!1;var o=n._valueTracker;if(!o)return!0;var a=o.getValue(),c="";return n&&(c=Se(n)?n.checked?"true":"false":n.value),n=c,n!==a?(o.setValue(n),!0):!1}function et(n){if(n=n||(typeof document<"u"?document:void 0),typeof n>"u")return null;try{return n.activeElement||n.body}catch{return n.body}}function gt(n,o){var a=o.checked;return Q({},o,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:a??n._wrapperState.initialChecked})}function On(n,o){var a=o.defaultValue==null?"":o.defaultValue,c=o.checked!=null?o.checked:o.defaultChecked;a=ye(o.value!=null?o.value:a),n._wrapperState={initialChecked:c,initialValue:a,controlled:o.type==="checkbox"||o.type==="radio"?o.checked!=null:o.value!=null}}function dn(n,o){o=o.checked,o!=null&&_(n,"checked",o,!1)}function fn(n,o){dn(n,o);var a=ye(o.value),c=o.type;if(a!=null)c==="number"?(a===0&&n.value===""||n.value!=a)&&(n.value=""+a):n.value!==""+a&&(n.value=""+a);else if(c==="submit"||c==="reset"){n.removeAttribute("value");return}o.hasOwnProperty("value")?jn(n,o.type,a):o.hasOwnProperty("defaultValue")&&jn(n,o.type,ye(o.defaultValue)),o.checked==null&&o.defaultChecked!=null&&(n.defaultChecked=!!o.defaultChecked)}function wr(n,o,a){if(o.hasOwnProperty("value")||o.hasOwnProperty("defaultValue")){var c=o.type;if(!(c!=="submit"&&c!=="reset"||o.value!==void 0&&o.value!==null))return;o=""+n._wrapperState.initialValue,a||o===n.value||(n.value=o),n.defaultValue=o}a=n.name,a!==""&&(n.name=""),n.defaultChecked=!!n._wrapperState.initialChecked,a!==""&&(n.name=a)}function jn(n,o,a){(o!=="number"||et(n.ownerDocument)!==n)&&(a==null?n.defaultValue=""+n._wrapperState.initialValue:n.defaultValue!==""+a&&(n.defaultValue=""+a))}var br=Array.isArray;function en(n,o,a,c){if(n=n.options,o){o={};for(var f=0;f<a.length;f++)o["$"+a[f]]=!0;for(a=0;a<n.length;a++)f=o.hasOwnProperty("$"+n[a].value),n[a].selected!==f&&(n[a].selected=f),f&&c&&(n[a].defaultSelected=!0)}else{for(a=""+ye(a),o=null,f=0;f<n.length;f++){if(n[f].value===a){n[f].selected=!0,c&&(n[f].defaultSelected=!0);return}o!==null||n[f].disabled||(o=n[f])}o!==null&&(o.selected=!0)}}function Ko(n,o){if(o.dangerouslySetInnerHTML!=null)throw Error(r(91));return Q({},o,{value:void 0,defaultValue:void 0,children:""+n._wrapperState.initialValue})}function _n(n,o){var a=o.value;if(a==null){if(a=o.children,o=o.defaultValue,a!=null){if(o!=null)throw Error(r(92));if(br(a)){if(1<a.length)throw Error(r(93));a=a[0]}o=a}o==null&&(o=""),a=o}n._wrapperState={initialValue:ye(a)}}function ca(n,o){var a=ye(o.value),c=ye(o.defaultValue);a!=null&&(a=""+a,a!==n.value&&(n.value=a),o.defaultValue==null&&n.defaultValue!==a&&(n.defaultValue=a)),c!=null&&(n.defaultValue=""+c)}function ua(n){var o=n.textContent;o===n._wrapperState.initialValue&&o!==""&&o!==null&&(n.value=o)}function Et(n){switch(n){case"svg":return"http://www.w3.org/2000/svg";case"math":return"http://www.w3.org/1998/Math/MathML";default:return"http://www.w3.org/1999/xhtml"}}function An(n,o){return n==null||n==="http://www.w3.org/1999/xhtml"?Et(o):n==="http://www.w3.org/2000/svg"&&o==="foreignObject"?"http://www.w3.org/1999/xhtml":n}var qo,da=(function(n){return typeof MSApp<"u"&&MSApp.execUnsafeLocalFunction?function(o,a,c,f){MSApp.execUnsafeLocalFunction(function(){return n(o,a,c,f)})}:n})(function(n,o){if(n.namespaceURI!=="http://www.w3.org/2000/svg"||"innerHTML"in n)n.innerHTML=o;else{for(qo=qo||document.createElement("div"),qo.innerHTML="<svg>"+o.valueOf().toString()+"</svg>",o=qo.firstChild;n.firstChild;)n.removeChild(n.firstChild);for(;o.firstChild;)n.appendChild(o.firstChild)}});function Ln(n,o){if(o){var a=n.firstChild;if(a&&a===n.lastChild&&a.nodeType===3){a.nodeValue=o;return}}n.textContent=o}var ao={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},fa=["Webkit","ms","Moz","O"];Object.keys(ao).forEach(function(n){fa.forEach(function(o){o=o+n.charAt(0).toUpperCase()+n.substring(1),ao[o]=ao[n]})});function Qo(n,o,a){return o==null||typeof o=="boolean"||o===""?"":a||typeof o!="number"||o===0||ao.hasOwnProperty(n)&&ao[n]?(""+o).trim():o+"px"}function tr(n,o){n=n.style;for(var a in o)if(o.hasOwnProperty(a)){var c=a.indexOf("--")===0,f=Qo(a,o[a],c);a==="float"&&(a="cssFloat"),c?n.setProperty(a,f):n[a]=f}}var ha=Q({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function In(n,o){if(o){if(ha[n]&&(o.children!=null||o.dangerouslySetInnerHTML!=null))throw Error(r(137,n));if(o.dangerouslySetInnerHTML!=null){if(o.children!=null)throw Error(r(60));if(typeof o.dangerouslySetInnerHTML!="object"||!("__html"in o.dangerouslySetInnerHTML))throw Error(r(61))}if(o.style!=null&&typeof o.style!="object")throw Error(r(62))}}function ei(n,o){if(n.indexOf("-")===-1)return typeof o.is=="string";switch(n){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var ti=null;function Yo(n){return n=n.target||n.srcElement||window,n.correspondingUseElement&&(n=n.correspondingUseElement),n.nodeType===3?n.parentNode:n}var Go=null,Sr=null,Dn=null;function hn(n){if(n=bi(n)){if(typeof Go!="function")throw Error(r(280));var o=n.stateNode;o&&(o=Ma(o),Go(n.stateNode,n.type,o))}}function pa(n){Sr?Dn?Dn.push(n):Dn=[n]:Sr=n}function Ee(){if(Sr){var n=Sr,o=Dn;if(Dn=Sr=null,hn(n),o)for(n=0;n<o.length;n++)hn(o[n])}}function Fe(n,o){return n(o)}function He(){}var kt=!1;function At(n,o,a){if(kt)return n(o,a);kt=!0;try{return Fe(n,o,a)}finally{kt=!1,(Sr!==null||Dn!==null)&&(He(),Ee())}}function Lt(n,o){var a=n.stateNode;if(a===null)return null;var c=Ma(a);if(c===null)return null;a=c[o];e:switch(o){case"onClick":case"onClickCapture":case"onDoubleClick":case"onDoubleClickCapture":case"onMouseDown":case"onMouseDownCapture":case"onMouseMove":case"onMouseMoveCapture":case"onMouseUp":case"onMouseUpCapture":case"onMouseEnter":(c=!c.disabled)||(n=n.type,c=!(n==="button"||n==="input"||n==="select"||n==="textarea")),n=!c;break e;default:n=!1}if(n)return null;if(a&&typeof a!="function")throw Error(r(231,o,typeof a));return a}var pn=!1;if(d)try{var ft={};Object.defineProperty(ft,"passive",{get:function(){pn=!0}}),window.addEventListener("test",ft,ft),window.removeEventListener("test",ft,ft)}catch{pn=!1}function Mn(n,o,a,c,f,m,S,N,O){var z=Array.prototype.slice.call(arguments,3);try{o.apply(a,z)}catch(Y){this.onError(Y)}}var ni=!1,ma=null,ga=!1,Rc=null,lw={onError:function(n){ni=!0,ma=n}};function cw(n,o,a,c,f,m,S,N,O){ni=!1,ma=null,Mn.apply(lw,arguments)}function uw(n,o,a,c,f,m,S,N,O){if(cw.apply(this,arguments),ni){if(ni){var z=ma;ni=!1,ma=null}else throw Error(r(198));ga||(ga=!0,Rc=z)}}function lo(n){var o=n,a=n;if(n.alternate)for(;o.return;)o=o.return;else{n=o;do o=n,(o.flags&4098)!==0&&(a=o.return),n=o.return;while(n)}return o.tag===3?a:null}function ch(n){if(n.tag===13){var o=n.memoizedState;if(o===null&&(n=n.alternate,n!==null&&(o=n.memoizedState)),o!==null)return o.dehydrated}return null}function uh(n){if(lo(n)!==n)throw Error(r(188))}function dw(n){var o=n.alternate;if(!o){if(o=lo(n),o===null)throw Error(r(188));return o!==n?null:n}for(var a=n,c=o;;){var f=a.return;if(f===null)break;var m=f.alternate;if(m===null){if(c=f.return,c!==null){a=c;continue}break}if(f.child===m.child){for(m=f.child;m;){if(m===a)return uh(f),n;if(m===c)return uh(f),o;m=m.sibling}throw Error(r(188))}if(a.return!==c.return)a=f,c=m;else{for(var S=!1,N=f.child;N;){if(N===a){S=!0,a=f,c=m;break}if(N===c){S=!0,c=f,a=m;break}N=N.sibling}if(!S){for(N=m.child;N;){if(N===a){S=!0,a=m,c=f;break}if(N===c){S=!0,c=m,a=f;break}N=N.sibling}if(!S)throw Error(r(189))}}if(a.alternate!==c)throw Error(r(190))}if(a.tag!==3)throw Error(r(188));return a.stateNode.current===a?n:o}function dh(n){return n=dw(n),n!==null?fh(n):null}function fh(n){if(n.tag===5||n.tag===6)return n;for(n=n.child;n!==null;){var o=fh(n);if(o!==null)return o;n=n.sibling}return null}var hh=e.unstable_scheduleCallback,ph=e.unstable_cancelCallback,fw=e.unstable_shouldYield,hw=e.unstable_requestPaint,tt=e.unstable_now,pw=e.unstable_getCurrentPriorityLevel,Pc=e.unstable_ImmediatePriority,mh=e.unstable_UserBlockingPriority,ya=e.unstable_NormalPriority,mw=e.unstable_LowPriority,gh=e.unstable_IdlePriority,va=null,Fn=null;function gw(n){if(Fn&&typeof Fn.onCommitFiberRoot=="function")try{Fn.onCommitFiberRoot(va,n,void 0,(n.current.flags&128)===128)}catch{}}var mn=Math.clz32?Math.clz32:xw,yw=Math.log,vw=Math.LN2;function xw(n){return n>>>=0,n===0?32:31-(yw(n)/vw|0)|0}var xa=64,wa=4194304;function ri(n){switch(n&-n){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return n&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return n&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return n}}function ba(n,o){var a=n.pendingLanes;if(a===0)return 0;var c=0,f=n.suspendedLanes,m=n.pingedLanes,S=a&268435455;if(S!==0){var N=S&~f;N!==0?c=ri(N):(m&=S,m!==0&&(c=ri(m)))}else S=a&~f,S!==0?c=ri(S):m!==0&&(c=ri(m));if(c===0)return 0;if(o!==0&&o!==c&&(o&f)===0&&(f=c&-c,m=o&-o,f>=m||f===16&&(m&4194240)!==0))return o;if((c&4)!==0&&(c|=a&16),o=n.entangledLanes,o!==0)for(n=n.entanglements,o&=c;0<o;)a=31-mn(o),f=1<<a,c|=n[a],o&=~f;return c}function ww(n,o){switch(n){case 1:case 2:case 4:return o+250;case 8:case 16:case 32:case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return o+5e3;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return-1;case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function bw(n,o){for(var a=n.suspendedLanes,c=n.pingedLanes,f=n.expirationTimes,m=n.pendingLanes;0<m;){var S=31-mn(m),N=1<<S,O=f[S];O===-1?((N&a)===0||(N&c)!==0)&&(f[S]=ww(N,o)):O<=o&&(n.expiredLanes|=N),m&=~N}}function Tc(n){return n=n.pendingLanes&-1073741825,n!==0?n:n&1073741824?1073741824:0}function yh(){var n=xa;return xa<<=1,(xa&4194240)===0&&(xa=64),n}function Oc(n){for(var o=[],a=0;31>a;a++)o.push(n);return o}function oi(n,o,a){n.pendingLanes|=o,o!==536870912&&(n.suspendedLanes=0,n.pingedLanes=0),n=n.eventTimes,o=31-mn(o),n[o]=a}function Sw(n,o){var a=n.pendingLanes&~o;n.pendingLanes=o,n.suspendedLanes=0,n.pingedLanes=0,n.expiredLanes&=o,n.mutableReadLanes&=o,n.entangledLanes&=o,o=n.entanglements;var c=n.eventTimes;for(n=n.expirationTimes;0<a;){var f=31-mn(a),m=1<<f;o[f]=0,c[f]=-1,n[f]=-1,a&=~m}}function jc(n,o){var a=n.entangledLanes|=o;for(n=n.entanglements;a;){var c=31-mn(a),f=1<<c;f&o|n[c]&o&&(n[c]|=o),a&=~f}}var Ue=0;function vh(n){return n&=-n,1<n?4<n?(n&268435455)!==0?16:536870912:4:1}var xh,_c,wh,bh,Sh,Ac=!1,Sa=[],Cr=null,Er=null,kr=null,si=new Map,ii=new Map,Nr=[],Cw="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput copy cut paste click change contextmenu reset submit".split(" ");function Ch(n,o){switch(n){case"focusin":case"focusout":Cr=null;break;case"dragenter":case"dragleave":Er=null;break;case"mouseover":case"mouseout":kr=null;break;case"pointerover":case"pointerout":si.delete(o.pointerId);break;case"gotpointercapture":case"lostpointercapture":ii.delete(o.pointerId)}}function ai(n,o,a,c,f,m){return n===null||n.nativeEvent!==m?(n={blockedOn:o,domEventName:a,eventSystemFlags:c,nativeEvent:m,targetContainers:[f]},o!==null&&(o=bi(o),o!==null&&_c(o)),n):(n.eventSystemFlags|=c,o=n.targetContainers,f!==null&&o.indexOf(f)===-1&&o.push(f),n)}function Ew(n,o,a,c,f){switch(o){case"focusin":return Cr=ai(Cr,n,o,a,c,f),!0;case"dragenter":return Er=ai(Er,n,o,a,c,f),!0;case"mouseover":return kr=ai(kr,n,o,a,c,f),!0;case"pointerover":var m=f.pointerId;return si.set(m,ai(si.get(m)||null,n,o,a,c,f)),!0;case"gotpointercapture":return m=f.pointerId,ii.set(m,ai(ii.get(m)||null,n,o,a,c,f)),!0}return!1}function Eh(n){var o=co(n.target);if(o!==null){var a=lo(o);if(a!==null){if(o=a.tag,o===13){if(o=ch(a),o!==null){n.blockedOn=o,Sh(n.priority,function(){wh(a)});return}}else if(o===3&&a.stateNode.current.memoizedState.isDehydrated){n.blockedOn=a.tag===3?a.stateNode.containerInfo:null;return}}}n.blockedOn=null}function Ca(n){if(n.blockedOn!==null)return!1;for(var o=n.targetContainers;0<o.length;){var a=Ic(n.domEventName,n.eventSystemFlags,o[0],n.nativeEvent);if(a===null){a=n.nativeEvent;var c=new a.constructor(a.type,a);ti=c,a.target.dispatchEvent(c),ti=null}else return o=bi(a),o!==null&&_c(o),n.blockedOn=a,!1;o.shift()}return!0}function kh(n,o,a){Ca(n)&&a.delete(o)}function kw(){Ac=!1,Cr!==null&&Ca(Cr)&&(Cr=null),Er!==null&&Ca(Er)&&(Er=null),kr!==null&&Ca(kr)&&(kr=null),si.forEach(kh),ii.forEach(kh)}function li(n,o){n.blockedOn===o&&(n.blockedOn=null,Ac||(Ac=!0,e.unstable_scheduleCallback(e.unstable_NormalPriority,kw)))}function ci(n){function o(f){return li(f,n)}if(0<Sa.length){li(Sa[0],n);for(var a=1;a<Sa.length;a++){var c=Sa[a];c.blockedOn===n&&(c.blockedOn=null)}}for(Cr!==null&&li(Cr,n),Er!==null&&li(Er,n),kr!==null&&li(kr,n),si.forEach(o),ii.forEach(o),a=0;a<Nr.length;a++)c=Nr[a],c.blockedOn===n&&(c.blockedOn=null);for(;0<Nr.length&&(a=Nr[0],a.blockedOn===null);)Eh(a),a.blockedOn===null&&Nr.shift()}var Xo=A.ReactCurrentBatchConfig,Ea=!0;function Nw(n,o,a,c){var f=Ue,m=Xo.transition;Xo.transition=null;try{Ue=1,Lc(n,o,a,c)}finally{Ue=f,Xo.transition=m}}function Rw(n,o,a,c){var f=Ue,m=Xo.transition;Xo.transition=null;try{Ue=4,Lc(n,o,a,c)}finally{Ue=f,Xo.transition=m}}function Lc(n,o,a,c){if(Ea){var f=Ic(n,o,a,c);if(f===null)Jc(n,o,c,ka,a),Ch(n,c);else if(Ew(f,n,o,a,c))c.stopPropagation();else if(Ch(n,c),o&4&&-1<Cw.indexOf(n)){for(;f!==null;){var m=bi(f);if(m!==null&&xh(m),m=Ic(n,o,a,c),m===null&&Jc(n,o,c,ka,a),m===f)break;f=m}f!==null&&c.stopPropagation()}else Jc(n,o,c,null,a)}}var ka=null;function Ic(n,o,a,c){if(ka=null,n=Yo(c),n=co(n),n!==null)if(o=lo(n),o===null)n=null;else if(a=o.tag,a===13){if(n=ch(o),n!==null)return n;n=null}else if(a===3){if(o.stateNode.current.memoizedState.isDehydrated)return o.tag===3?o.stateNode.containerInfo:null;n=null}else o!==n&&(n=null);return ka=n,null}function Nh(n){switch(n){case"cancel":case"click":case"close":case"contextmenu":case"copy":case"cut":case"auxclick":case"dblclick":case"dragend":case"dragstart":case"drop":case"focusin":case"focusout":case"input":case"invalid":case"keydown":case"keypress":case"keyup":case"mousedown":case"mouseup":case"paste":case"pause":case"play":case"pointercancel":case"pointerdown":case"pointerup":case"ratechange":case"reset":case"resize":case"seeked":case"submit":case"touchcancel":case"touchend":case"touchstart":case"volumechange":case"change":case"selectionchange":case"textInput":case"compositionstart":case"compositionend":case"compositionupdate":case"beforeblur":case"afterblur":case"beforeinput":case"blur":case"fullscreenchange":case"focus":case"hashchange":case"popstate":case"select":case"selectstart":return 1;case"drag":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"mousemove":case"mouseout":case"mouseover":case"pointermove":case"pointerout":case"pointerover":case"scroll":case"toggle":case"touchmove":case"wheel":case"mouseenter":case"mouseleave":case"pointerenter":case"pointerleave":return 4;case"message":switch(pw()){case Pc:return 1;case mh:return 4;case ya:case mw:return 16;case gh:return 536870912;default:return 16}default:return 16}}var Rr=null,Dc=null,Na=null;function Rh(){if(Na)return Na;var n,o=Dc,a=o.length,c,f="value"in Rr?Rr.value:Rr.textContent,m=f.length;for(n=0;n<a&&o[n]===f[n];n++);var S=a-n;for(c=1;c<=S&&o[a-c]===f[m-c];c++);return Na=f.slice(n,1<c?1-c:void 0)}function Ra(n){var o=n.keyCode;return"charCode"in n?(n=n.charCode,n===0&&o===13&&(n=13)):n=o,n===10&&(n=13),32<=n||n===13?n:0}function Pa(){return!0}function Ph(){return!1}function qt(n){function o(a,c,f,m,S){this._reactName=a,this._targetInst=f,this.type=c,this.nativeEvent=m,this.target=S,this.currentTarget=null;for(var N in n)n.hasOwnProperty(N)&&(a=n[N],this[N]=a?a(m):m[N]);return this.isDefaultPrevented=(m.defaultPrevented!=null?m.defaultPrevented:m.returnValue===!1)?Pa:Ph,this.isPropagationStopped=Ph,this}return Q(o.prototype,{preventDefault:function(){this.defaultPrevented=!0;var a=this.nativeEvent;a&&(a.preventDefault?a.preventDefault():typeof a.returnValue!="unknown"&&(a.returnValue=!1),this.isDefaultPrevented=Pa)},stopPropagation:function(){var a=this.nativeEvent;a&&(a.stopPropagation?a.stopPropagation():typeof a.cancelBubble!="unknown"&&(a.cancelBubble=!0),this.isPropagationStopped=Pa)},persist:function(){},isPersistent:Pa}),o}var Jo={eventPhase:0,bubbles:0,cancelable:0,timeStamp:function(n){return n.timeStamp||Date.now()},defaultPrevented:0,isTrusted:0},Mc=qt(Jo),ui=Q({},Jo,{view:0,detail:0}),Pw=qt(ui),Fc,zc,di,Ta=Q({},ui,{screenX:0,screenY:0,clientX:0,clientY:0,pageX:0,pageY:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,getModifierState:Uc,button:0,buttons:0,relatedTarget:function(n){return n.relatedTarget===void 0?n.fromElement===n.srcElement?n.toElement:n.fromElement:n.relatedTarget},movementX:function(n){return"movementX"in n?n.movementX:(n!==di&&(di&&n.type==="mousemove"?(Fc=n.screenX-di.screenX,zc=n.screenY-di.screenY):zc=Fc=0,di=n),Fc)},movementY:function(n){return"movementY"in n?n.movementY:zc}}),Th=qt(Ta),Tw=Q({},Ta,{dataTransfer:0}),Ow=qt(Tw),jw=Q({},ui,{relatedTarget:0}),$c=qt(jw),_w=Q({},Jo,{animationName:0,elapsedTime:0,pseudoElement:0}),Aw=qt(_w),Lw=Q({},Jo,{clipboardData:function(n){return"clipboardData"in n?n.clipboardData:window.clipboardData}}),Iw=qt(Lw),Dw=Q({},Jo,{data:0}),Oh=qt(Dw),Mw={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},Fw={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},zw={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};function $w(n){var o=this.nativeEvent;return o.getModifierState?o.getModifierState(n):(n=zw[n])?!!o[n]:!1}function Uc(){return $w}var Uw=Q({},ui,{key:function(n){if(n.key){var o=Mw[n.key]||n.key;if(o!=="Unidentified")return o}return n.type==="keypress"?(n=Ra(n),n===13?"Enter":String.fromCharCode(n)):n.type==="keydown"||n.type==="keyup"?Fw[n.keyCode]||"Unidentified":""},code:0,location:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,repeat:0,locale:0,getModifierState:Uc,charCode:function(n){return n.type==="keypress"?Ra(n):0},keyCode:function(n){return n.type==="keydown"||n.type==="keyup"?n.keyCode:0},which:function(n){return n.type==="keypress"?Ra(n):n.type==="keydown"||n.type==="keyup"?n.keyCode:0}}),Bw=qt(Uw),Hw=Q({},Ta,{pointerId:0,width:0,height:0,pressure:0,tangentialPressure:0,tiltX:0,tiltY:0,twist:0,pointerType:0,isPrimary:0}),jh=qt(Hw),Vw=Q({},ui,{touches:0,targetTouches:0,changedTouches:0,altKey:0,metaKey:0,ctrlKey:0,shiftKey:0,getModifierState:Uc}),Ww=qt(Vw),Kw=Q({},Jo,{propertyName:0,elapsedTime:0,pseudoElement:0}),qw=qt(Kw),Qw=Q({},Ta,{deltaX:function(n){return"deltaX"in n?n.deltaX:"wheelDeltaX"in n?-n.wheelDeltaX:0},deltaY:function(n){return"deltaY"in n?n.deltaY:"wheelDeltaY"in n?-n.wheelDeltaY:"wheelDelta"in n?-n.wheelDelta:0},deltaZ:0,deltaMode:0}),Yw=qt(Qw),Gw=[9,13,27,32],Bc=d&&"CompositionEvent"in window,fi=null;d&&"documentMode"in document&&(fi=document.documentMode);var Xw=d&&"TextEvent"in window&&!fi,_h=d&&(!Bc||fi&&8<fi&&11>=fi),Ah=" ",Lh=!1;function Ih(n,o){switch(n){case"keyup":return Gw.indexOf(o.keyCode)!==-1;case"keydown":return o.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Dh(n){return n=n.detail,typeof n=="object"&&"data"in n?n.data:null}var Zo=!1;function Jw(n,o){switch(n){case"compositionend":return Dh(o);case"keypress":return o.which!==32?null:(Lh=!0,Ah);case"textInput":return n=o.data,n===Ah&&Lh?null:n;default:return null}}function Zw(n,o){if(Zo)return n==="compositionend"||!Bc&&Ih(n,o)?(n=Rh(),Na=Dc=Rr=null,Zo=!1,n):null;switch(n){case"paste":return null;case"keypress":if(!(o.ctrlKey||o.altKey||o.metaKey)||o.ctrlKey&&o.altKey){if(o.char&&1<o.char.length)return o.char;if(o.which)return String.fromCharCode(o.which)}return null;case"compositionend":return _h&&o.locale!=="ko"?null:o.data;default:return null}}var eb={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function Mh(n){var o=n&&n.nodeName&&n.nodeName.toLowerCase();return o==="input"?!!eb[n.type]:o==="textarea"}function Fh(n,o,a,c){pa(c),o=La(o,"onChange"),0<o.length&&(a=new Mc("onChange","change",null,a,c),n.push({event:a,listeners:o}))}var hi=null,pi=null;function tb(n){np(n,0)}function Oa(n){var o=os(n);if(_e(o))return n}function nb(n,o){if(n==="change")return o}var zh=!1;if(d){var Hc;if(d){var Vc="oninput"in document;if(!Vc){var $h=document.createElement("div");$h.setAttribute("oninput","return;"),Vc=typeof $h.oninput=="function"}Hc=Vc}else Hc=!1;zh=Hc&&(!document.documentMode||9<document.documentMode)}function Uh(){hi&&(hi.detachEvent("onpropertychange",Bh),pi=hi=null)}function Bh(n){if(n.propertyName==="value"&&Oa(pi)){var o=[];Fh(o,pi,n,Yo(n)),At(tb,o)}}function rb(n,o,a){n==="focusin"?(Uh(),hi=o,pi=a,hi.attachEvent("onpropertychange",Bh)):n==="focusout"&&Uh()}function ob(n){if(n==="selectionchange"||n==="keyup"||n==="keydown")return Oa(pi)}function sb(n,o){if(n==="click")return Oa(o)}function ib(n,o){if(n==="input"||n==="change")return Oa(o)}function ab(n,o){return n===o&&(n!==0||1/n===1/o)||n!==n&&o!==o}var gn=typeof Object.is=="function"?Object.is:ab;function mi(n,o){if(gn(n,o))return!0;if(typeof n!="object"||n===null||typeof o!="object"||o===null)return!1;var a=Object.keys(n),c=Object.keys(o);if(a.length!==c.length)return!1;for(c=0;c<a.length;c++){var f=a[c];if(!h.call(o,f)||!gn(n[f],o[f]))return!1}return!0}function Hh(n){for(;n&&n.firstChild;)n=n.firstChild;return n}function Vh(n,o){var a=Hh(n);n=0;for(var c;a;){if(a.nodeType===3){if(c=n+a.textContent.length,n<=o&&c>=o)return{node:a,offset:o-n};n=c}e:{for(;a;){if(a.nextSibling){a=a.nextSibling;break e}a=a.parentNode}a=void 0}a=Hh(a)}}function Wh(n,o){return n&&o?n===o?!0:n&&n.nodeType===3?!1:o&&o.nodeType===3?Wh(n,o.parentNode):"contains"in n?n.contains(o):n.compareDocumentPosition?!!(n.compareDocumentPosition(o)&16):!1:!1}function Kh(){for(var n=window,o=et();o instanceof n.HTMLIFrameElement;){try{var a=typeof o.contentWindow.location.href=="string"}catch{a=!1}if(a)n=o.contentWindow;else break;o=et(n.document)}return o}function Wc(n){var o=n&&n.nodeName&&n.nodeName.toLowerCase();return o&&(o==="input"&&(n.type==="text"||n.type==="search"||n.type==="tel"||n.type==="url"||n.type==="password")||o==="textarea"||n.contentEditable==="true")}function lb(n){var o=Kh(),a=n.focusedElem,c=n.selectionRange;if(o!==a&&a&&a.ownerDocument&&Wh(a.ownerDocument.documentElement,a)){if(c!==null&&Wc(a)){if(o=c.start,n=c.end,n===void 0&&(n=o),"selectionStart"in a)a.selectionStart=o,a.selectionEnd=Math.min(n,a.value.length);else if(n=(o=a.ownerDocument||document)&&o.defaultView||window,n.getSelection){n=n.getSelection();var f=a.textContent.length,m=Math.min(c.start,f);c=c.end===void 0?m:Math.min(c.end,f),!n.extend&&m>c&&(f=c,c=m,m=f),f=Vh(a,m);var S=Vh(a,c);f&&S&&(n.rangeCount!==1||n.anchorNode!==f.node||n.anchorOffset!==f.offset||n.focusNode!==S.node||n.focusOffset!==S.offset)&&(o=o.createRange(),o.setStart(f.node,f.offset),n.removeAllRanges(),m>c?(n.addRange(o),n.extend(S.node,S.offset)):(o.setEnd(S.node,S.offset),n.addRange(o)))}}for(o=[],n=a;n=n.parentNode;)n.nodeType===1&&o.push({element:n,left:n.scrollLeft,top:n.scrollTop});for(typeof a.focus=="function"&&a.focus(),a=0;a<o.length;a++)n=o[a],n.element.scrollLeft=n.left,n.element.scrollTop=n.top}}var cb=d&&"documentMode"in document&&11>=document.documentMode,es=null,Kc=null,gi=null,qc=!1;function qh(n,o,a){var c=a.window===a?a.document:a.nodeType===9?a:a.ownerDocument;qc||es==null||es!==et(c)||(c=es,"selectionStart"in c&&Wc(c)?c={start:c.selectionStart,end:c.selectionEnd}:(c=(c.ownerDocument&&c.ownerDocument.defaultView||window).getSelection(),c={anchorNode:c.anchorNode,anchorOffset:c.anchorOffset,focusNode:c.focusNode,focusOffset:c.focusOffset}),gi&&mi(gi,c)||(gi=c,c=La(Kc,"onSelect"),0<c.length&&(o=new Mc("onSelect","select",null,o,a),n.push({event:o,listeners:c}),o.target=es)))}function ja(n,o){var a={};return a[n.toLowerCase()]=o.toLowerCase(),a["Webkit"+n]="webkit"+o,a["Moz"+n]="moz"+o,a}var ts={animationend:ja("Animation","AnimationEnd"),animationiteration:ja("Animation","AnimationIteration"),animationstart:ja("Animation","AnimationStart"),transitionend:ja("Transition","TransitionEnd")},Qc={},Qh={};d&&(Qh=document.createElement("div").style,"AnimationEvent"in window||(delete ts.animationend.animation,delete ts.animationiteration.animation,delete ts.animationstart.animation),"TransitionEvent"in window||delete ts.transitionend.transition);function _a(n){if(Qc[n])return Qc[n];if(!ts[n])return n;var o=ts[n],a;for(a in o)if(o.hasOwnProperty(a)&&a in Qh)return Qc[n]=o[a];return n}var Yh=_a("animationend"),Gh=_a("animationiteration"),Xh=_a("animationstart"),Jh=_a("transitionend"),Zh=new Map,ep="abort auxClick cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");function Pr(n,o){Zh.set(n,o),l(o,[n])}for(var Yc=0;Yc<ep.length;Yc++){var Gc=ep[Yc],ub=Gc.toLowerCase(),db=Gc[0].toUpperCase()+Gc.slice(1);Pr(ub,"on"+db)}Pr(Yh,"onAnimationEnd"),Pr(Gh,"onAnimationIteration"),Pr(Xh,"onAnimationStart"),Pr("dblclick","onDoubleClick"),Pr("focusin","onFocus"),Pr("focusout","onBlur"),Pr(Jh,"onTransitionEnd"),u("onMouseEnter",["mouseout","mouseover"]),u("onMouseLeave",["mouseout","mouseover"]),u("onPointerEnter",["pointerout","pointerover"]),u("onPointerLeave",["pointerout","pointerover"]),l("onChange","change click focusin focusout input keydown keyup selectionchange".split(" ")),l("onSelect","focusout contextmenu dragend focusin keydown keyup mousedown mouseup selectionchange".split(" ")),l("onBeforeInput",["compositionend","keypress","textInput","paste"]),l("onCompositionEnd","compositionend focusout keydown keypress keyup mousedown".split(" ")),l("onCompositionStart","compositionstart focusout keydown keypress keyup mousedown".split(" ")),l("onCompositionUpdate","compositionupdate focusout keydown keypress keyup mousedown".split(" "));var yi="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange resize seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),fb=new Set("cancel close invalid load scroll toggle".split(" ").concat(yi));function tp(n,o,a){var c=n.type||"unknown-event";n.currentTarget=a,uw(c,o,void 0,n),n.currentTarget=null}function np(n,o){o=(o&4)!==0;for(var a=0;a<n.length;a++){var c=n[a],f=c.event;c=c.listeners;e:{var m=void 0;if(o)for(var S=c.length-1;0<=S;S--){var N=c[S],O=N.instance,z=N.currentTarget;if(N=N.listener,O!==m&&f.isPropagationStopped())break e;tp(f,N,z),m=O}else for(S=0;S<c.length;S++){if(N=c[S],O=N.instance,z=N.currentTarget,N=N.listener,O!==m&&f.isPropagationStopped())break e;tp(f,N,z),m=O}}}if(ga)throw n=Rc,ga=!1,Rc=null,n}function Ke(n,o){var a=o[ou];a===void 0&&(a=o[ou]=new Set);var c=n+"__bubble";a.has(c)||(rp(o,n,2,!1),a.add(c))}function Xc(n,o,a){var c=0;o&&(c|=4),rp(a,n,c,o)}var Aa="_reactListening"+Math.random().toString(36).slice(2);function vi(n){if(!n[Aa]){n[Aa]=!0,s.forEach(function(a){a!=="selectionchange"&&(fb.has(a)||Xc(a,!1,n),Xc(a,!0,n))});var o=n.nodeType===9?n:n.ownerDocument;o===null||o[Aa]||(o[Aa]=!0,Xc("selectionchange",!1,o))}}function rp(n,o,a,c){switch(Nh(o)){case 1:var f=Nw;break;case 4:f=Rw;break;default:f=Lc}a=f.bind(null,o,a,n),f=void 0,!pn||o!=="touchstart"&&o!=="touchmove"&&o!=="wheel"||(f=!0),c?f!==void 0?n.addEventListener(o,a,{capture:!0,passive:f}):n.addEventListener(o,a,!0):f!==void 0?n.addEventListener(o,a,{passive:f}):n.addEventListener(o,a,!1)}function Jc(n,o,a,c,f){var m=c;if((o&1)===0&&(o&2)===0&&c!==null)e:for(;;){if(c===null)return;var S=c.tag;if(S===3||S===4){var N=c.stateNode.containerInfo;if(N===f||N.nodeType===8&&N.parentNode===f)break;if(S===4)for(S=c.return;S!==null;){var O=S.tag;if((O===3||O===4)&&(O=S.stateNode.containerInfo,O===f||O.nodeType===8&&O.parentNode===f))return;S=S.return}for(;N!==null;){if(S=co(N),S===null)return;if(O=S.tag,O===5||O===6){c=m=S;continue e}N=N.parentNode}}c=c.return}At(function(){var z=m,Y=Yo(a),X=[];e:{var q=Zh.get(n);if(q!==void 0){var ue=Mc,he=n;switch(n){case"keypress":if(Ra(a)===0)break e;case"keydown":case"keyup":ue=Bw;break;case"focusin":he="focus",ue=$c;break;case"focusout":he="blur",ue=$c;break;case"beforeblur":case"afterblur":ue=$c;break;case"click":if(a.button===2)break e;case"auxclick":case"dblclick":case"mousedown":case"mousemove":case"mouseup":case"mouseout":case"mouseover":case"contextmenu":ue=Th;break;case"drag":case"dragend":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"dragstart":case"drop":ue=Ow;break;case"touchcancel":case"touchend":case"touchmove":case"touchstart":ue=Ww;break;case Yh:case Gh:case Xh:ue=Aw;break;case Jh:ue=qw;break;case"scroll":ue=Pw;break;case"wheel":ue=Yw;break;case"copy":case"cut":case"paste":ue=Iw;break;case"gotpointercapture":case"lostpointercapture":case"pointercancel":case"pointerdown":case"pointermove":case"pointerout":case"pointerover":case"pointerup":ue=jh}var pe=(o&4)!==0,nt=!pe&&n==="scroll",I=pe?q!==null?q+"Capture":null:q;pe=[];for(var L=z,D;L!==null;){D=L;var re=D.stateNode;if(D.tag===5&&re!==null&&(D=re,I!==null&&(re=Lt(L,I),re!=null&&pe.push(xi(L,re,D)))),nt)break;L=L.return}0<pe.length&&(q=new ue(q,he,null,a,Y),X.push({event:q,listeners:pe}))}}if((o&7)===0){e:{if(q=n==="mouseover"||n==="pointerover",ue=n==="mouseout"||n==="pointerout",q&&a!==ti&&(he=a.relatedTarget||a.fromElement)&&(co(he)||he[nr]))break e;if((ue||q)&&(q=Y.window===Y?Y:(q=Y.ownerDocument)?q.defaultView||q.parentWindow:window,ue?(he=a.relatedTarget||a.toElement,ue=z,he=he?co(he):null,he!==null&&(nt=lo(he),he!==nt||he.tag!==5&&he.tag!==6)&&(he=null)):(ue=null,he=z),ue!==he)){if(pe=Th,re="onMouseLeave",I="onMouseEnter",L="mouse",(n==="pointerout"||n==="pointerover")&&(pe=jh,re="onPointerLeave",I="onPointerEnter",L="pointer"),nt=ue==null?q:os(ue),D=he==null?q:os(he),q=new pe(re,L+"leave",ue,a,Y),q.target=nt,q.relatedTarget=D,re=null,co(Y)===z&&(pe=new pe(I,L+"enter",he,a,Y),pe.target=D,pe.relatedTarget=nt,re=pe),nt=re,ue&&he)t:{for(pe=ue,I=he,L=0,D=pe;D;D=ns(D))L++;for(D=0,re=I;re;re=ns(re))D++;for(;0<L-D;)pe=ns(pe),L--;for(;0<D-L;)I=ns(I),D--;for(;L--;){if(pe===I||I!==null&&pe===I.alternate)break t;pe=ns(pe),I=ns(I)}pe=null}else pe=null;ue!==null&&op(X,q,ue,pe,!1),he!==null&&nt!==null&&op(X,nt,he,pe,!0)}}e:{if(q=z?os(z):window,ue=q.nodeName&&q.nodeName.toLowerCase(),ue==="select"||ue==="input"&&q.type==="file")var ge=nb;else if(Mh(q))if(zh)ge=ib;else{ge=ob;var xe=rb}else(ue=q.nodeName)&&ue.toLowerCase()==="input"&&(q.type==="checkbox"||q.type==="radio")&&(ge=sb);if(ge&&(ge=ge(n,z))){Fh(X,ge,a,Y);break e}xe&&xe(n,q,z),n==="focusout"&&(xe=q._wrapperState)&&xe.controlled&&q.type==="number"&&jn(q,"number",q.value)}switch(xe=z?os(z):window,n){case"focusin":(Mh(xe)||xe.contentEditable==="true")&&(es=xe,Kc=z,gi=null);break;case"focusout":gi=Kc=es=null;break;case"mousedown":qc=!0;break;case"contextmenu":case"mouseup":case"dragend":qc=!1,qh(X,a,Y);break;case"selectionchange":if(cb)break;case"keydown":case"keyup":qh(X,a,Y)}var we;if(Bc)e:{switch(n){case"compositionstart":var Ce="onCompositionStart";break e;case"compositionend":Ce="onCompositionEnd";break e;case"compositionupdate":Ce="onCompositionUpdate";break e}Ce=void 0}else Zo?Ih(n,a)&&(Ce="onCompositionEnd"):n==="keydown"&&a.keyCode===229&&(Ce="onCompositionStart");Ce&&(_h&&a.locale!=="ko"&&(Zo||Ce!=="onCompositionStart"?Ce==="onCompositionEnd"&&Zo&&(we=Rh()):(Rr=Y,Dc="value"in Rr?Rr.value:Rr.textContent,Zo=!0)),xe=La(z,Ce),0<xe.length&&(Ce=new Oh(Ce,n,null,a,Y),X.push({event:Ce,listeners:xe}),we?Ce.data=we:(we=Dh(a),we!==null&&(Ce.data=we)))),(we=Xw?Jw(n,a):Zw(n,a))&&(z=La(z,"onBeforeInput"),0<z.length&&(Y=new Oh("onBeforeInput","beforeinput",null,a,Y),X.push({event:Y,listeners:z}),Y.data=we))}np(X,o)})}function xi(n,o,a){return{instance:n,listener:o,currentTarget:a}}function La(n,o){for(var a=o+"Capture",c=[];n!==null;){var f=n,m=f.stateNode;f.tag===5&&m!==null&&(f=m,m=Lt(n,a),m!=null&&c.unshift(xi(n,m,f)),m=Lt(n,o),m!=null&&c.push(xi(n,m,f))),n=n.return}return c}function ns(n){if(n===null)return null;do n=n.return;while(n&&n.tag!==5);return n||null}function op(n,o,a,c,f){for(var m=o._reactName,S=[];a!==null&&a!==c;){var N=a,O=N.alternate,z=N.stateNode;if(O!==null&&O===c)break;N.tag===5&&z!==null&&(N=z,f?(O=Lt(a,m),O!=null&&S.unshift(xi(a,O,N))):f||(O=Lt(a,m),O!=null&&S.push(xi(a,O,N)))),a=a.return}S.length!==0&&n.push({event:o,listeners:S})}var hb=/\r\n?/g,pb=/\u0000|\uFFFD/g;function sp(n){return(typeof n=="string"?n:""+n).replace(hb,`
`).replace(pb,"")}function Ia(n,o,a){if(o=sp(o),sp(n)!==o&&a)throw Error(r(425))}function Da(){}var Zc=null,eu=null;function tu(n,o){return n==="textarea"||n==="noscript"||typeof o.children=="string"||typeof o.children=="number"||typeof o.dangerouslySetInnerHTML=="object"&&o.dangerouslySetInnerHTML!==null&&o.dangerouslySetInnerHTML.__html!=null}var nu=typeof setTimeout=="function"?setTimeout:void 0,mb=typeof clearTimeout=="function"?clearTimeout:void 0,ip=typeof Promise=="function"?Promise:void 0,gb=typeof queueMicrotask=="function"?queueMicrotask:typeof ip<"u"?function(n){return ip.resolve(null).then(n).catch(yb)}:nu;function yb(n){setTimeout(function(){throw n})}function ru(n,o){var a=o,c=0;do{var f=a.nextSibling;if(n.removeChild(a),f&&f.nodeType===8)if(a=f.data,a==="/$"){if(c===0){n.removeChild(f),ci(o);return}c--}else a!=="$"&&a!=="$?"&&a!=="$!"||c++;a=f}while(a);ci(o)}function Tr(n){for(;n!=null;n=n.nextSibling){var o=n.nodeType;if(o===1||o===3)break;if(o===8){if(o=n.data,o==="$"||o==="$!"||o==="$?")break;if(o==="/$")return null}}return n}function ap(n){n=n.previousSibling;for(var o=0;n;){if(n.nodeType===8){var a=n.data;if(a==="$"||a==="$!"||a==="$?"){if(o===0)return n;o--}else a==="/$"&&o++}n=n.previousSibling}return null}var rs=Math.random().toString(36).slice(2),zn="__reactFiber$"+rs,wi="__reactProps$"+rs,nr="__reactContainer$"+rs,ou="__reactEvents$"+rs,vb="__reactListeners$"+rs,xb="__reactHandles$"+rs;function co(n){var o=n[zn];if(o)return o;for(var a=n.parentNode;a;){if(o=a[nr]||a[zn]){if(a=o.alternate,o.child!==null||a!==null&&a.child!==null)for(n=ap(n);n!==null;){if(a=n[zn])return a;n=ap(n)}return o}n=a,a=n.parentNode}return null}function bi(n){return n=n[zn]||n[nr],!n||n.tag!==5&&n.tag!==6&&n.tag!==13&&n.tag!==3?null:n}function os(n){if(n.tag===5||n.tag===6)return n.stateNode;throw Error(r(33))}function Ma(n){return n[wi]||null}var su=[],ss=-1;function Or(n){return{current:n}}function qe(n){0>ss||(n.current=su[ss],su[ss]=null,ss--)}function We(n,o){ss++,su[ss]=n.current,n.current=o}var jr={},yt=Or(jr),It=Or(!1),uo=jr;function is(n,o){var a=n.type.contextTypes;if(!a)return jr;var c=n.stateNode;if(c&&c.__reactInternalMemoizedUnmaskedChildContext===o)return c.__reactInternalMemoizedMaskedChildContext;var f={},m;for(m in a)f[m]=o[m];return c&&(n=n.stateNode,n.__reactInternalMemoizedUnmaskedChildContext=o,n.__reactInternalMemoizedMaskedChildContext=f),f}function Dt(n){return n=n.childContextTypes,n!=null}function Fa(){qe(It),qe(yt)}function lp(n,o,a){if(yt.current!==jr)throw Error(r(168));We(yt,o),We(It,a)}function cp(n,o,a){var c=n.stateNode;if(o=o.childContextTypes,typeof c.getChildContext!="function")return a;c=c.getChildContext();for(var f in c)if(!(f in o))throw Error(r(108,ee(n)||"Unknown",f));return Q({},a,c)}function za(n){return n=(n=n.stateNode)&&n.__reactInternalMemoizedMergedChildContext||jr,uo=yt.current,We(yt,n),We(It,It.current),!0}function up(n,o,a){var c=n.stateNode;if(!c)throw Error(r(169));a?(n=cp(n,o,uo),c.__reactInternalMemoizedMergedChildContext=n,qe(It),qe(yt),We(yt,n)):qe(It),We(It,a)}var rr=null,$a=!1,iu=!1;function dp(n){rr===null?rr=[n]:rr.push(n)}function wb(n){$a=!0,dp(n)}function _r(){if(!iu&&rr!==null){iu=!0;var n=0,o=Ue;try{var a=rr;for(Ue=1;n<a.length;n++){var c=a[n];do c=c(!0);while(c!==null)}rr=null,$a=!1}catch(f){throw rr!==null&&(rr=rr.slice(n+1)),hh(Pc,_r),f}finally{Ue=o,iu=!1}}return null}var as=[],ls=0,Ua=null,Ba=0,tn=[],nn=0,fo=null,or=1,sr="";function ho(n,o){as[ls++]=Ba,as[ls++]=Ua,Ua=n,Ba=o}function fp(n,o,a){tn[nn++]=or,tn[nn++]=sr,tn[nn++]=fo,fo=n;var c=or;n=sr;var f=32-mn(c)-1;c&=~(1<<f),a+=1;var m=32-mn(o)+f;if(30<m){var S=f-f%5;m=(c&(1<<S)-1).toString(32),c>>=S,f-=S,or=1<<32-mn(o)+f|a<<f|c,sr=m+n}else or=1<<m|a<<f|c,sr=n}function au(n){n.return!==null&&(ho(n,1),fp(n,1,0))}function lu(n){for(;n===Ua;)Ua=as[--ls],as[ls]=null,Ba=as[--ls],as[ls]=null;for(;n===fo;)fo=tn[--nn],tn[nn]=null,sr=tn[--nn],tn[nn]=null,or=tn[--nn],tn[nn]=null}var Qt=null,Yt=null,Ye=!1,yn=null;function hp(n,o){var a=an(5,null,null,0);a.elementType="DELETED",a.stateNode=o,a.return=n,o=n.deletions,o===null?(n.deletions=[a],n.flags|=16):o.push(a)}function pp(n,o){switch(n.tag){case 5:var a=n.type;return o=o.nodeType!==1||a.toLowerCase()!==o.nodeName.toLowerCase()?null:o,o!==null?(n.stateNode=o,Qt=n,Yt=Tr(o.firstChild),!0):!1;case 6:return o=n.pendingProps===""||o.nodeType!==3?null:o,o!==null?(n.stateNode=o,Qt=n,Yt=null,!0):!1;case 13:return o=o.nodeType!==8?null:o,o!==null?(a=fo!==null?{id:or,overflow:sr}:null,n.memoizedState={dehydrated:o,treeContext:a,retryLane:1073741824},a=an(18,null,null,0),a.stateNode=o,a.return=n,n.child=a,Qt=n,Yt=null,!0):!1;default:return!1}}function cu(n){return(n.mode&1)!==0&&(n.flags&128)===0}function uu(n){if(Ye){var o=Yt;if(o){var a=o;if(!pp(n,o)){if(cu(n))throw Error(r(418));o=Tr(a.nextSibling);var c=Qt;o&&pp(n,o)?hp(c,a):(n.flags=n.flags&-4097|2,Ye=!1,Qt=n)}}else{if(cu(n))throw Error(r(418));n.flags=n.flags&-4097|2,Ye=!1,Qt=n}}}function mp(n){for(n=n.return;n!==null&&n.tag!==5&&n.tag!==3&&n.tag!==13;)n=n.return;Qt=n}function Ha(n){if(n!==Qt)return!1;if(!Ye)return mp(n),Ye=!0,!1;var o;if((o=n.tag!==3)&&!(o=n.tag!==5)&&(o=n.type,o=o!=="head"&&o!=="body"&&!tu(n.type,n.memoizedProps)),o&&(o=Yt)){if(cu(n))throw gp(),Error(r(418));for(;o;)hp(n,o),o=Tr(o.nextSibling)}if(mp(n),n.tag===13){if(n=n.memoizedState,n=n!==null?n.dehydrated:null,!n)throw Error(r(317));e:{for(n=n.nextSibling,o=0;n;){if(n.nodeType===8){var a=n.data;if(a==="/$"){if(o===0){Yt=Tr(n.nextSibling);break e}o--}else a!=="$"&&a!=="$!"&&a!=="$?"||o++}n=n.nextSibling}Yt=null}}else Yt=Qt?Tr(n.stateNode.nextSibling):null;return!0}function gp(){for(var n=Yt;n;)n=Tr(n.nextSibling)}function cs(){Yt=Qt=null,Ye=!1}function du(n){yn===null?yn=[n]:yn.push(n)}var bb=A.ReactCurrentBatchConfig;function Si(n,o,a){if(n=a.ref,n!==null&&typeof n!="function"&&typeof n!="object"){if(a._owner){if(a=a._owner,a){if(a.tag!==1)throw Error(r(309));var c=a.stateNode}if(!c)throw Error(r(147,n));var f=c,m=""+n;return o!==null&&o.ref!==null&&typeof o.ref=="function"&&o.ref._stringRef===m?o.ref:(o=function(S){var N=f.refs;S===null?delete N[m]:N[m]=S},o._stringRef=m,o)}if(typeof n!="string")throw Error(r(284));if(!a._owner)throw Error(r(290,n))}return n}function Va(n,o){throw n=Object.prototype.toString.call(o),Error(r(31,n==="[object Object]"?"object with keys {"+Object.keys(o).join(", ")+"}":n))}function yp(n){var o=n._init;return o(n._payload)}function vp(n){function o(I,L){if(n){var D=I.deletions;D===null?(I.deletions=[L],I.flags|=16):D.push(L)}}function a(I,L){if(!n)return null;for(;L!==null;)o(I,L),L=L.sibling;return null}function c(I,L){for(I=new Map;L!==null;)L.key!==null?I.set(L.key,L):I.set(L.index,L),L=L.sibling;return I}function f(I,L){return I=$r(I,L),I.index=0,I.sibling=null,I}function m(I,L,D){return I.index=D,n?(D=I.alternate,D!==null?(D=D.index,D<L?(I.flags|=2,L):D):(I.flags|=2,L)):(I.flags|=1048576,L)}function S(I){return n&&I.alternate===null&&(I.flags|=2),I}function N(I,L,D,re){return L===null||L.tag!==6?(L=nd(D,I.mode,re),L.return=I,L):(L=f(L,D),L.return=I,L)}function O(I,L,D,re){var ge=D.type;return ge===B?Y(I,L,D.props.children,re,D.key):L!==null&&(L.elementType===ge||typeof ge=="object"&&ge!==null&&ge.$$typeof===ne&&yp(ge)===L.type)?(re=f(L,D.props),re.ref=Si(I,L,D),re.return=I,re):(re=pl(D.type,D.key,D.props,null,I.mode,re),re.ref=Si(I,L,D),re.return=I,re)}function z(I,L,D,re){return L===null||L.tag!==4||L.stateNode.containerInfo!==D.containerInfo||L.stateNode.implementation!==D.implementation?(L=rd(D,I.mode,re),L.return=I,L):(L=f(L,D.children||[]),L.return=I,L)}function Y(I,L,D,re,ge){return L===null||L.tag!==7?(L=bo(D,I.mode,re,ge),L.return=I,L):(L=f(L,D),L.return=I,L)}function X(I,L,D){if(typeof L=="string"&&L!==""||typeof L=="number")return L=nd(""+L,I.mode,D),L.return=I,L;if(typeof L=="object"&&L!==null){switch(L.$$typeof){case F:return D=pl(L.type,L.key,L.props,null,I.mode,D),D.ref=Si(I,null,L),D.return=I,D;case V:return L=rd(L,I.mode,D),L.return=I,L;case ne:var re=L._init;return X(I,re(L._payload),D)}if(br(L)||H(L))return L=bo(L,I.mode,D,null),L.return=I,L;Va(I,L)}return null}function q(I,L,D,re){var ge=L!==null?L.key:null;if(typeof D=="string"&&D!==""||typeof D=="number")return ge!==null?null:N(I,L,""+D,re);if(typeof D=="object"&&D!==null){switch(D.$$typeof){case F:return D.key===ge?O(I,L,D,re):null;case V:return D.key===ge?z(I,L,D,re):null;case ne:return ge=D._init,q(I,L,ge(D._payload),re)}if(br(D)||H(D))return ge!==null?null:Y(I,L,D,re,null);Va(I,D)}return null}function ue(I,L,D,re,ge){if(typeof re=="string"&&re!==""||typeof re=="number")return I=I.get(D)||null,N(L,I,""+re,ge);if(typeof re=="object"&&re!==null){switch(re.$$typeof){case F:return I=I.get(re.key===null?D:re.key)||null,O(L,I,re,ge);case V:return I=I.get(re.key===null?D:re.key)||null,z(L,I,re,ge);case ne:var xe=re._init;return ue(I,L,D,xe(re._payload),ge)}if(br(re)||H(re))return I=I.get(D)||null,Y(L,I,re,ge,null);Va(L,re)}return null}function he(I,L,D,re){for(var ge=null,xe=null,we=L,Ce=L=0,ut=null;we!==null&&Ce<D.length;Ce++){we.index>Ce?(ut=we,we=null):ut=we.sibling;var De=q(I,we,D[Ce],re);if(De===null){we===null&&(we=ut);break}n&&we&&De.alternate===null&&o(I,we),L=m(De,L,Ce),xe===null?ge=De:xe.sibling=De,xe=De,we=ut}if(Ce===D.length)return a(I,we),Ye&&ho(I,Ce),ge;if(we===null){for(;Ce<D.length;Ce++)we=X(I,D[Ce],re),we!==null&&(L=m(we,L,Ce),xe===null?ge=we:xe.sibling=we,xe=we);return Ye&&ho(I,Ce),ge}for(we=c(I,we);Ce<D.length;Ce++)ut=ue(we,I,Ce,D[Ce],re),ut!==null&&(n&&ut.alternate!==null&&we.delete(ut.key===null?Ce:ut.key),L=m(ut,L,Ce),xe===null?ge=ut:xe.sibling=ut,xe=ut);return n&&we.forEach(function(Ur){return o(I,Ur)}),Ye&&ho(I,Ce),ge}function pe(I,L,D,re){var ge=H(D);if(typeof ge!="function")throw Error(r(150));if(D=ge.call(D),D==null)throw Error(r(151));for(var xe=ge=null,we=L,Ce=L=0,ut=null,De=D.next();we!==null&&!De.done;Ce++,De=D.next()){we.index>Ce?(ut=we,we=null):ut=we.sibling;var Ur=q(I,we,De.value,re);if(Ur===null){we===null&&(we=ut);break}n&&we&&Ur.alternate===null&&o(I,we),L=m(Ur,L,Ce),xe===null?ge=Ur:xe.sibling=Ur,xe=Ur,we=ut}if(De.done)return a(I,we),Ye&&ho(I,Ce),ge;if(we===null){for(;!De.done;Ce++,De=D.next())De=X(I,De.value,re),De!==null&&(L=m(De,L,Ce),xe===null?ge=De:xe.sibling=De,xe=De);return Ye&&ho(I,Ce),ge}for(we=c(I,we);!De.done;Ce++,De=D.next())De=ue(we,I,Ce,De.value,re),De!==null&&(n&&De.alternate!==null&&we.delete(De.key===null?Ce:De.key),L=m(De,L,Ce),xe===null?ge=De:xe.sibling=De,xe=De);return n&&we.forEach(function(e1){return o(I,e1)}),Ye&&ho(I,Ce),ge}function nt(I,L,D,re){if(typeof D=="object"&&D!==null&&D.type===B&&D.key===null&&(D=D.props.children),typeof D=="object"&&D!==null){switch(D.$$typeof){case F:e:{for(var ge=D.key,xe=L;xe!==null;){if(xe.key===ge){if(ge=D.type,ge===B){if(xe.tag===7){a(I,xe.sibling),L=f(xe,D.props.children),L.return=I,I=L;break e}}else if(xe.elementType===ge||typeof ge=="object"&&ge!==null&&ge.$$typeof===ne&&yp(ge)===xe.type){a(I,xe.sibling),L=f(xe,D.props),L.ref=Si(I,xe,D),L.return=I,I=L;break e}a(I,xe);break}else o(I,xe);xe=xe.sibling}D.type===B?(L=bo(D.props.children,I.mode,re,D.key),L.return=I,I=L):(re=pl(D.type,D.key,D.props,null,I.mode,re),re.ref=Si(I,L,D),re.return=I,I=re)}return S(I);case V:e:{for(xe=D.key;L!==null;){if(L.key===xe)if(L.tag===4&&L.stateNode.containerInfo===D.containerInfo&&L.stateNode.implementation===D.implementation){a(I,L.sibling),L=f(L,D.children||[]),L.return=I,I=L;break e}else{a(I,L);break}else o(I,L);L=L.sibling}L=rd(D,I.mode,re),L.return=I,I=L}return S(I);case ne:return xe=D._init,nt(I,L,xe(D._payload),re)}if(br(D))return he(I,L,D,re);if(H(D))return pe(I,L,D,re);Va(I,D)}return typeof D=="string"&&D!==""||typeof D=="number"?(D=""+D,L!==null&&L.tag===6?(a(I,L.sibling),L=f(L,D),L.return=I,I=L):(a(I,L),L=nd(D,I.mode,re),L.return=I,I=L),S(I)):a(I,L)}return nt}var us=vp(!0),xp=vp(!1),Wa=Or(null),Ka=null,ds=null,fu=null;function hu(){fu=ds=Ka=null}function pu(n){var o=Wa.current;qe(Wa),n._currentValue=o}function mu(n,o,a){for(;n!==null;){var c=n.alternate;if((n.childLanes&o)!==o?(n.childLanes|=o,c!==null&&(c.childLanes|=o)):c!==null&&(c.childLanes&o)!==o&&(c.childLanes|=o),n===a)break;n=n.return}}function fs(n,o){Ka=n,fu=ds=null,n=n.dependencies,n!==null&&n.firstContext!==null&&((n.lanes&o)!==0&&(Mt=!0),n.firstContext=null)}function rn(n){var o=n._currentValue;if(fu!==n)if(n={context:n,memoizedValue:o,next:null},ds===null){if(Ka===null)throw Error(r(308));ds=n,Ka.dependencies={lanes:0,firstContext:n}}else ds=ds.next=n;return o}var po=null;function gu(n){po===null?po=[n]:po.push(n)}function wp(n,o,a,c){var f=o.interleaved;return f===null?(a.next=a,gu(o)):(a.next=f.next,f.next=a),o.interleaved=a,ir(n,c)}function ir(n,o){n.lanes|=o;var a=n.alternate;for(a!==null&&(a.lanes|=o),a=n,n=n.return;n!==null;)n.childLanes|=o,a=n.alternate,a!==null&&(a.childLanes|=o),a=n,n=n.return;return a.tag===3?a.stateNode:null}var Ar=!1;function yu(n){n.updateQueue={baseState:n.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function bp(n,o){n=n.updateQueue,o.updateQueue===n&&(o.updateQueue={baseState:n.baseState,firstBaseUpdate:n.firstBaseUpdate,lastBaseUpdate:n.lastBaseUpdate,shared:n.shared,effects:n.effects})}function ar(n,o){return{eventTime:n,lane:o,tag:0,payload:null,callback:null,next:null}}function Lr(n,o,a){var c=n.updateQueue;if(c===null)return null;if(c=c.shared,(Le&2)!==0){var f=c.pending;return f===null?o.next=o:(o.next=f.next,f.next=o),c.pending=o,ir(n,a)}return f=c.interleaved,f===null?(o.next=o,gu(c)):(o.next=f.next,f.next=o),c.interleaved=o,ir(n,a)}function qa(n,o,a){if(o=o.updateQueue,o!==null&&(o=o.shared,(a&4194240)!==0)){var c=o.lanes;c&=n.pendingLanes,a|=c,o.lanes=a,jc(n,a)}}function Sp(n,o){var a=n.updateQueue,c=n.alternate;if(c!==null&&(c=c.updateQueue,a===c)){var f=null,m=null;if(a=a.firstBaseUpdate,a!==null){do{var S={eventTime:a.eventTime,lane:a.lane,tag:a.tag,payload:a.payload,callback:a.callback,next:null};m===null?f=m=S:m=m.next=S,a=a.next}while(a!==null);m===null?f=m=o:m=m.next=o}else f=m=o;a={baseState:c.baseState,firstBaseUpdate:f,lastBaseUpdate:m,shared:c.shared,effects:c.effects},n.updateQueue=a;return}n=a.lastBaseUpdate,n===null?a.firstBaseUpdate=o:n.next=o,a.lastBaseUpdate=o}function Qa(n,o,a,c){var f=n.updateQueue;Ar=!1;var m=f.firstBaseUpdate,S=f.lastBaseUpdate,N=f.shared.pending;if(N!==null){f.shared.pending=null;var O=N,z=O.next;O.next=null,S===null?m=z:S.next=z,S=O;var Y=n.alternate;Y!==null&&(Y=Y.updateQueue,N=Y.lastBaseUpdate,N!==S&&(N===null?Y.firstBaseUpdate=z:N.next=z,Y.lastBaseUpdate=O))}if(m!==null){var X=f.baseState;S=0,Y=z=O=null,N=m;do{var q=N.lane,ue=N.eventTime;if((c&q)===q){Y!==null&&(Y=Y.next={eventTime:ue,lane:0,tag:N.tag,payload:N.payload,callback:N.callback,next:null});e:{var he=n,pe=N;switch(q=o,ue=a,pe.tag){case 1:if(he=pe.payload,typeof he=="function"){X=he.call(ue,X,q);break e}X=he;break e;case 3:he.flags=he.flags&-65537|128;case 0:if(he=pe.payload,q=typeof he=="function"?he.call(ue,X,q):he,q==null)break e;X=Q({},X,q);break e;case 2:Ar=!0}}N.callback!==null&&N.lane!==0&&(n.flags|=64,q=f.effects,q===null?f.effects=[N]:q.push(N))}else ue={eventTime:ue,lane:q,tag:N.tag,payload:N.payload,callback:N.callback,next:null},Y===null?(z=Y=ue,O=X):Y=Y.next=ue,S|=q;if(N=N.next,N===null){if(N=f.shared.pending,N===null)break;q=N,N=q.next,q.next=null,f.lastBaseUpdate=q,f.shared.pending=null}}while(!0);if(Y===null&&(O=X),f.baseState=O,f.firstBaseUpdate=z,f.lastBaseUpdate=Y,o=f.shared.interleaved,o!==null){f=o;do S|=f.lane,f=f.next;while(f!==o)}else m===null&&(f.shared.lanes=0);yo|=S,n.lanes=S,n.memoizedState=X}}function Cp(n,o,a){if(n=o.effects,o.effects=null,n!==null)for(o=0;o<n.length;o++){var c=n[o],f=c.callback;if(f!==null){if(c.callback=null,c=a,typeof f!="function")throw Error(r(191,f));f.call(c)}}}var Ci={},$n=Or(Ci),Ei=Or(Ci),ki=Or(Ci);function mo(n){if(n===Ci)throw Error(r(174));return n}function vu(n,o){switch(We(ki,o),We(Ei,n),We($n,Ci),n=o.nodeType,n){case 9:case 11:o=(o=o.documentElement)?o.namespaceURI:An(null,"");break;default:n=n===8?o.parentNode:o,o=n.namespaceURI||null,n=n.tagName,o=An(o,n)}qe($n),We($n,o)}function hs(){qe($n),qe(Ei),qe(ki)}function Ep(n){mo(ki.current);var o=mo($n.current),a=An(o,n.type);o!==a&&(We(Ei,n),We($n,a))}function xu(n){Ei.current===n&&(qe($n),qe(Ei))}var Ge=Or(0);function Ya(n){for(var o=n;o!==null;){if(o.tag===13){var a=o.memoizedState;if(a!==null&&(a=a.dehydrated,a===null||a.data==="$?"||a.data==="$!"))return o}else if(o.tag===19&&o.memoizedProps.revealOrder!==void 0){if((o.flags&128)!==0)return o}else if(o.child!==null){o.child.return=o,o=o.child;continue}if(o===n)break;for(;o.sibling===null;){if(o.return===null||o.return===n)return null;o=o.return}o.sibling.return=o.return,o=o.sibling}return null}var wu=[];function bu(){for(var n=0;n<wu.length;n++)wu[n]._workInProgressVersionPrimary=null;wu.length=0}var Ga=A.ReactCurrentDispatcher,Su=A.ReactCurrentBatchConfig,go=0,Xe=null,st=null,lt=null,Xa=!1,Ni=!1,Ri=0,Sb=0;function vt(){throw Error(r(321))}function Cu(n,o){if(o===null)return!1;for(var a=0;a<o.length&&a<n.length;a++)if(!gn(n[a],o[a]))return!1;return!0}function Eu(n,o,a,c,f,m){if(go=m,Xe=o,o.memoizedState=null,o.updateQueue=null,o.lanes=0,Ga.current=n===null||n.memoizedState===null?Nb:Rb,n=a(c,f),Ni){m=0;do{if(Ni=!1,Ri=0,25<=m)throw Error(r(301));m+=1,lt=st=null,o.updateQueue=null,Ga.current=Pb,n=a(c,f)}while(Ni)}if(Ga.current=el,o=st!==null&&st.next!==null,go=0,lt=st=Xe=null,Xa=!1,o)throw Error(r(300));return n}function ku(){var n=Ri!==0;return Ri=0,n}function Un(){var n={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return lt===null?Xe.memoizedState=lt=n:lt=lt.next=n,lt}function on(){if(st===null){var n=Xe.alternate;n=n!==null?n.memoizedState:null}else n=st.next;var o=lt===null?Xe.memoizedState:lt.next;if(o!==null)lt=o,st=n;else{if(n===null)throw Error(r(310));st=n,n={memoizedState:st.memoizedState,baseState:st.baseState,baseQueue:st.baseQueue,queue:st.queue,next:null},lt===null?Xe.memoizedState=lt=n:lt=lt.next=n}return lt}function Pi(n,o){return typeof o=="function"?o(n):o}function Nu(n){var o=on(),a=o.queue;if(a===null)throw Error(r(311));a.lastRenderedReducer=n;var c=st,f=c.baseQueue,m=a.pending;if(m!==null){if(f!==null){var S=f.next;f.next=m.next,m.next=S}c.baseQueue=f=m,a.pending=null}if(f!==null){m=f.next,c=c.baseState;var N=S=null,O=null,z=m;do{var Y=z.lane;if((go&Y)===Y)O!==null&&(O=O.next={lane:0,action:z.action,hasEagerState:z.hasEagerState,eagerState:z.eagerState,next:null}),c=z.hasEagerState?z.eagerState:n(c,z.action);else{var X={lane:Y,action:z.action,hasEagerState:z.hasEagerState,eagerState:z.eagerState,next:null};O===null?(N=O=X,S=c):O=O.next=X,Xe.lanes|=Y,yo|=Y}z=z.next}while(z!==null&&z!==m);O===null?S=c:O.next=N,gn(c,o.memoizedState)||(Mt=!0),o.memoizedState=c,o.baseState=S,o.baseQueue=O,a.lastRenderedState=c}if(n=a.interleaved,n!==null){f=n;do m=f.lane,Xe.lanes|=m,yo|=m,f=f.next;while(f!==n)}else f===null&&(a.lanes=0);return[o.memoizedState,a.dispatch]}function Ru(n){var o=on(),a=o.queue;if(a===null)throw Error(r(311));a.lastRenderedReducer=n;var c=a.dispatch,f=a.pending,m=o.memoizedState;if(f!==null){a.pending=null;var S=f=f.next;do m=n(m,S.action),S=S.next;while(S!==f);gn(m,o.memoizedState)||(Mt=!0),o.memoizedState=m,o.baseQueue===null&&(o.baseState=m),a.lastRenderedState=m}return[m,c]}function kp(){}function Np(n,o){var a=Xe,c=on(),f=o(),m=!gn(c.memoizedState,f);if(m&&(c.memoizedState=f,Mt=!0),c=c.queue,Pu(Tp.bind(null,a,c,n),[n]),c.getSnapshot!==o||m||lt!==null&&lt.memoizedState.tag&1){if(a.flags|=2048,Ti(9,Pp.bind(null,a,c,f,o),void 0,null),ct===null)throw Error(r(349));(go&30)!==0||Rp(a,o,f)}return f}function Rp(n,o,a){n.flags|=16384,n={getSnapshot:o,value:a},o=Xe.updateQueue,o===null?(o={lastEffect:null,stores:null},Xe.updateQueue=o,o.stores=[n]):(a=o.stores,a===null?o.stores=[n]:a.push(n))}function Pp(n,o,a,c){o.value=a,o.getSnapshot=c,Op(o)&&jp(n)}function Tp(n,o,a){return a(function(){Op(o)&&jp(n)})}function Op(n){var o=n.getSnapshot;n=n.value;try{var a=o();return!gn(n,a)}catch{return!0}}function jp(n){var o=ir(n,1);o!==null&&bn(o,n,1,-1)}function _p(n){var o=Un();return typeof n=="function"&&(n=n()),o.memoizedState=o.baseState=n,n={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:Pi,lastRenderedState:n},o.queue=n,n=n.dispatch=kb.bind(null,Xe,n),[o.memoizedState,n]}function Ti(n,o,a,c){return n={tag:n,create:o,destroy:a,deps:c,next:null},o=Xe.updateQueue,o===null?(o={lastEffect:null,stores:null},Xe.updateQueue=o,o.lastEffect=n.next=n):(a=o.lastEffect,a===null?o.lastEffect=n.next=n:(c=a.next,a.next=n,n.next=c,o.lastEffect=n)),n}function Ap(){return on().memoizedState}function Ja(n,o,a,c){var f=Un();Xe.flags|=n,f.memoizedState=Ti(1|o,a,void 0,c===void 0?null:c)}function Za(n,o,a,c){var f=on();c=c===void 0?null:c;var m=void 0;if(st!==null){var S=st.memoizedState;if(m=S.destroy,c!==null&&Cu(c,S.deps)){f.memoizedState=Ti(o,a,m,c);return}}Xe.flags|=n,f.memoizedState=Ti(1|o,a,m,c)}function Lp(n,o){return Ja(8390656,8,n,o)}function Pu(n,o){return Za(2048,8,n,o)}function Ip(n,o){return Za(4,2,n,o)}function Dp(n,o){return Za(4,4,n,o)}function Mp(n,o){if(typeof o=="function")return n=n(),o(n),function(){o(null)};if(o!=null)return n=n(),o.current=n,function(){o.current=null}}function Fp(n,o,a){return a=a!=null?a.concat([n]):null,Za(4,4,Mp.bind(null,o,n),a)}function Tu(){}function zp(n,o){var a=on();o=o===void 0?null:o;var c=a.memoizedState;return c!==null&&o!==null&&Cu(o,c[1])?c[0]:(a.memoizedState=[n,o],n)}function $p(n,o){var a=on();o=o===void 0?null:o;var c=a.memoizedState;return c!==null&&o!==null&&Cu(o,c[1])?c[0]:(n=n(),a.memoizedState=[n,o],n)}function Up(n,o,a){return(go&21)===0?(n.baseState&&(n.baseState=!1,Mt=!0),n.memoizedState=a):(gn(a,o)||(a=yh(),Xe.lanes|=a,yo|=a,n.baseState=!0),o)}function Cb(n,o){var a=Ue;Ue=a!==0&&4>a?a:4,n(!0);var c=Su.transition;Su.transition={};try{n(!1),o()}finally{Ue=a,Su.transition=c}}function Bp(){return on().memoizedState}function Eb(n,o,a){var c=Fr(n);if(a={lane:c,action:a,hasEagerState:!1,eagerState:null,next:null},Hp(n))Vp(o,a);else if(a=wp(n,o,a,c),a!==null){var f=Rt();bn(a,n,c,f),Wp(a,o,c)}}function kb(n,o,a){var c=Fr(n),f={lane:c,action:a,hasEagerState:!1,eagerState:null,next:null};if(Hp(n))Vp(o,f);else{var m=n.alternate;if(n.lanes===0&&(m===null||m.lanes===0)&&(m=o.lastRenderedReducer,m!==null))try{var S=o.lastRenderedState,N=m(S,a);if(f.hasEagerState=!0,f.eagerState=N,gn(N,S)){var O=o.interleaved;O===null?(f.next=f,gu(o)):(f.next=O.next,O.next=f),o.interleaved=f;return}}catch{}finally{}a=wp(n,o,f,c),a!==null&&(f=Rt(),bn(a,n,c,f),Wp(a,o,c))}}function Hp(n){var o=n.alternate;return n===Xe||o!==null&&o===Xe}function Vp(n,o){Ni=Xa=!0;var a=n.pending;a===null?o.next=o:(o.next=a.next,a.next=o),n.pending=o}function Wp(n,o,a){if((a&4194240)!==0){var c=o.lanes;c&=n.pendingLanes,a|=c,o.lanes=a,jc(n,a)}}var el={readContext:rn,useCallback:vt,useContext:vt,useEffect:vt,useImperativeHandle:vt,useInsertionEffect:vt,useLayoutEffect:vt,useMemo:vt,useReducer:vt,useRef:vt,useState:vt,useDebugValue:vt,useDeferredValue:vt,useTransition:vt,useMutableSource:vt,useSyncExternalStore:vt,useId:vt,unstable_isNewReconciler:!1},Nb={readContext:rn,useCallback:function(n,o){return Un().memoizedState=[n,o===void 0?null:o],n},useContext:rn,useEffect:Lp,useImperativeHandle:function(n,o,a){return a=a!=null?a.concat([n]):null,Ja(4194308,4,Mp.bind(null,o,n),a)},useLayoutEffect:function(n,o){return Ja(4194308,4,n,o)},useInsertionEffect:function(n,o){return Ja(4,2,n,o)},useMemo:function(n,o){var a=Un();return o=o===void 0?null:o,n=n(),a.memoizedState=[n,o],n},useReducer:function(n,o,a){var c=Un();return o=a!==void 0?a(o):o,c.memoizedState=c.baseState=o,n={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:n,lastRenderedState:o},c.queue=n,n=n.dispatch=Eb.bind(null,Xe,n),[c.memoizedState,n]},useRef:function(n){var o=Un();return n={current:n},o.memoizedState=n},useState:_p,useDebugValue:Tu,useDeferredValue:function(n){return Un().memoizedState=n},useTransition:function(){var n=_p(!1),o=n[0];return n=Cb.bind(null,n[1]),Un().memoizedState=n,[o,n]},useMutableSource:function(){},useSyncExternalStore:function(n,o,a){var c=Xe,f=Un();if(Ye){if(a===void 0)throw Error(r(407));a=a()}else{if(a=o(),ct===null)throw Error(r(349));(go&30)!==0||Rp(c,o,a)}f.memoizedState=a;var m={value:a,getSnapshot:o};return f.queue=m,Lp(Tp.bind(null,c,m,n),[n]),c.flags|=2048,Ti(9,Pp.bind(null,c,m,a,o),void 0,null),a},useId:function(){var n=Un(),o=ct.identifierPrefix;if(Ye){var a=sr,c=or;a=(c&~(1<<32-mn(c)-1)).toString(32)+a,o=":"+o+"R"+a,a=Ri++,0<a&&(o+="H"+a.toString(32)),o+=":"}else a=Sb++,o=":"+o+"r"+a.toString(32)+":";return n.memoizedState=o},unstable_isNewReconciler:!1},Rb={readContext:rn,useCallback:zp,useContext:rn,useEffect:Pu,useImperativeHandle:Fp,useInsertionEffect:Ip,useLayoutEffect:Dp,useMemo:$p,useReducer:Nu,useRef:Ap,useState:function(){return Nu(Pi)},useDebugValue:Tu,useDeferredValue:function(n){var o=on();return Up(o,st.memoizedState,n)},useTransition:function(){var n=Nu(Pi)[0],o=on().memoizedState;return[n,o]},useMutableSource:kp,useSyncExternalStore:Np,useId:Bp,unstable_isNewReconciler:!1},Pb={readContext:rn,useCallback:zp,useContext:rn,useEffect:Pu,useImperativeHandle:Fp,useInsertionEffect:Ip,useLayoutEffect:Dp,useMemo:$p,useReducer:Ru,useRef:Ap,useState:function(){return Ru(Pi)},useDebugValue:Tu,useDeferredValue:function(n){var o=on();return st===null?o.memoizedState=n:Up(o,st.memoizedState,n)},useTransition:function(){var n=Ru(Pi)[0],o=on().memoizedState;return[n,o]},useMutableSource:kp,useSyncExternalStore:Np,useId:Bp,unstable_isNewReconciler:!1};function vn(n,o){if(n&&n.defaultProps){o=Q({},o),n=n.defaultProps;for(var a in n)o[a]===void 0&&(o[a]=n[a]);return o}return o}function Ou(n,o,a,c){o=n.memoizedState,a=a(c,o),a=a==null?o:Q({},o,a),n.memoizedState=a,n.lanes===0&&(n.updateQueue.baseState=a)}var tl={isMounted:function(n){return(n=n._reactInternals)?lo(n)===n:!1},enqueueSetState:function(n,o,a){n=n._reactInternals;var c=Rt(),f=Fr(n),m=ar(c,f);m.payload=o,a!=null&&(m.callback=a),o=Lr(n,m,f),o!==null&&(bn(o,n,f,c),qa(o,n,f))},enqueueReplaceState:function(n,o,a){n=n._reactInternals;var c=Rt(),f=Fr(n),m=ar(c,f);m.tag=1,m.payload=o,a!=null&&(m.callback=a),o=Lr(n,m,f),o!==null&&(bn(o,n,f,c),qa(o,n,f))},enqueueForceUpdate:function(n,o){n=n._reactInternals;var a=Rt(),c=Fr(n),f=ar(a,c);f.tag=2,o!=null&&(f.callback=o),o=Lr(n,f,c),o!==null&&(bn(o,n,c,a),qa(o,n,c))}};function Kp(n,o,a,c,f,m,S){return n=n.stateNode,typeof n.shouldComponentUpdate=="function"?n.shouldComponentUpdate(c,m,S):o.prototype&&o.prototype.isPureReactComponent?!mi(a,c)||!mi(f,m):!0}function qp(n,o,a){var c=!1,f=jr,m=o.contextType;return typeof m=="object"&&m!==null?m=rn(m):(f=Dt(o)?uo:yt.current,c=o.contextTypes,m=(c=c!=null)?is(n,f):jr),o=new o(a,m),n.memoizedState=o.state!==null&&o.state!==void 0?o.state:null,o.updater=tl,n.stateNode=o,o._reactInternals=n,c&&(n=n.stateNode,n.__reactInternalMemoizedUnmaskedChildContext=f,n.__reactInternalMemoizedMaskedChildContext=m),o}function Qp(n,o,a,c){n=o.state,typeof o.componentWillReceiveProps=="function"&&o.componentWillReceiveProps(a,c),typeof o.UNSAFE_componentWillReceiveProps=="function"&&o.UNSAFE_componentWillReceiveProps(a,c),o.state!==n&&tl.enqueueReplaceState(o,o.state,null)}function ju(n,o,a,c){var f=n.stateNode;f.props=a,f.state=n.memoizedState,f.refs={},yu(n);var m=o.contextType;typeof m=="object"&&m!==null?f.context=rn(m):(m=Dt(o)?uo:yt.current,f.context=is(n,m)),f.state=n.memoizedState,m=o.getDerivedStateFromProps,typeof m=="function"&&(Ou(n,o,m,a),f.state=n.memoizedState),typeof o.getDerivedStateFromProps=="function"||typeof f.getSnapshotBeforeUpdate=="function"||typeof f.UNSAFE_componentWillMount!="function"&&typeof f.componentWillMount!="function"||(o=f.state,typeof f.componentWillMount=="function"&&f.componentWillMount(),typeof f.UNSAFE_componentWillMount=="function"&&f.UNSAFE_componentWillMount(),o!==f.state&&tl.enqueueReplaceState(f,f.state,null),Qa(n,a,f,c),f.state=n.memoizedState),typeof f.componentDidMount=="function"&&(n.flags|=4194308)}function ps(n,o){try{var a="",c=o;do a+=me(c),c=c.return;while(c);var f=a}catch(m){f=`
Error generating stack: `+m.message+`
`+m.stack}return{value:n,source:o,stack:f,digest:null}}function _u(n,o,a){return{value:n,source:null,stack:a??null,digest:o??null}}function Au(n,o){try{console.error(o.value)}catch(a){setTimeout(function(){throw a})}}var Tb=typeof WeakMap=="function"?WeakMap:Map;function Yp(n,o,a){a=ar(-1,a),a.tag=3,a.payload={element:null};var c=o.value;return a.callback=function(){ll||(ll=!0,Qu=c),Au(n,o)},a}function Gp(n,o,a){a=ar(-1,a),a.tag=3;var c=n.type.getDerivedStateFromError;if(typeof c=="function"){var f=o.value;a.payload=function(){return c(f)},a.callback=function(){Au(n,o)}}var m=n.stateNode;return m!==null&&typeof m.componentDidCatch=="function"&&(a.callback=function(){Au(n,o),typeof c!="function"&&(Dr===null?Dr=new Set([this]):Dr.add(this));var S=o.stack;this.componentDidCatch(o.value,{componentStack:S!==null?S:""})}),a}function Xp(n,o,a){var c=n.pingCache;if(c===null){c=n.pingCache=new Tb;var f=new Set;c.set(o,f)}else f=c.get(o),f===void 0&&(f=new Set,c.set(o,f));f.has(a)||(f.add(a),n=Hb.bind(null,n,o,a),o.then(n,n))}function Jp(n){do{var o;if((o=n.tag===13)&&(o=n.memoizedState,o=o!==null?o.dehydrated!==null:!0),o)return n;n=n.return}while(n!==null);return null}function Zp(n,o,a,c,f){return(n.mode&1)===0?(n===o?n.flags|=65536:(n.flags|=128,a.flags|=131072,a.flags&=-52805,a.tag===1&&(a.alternate===null?a.tag=17:(o=ar(-1,1),o.tag=2,Lr(a,o,1))),a.lanes|=1),n):(n.flags|=65536,n.lanes=f,n)}var Ob=A.ReactCurrentOwner,Mt=!1;function Nt(n,o,a,c){o.child=n===null?xp(o,null,a,c):us(o,n.child,a,c)}function em(n,o,a,c,f){a=a.render;var m=o.ref;return fs(o,f),c=Eu(n,o,a,c,m,f),a=ku(),n!==null&&!Mt?(o.updateQueue=n.updateQueue,o.flags&=-2053,n.lanes&=~f,lr(n,o,f)):(Ye&&a&&au(o),o.flags|=1,Nt(n,o,c,f),o.child)}function tm(n,o,a,c,f){if(n===null){var m=a.type;return typeof m=="function"&&!td(m)&&m.defaultProps===void 0&&a.compare===null&&a.defaultProps===void 0?(o.tag=15,o.type=m,nm(n,o,m,c,f)):(n=pl(a.type,null,c,o,o.mode,f),n.ref=o.ref,n.return=o,o.child=n)}if(m=n.child,(n.lanes&f)===0){var S=m.memoizedProps;if(a=a.compare,a=a!==null?a:mi,a(S,c)&&n.ref===o.ref)return lr(n,o,f)}return o.flags|=1,n=$r(m,c),n.ref=o.ref,n.return=o,o.child=n}function nm(n,o,a,c,f){if(n!==null){var m=n.memoizedProps;if(mi(m,c)&&n.ref===o.ref)if(Mt=!1,o.pendingProps=c=m,(n.lanes&f)!==0)(n.flags&131072)!==0&&(Mt=!0);else return o.lanes=n.lanes,lr(n,o,f)}return Lu(n,o,a,c,f)}function rm(n,o,a){var c=o.pendingProps,f=c.children,m=n!==null?n.memoizedState:null;if(c.mode==="hidden")if((o.mode&1)===0)o.memoizedState={baseLanes:0,cachePool:null,transitions:null},We(gs,Gt),Gt|=a;else{if((a&1073741824)===0)return n=m!==null?m.baseLanes|a:a,o.lanes=o.childLanes=1073741824,o.memoizedState={baseLanes:n,cachePool:null,transitions:null},o.updateQueue=null,We(gs,Gt),Gt|=n,null;o.memoizedState={baseLanes:0,cachePool:null,transitions:null},c=m!==null?m.baseLanes:a,We(gs,Gt),Gt|=c}else m!==null?(c=m.baseLanes|a,o.memoizedState=null):c=a,We(gs,Gt),Gt|=c;return Nt(n,o,f,a),o.child}function om(n,o){var a=o.ref;(n===null&&a!==null||n!==null&&n.ref!==a)&&(o.flags|=512,o.flags|=2097152)}function Lu(n,o,a,c,f){var m=Dt(a)?uo:yt.current;return m=is(o,m),fs(o,f),a=Eu(n,o,a,c,m,f),c=ku(),n!==null&&!Mt?(o.updateQueue=n.updateQueue,o.flags&=-2053,n.lanes&=~f,lr(n,o,f)):(Ye&&c&&au(o),o.flags|=1,Nt(n,o,a,f),o.child)}function sm(n,o,a,c,f){if(Dt(a)){var m=!0;za(o)}else m=!1;if(fs(o,f),o.stateNode===null)rl(n,o),qp(o,a,c),ju(o,a,c,f),c=!0;else if(n===null){var S=o.stateNode,N=o.memoizedProps;S.props=N;var O=S.context,z=a.contextType;typeof z=="object"&&z!==null?z=rn(z):(z=Dt(a)?uo:yt.current,z=is(o,z));var Y=a.getDerivedStateFromProps,X=typeof Y=="function"||typeof S.getSnapshotBeforeUpdate=="function";X||typeof S.UNSAFE_componentWillReceiveProps!="function"&&typeof S.componentWillReceiveProps!="function"||(N!==c||O!==z)&&Qp(o,S,c,z),Ar=!1;var q=o.memoizedState;S.state=q,Qa(o,c,S,f),O=o.memoizedState,N!==c||q!==O||It.current||Ar?(typeof Y=="function"&&(Ou(o,a,Y,c),O=o.memoizedState),(N=Ar||Kp(o,a,N,c,q,O,z))?(X||typeof S.UNSAFE_componentWillMount!="function"&&typeof S.componentWillMount!="function"||(typeof S.componentWillMount=="function"&&S.componentWillMount(),typeof S.UNSAFE_componentWillMount=="function"&&S.UNSAFE_componentWillMount()),typeof S.componentDidMount=="function"&&(o.flags|=4194308)):(typeof S.componentDidMount=="function"&&(o.flags|=4194308),o.memoizedProps=c,o.memoizedState=O),S.props=c,S.state=O,S.context=z,c=N):(typeof S.componentDidMount=="function"&&(o.flags|=4194308),c=!1)}else{S=o.stateNode,bp(n,o),N=o.memoizedProps,z=o.type===o.elementType?N:vn(o.type,N),S.props=z,X=o.pendingProps,q=S.context,O=a.contextType,typeof O=="object"&&O!==null?O=rn(O):(O=Dt(a)?uo:yt.current,O=is(o,O));var ue=a.getDerivedStateFromProps;(Y=typeof ue=="function"||typeof S.getSnapshotBeforeUpdate=="function")||typeof S.UNSAFE_componentWillReceiveProps!="function"&&typeof S.componentWillReceiveProps!="function"||(N!==X||q!==O)&&Qp(o,S,c,O),Ar=!1,q=o.memoizedState,S.state=q,Qa(o,c,S,f);var he=o.memoizedState;N!==X||q!==he||It.current||Ar?(typeof ue=="function"&&(Ou(o,a,ue,c),he=o.memoizedState),(z=Ar||Kp(o,a,z,c,q,he,O)||!1)?(Y||typeof S.UNSAFE_componentWillUpdate!="function"&&typeof S.componentWillUpdate!="function"||(typeof S.componentWillUpdate=="function"&&S.componentWillUpdate(c,he,O),typeof S.UNSAFE_componentWillUpdate=="function"&&S.UNSAFE_componentWillUpdate(c,he,O)),typeof S.componentDidUpdate=="function"&&(o.flags|=4),typeof S.getSnapshotBeforeUpdate=="function"&&(o.flags|=1024)):(typeof S.componentDidUpdate!="function"||N===n.memoizedProps&&q===n.memoizedState||(o.flags|=4),typeof S.getSnapshotBeforeUpdate!="function"||N===n.memoizedProps&&q===n.memoizedState||(o.flags|=1024),o.memoizedProps=c,o.memoizedState=he),S.props=c,S.state=he,S.context=O,c=z):(typeof S.componentDidUpdate!="function"||N===n.memoizedProps&&q===n.memoizedState||(o.flags|=4),typeof S.getSnapshotBeforeUpdate!="function"||N===n.memoizedProps&&q===n.memoizedState||(o.flags|=1024),c=!1)}return Iu(n,o,a,c,m,f)}function Iu(n,o,a,c,f,m){om(n,o);var S=(o.flags&128)!==0;if(!c&&!S)return f&&up(o,a,!1),lr(n,o,m);c=o.stateNode,Ob.current=o;var N=S&&typeof a.getDerivedStateFromError!="function"?null:c.render();return o.flags|=1,n!==null&&S?(o.child=us(o,n.child,null,m),o.child=us(o,null,N,m)):Nt(n,o,N,m),o.memoizedState=c.state,f&&up(o,a,!0),o.child}function im(n){var o=n.stateNode;o.pendingContext?lp(n,o.pendingContext,o.pendingContext!==o.context):o.context&&lp(n,o.context,!1),vu(n,o.containerInfo)}function am(n,o,a,c,f){return cs(),du(f),o.flags|=256,Nt(n,o,a,c),o.child}var Du={dehydrated:null,treeContext:null,retryLane:0};function Mu(n){return{baseLanes:n,cachePool:null,transitions:null}}function lm(n,o,a){var c=o.pendingProps,f=Ge.current,m=!1,S=(o.flags&128)!==0,N;if((N=S)||(N=n!==null&&n.memoizedState===null?!1:(f&2)!==0),N?(m=!0,o.flags&=-129):(n===null||n.memoizedState!==null)&&(f|=1),We(Ge,f&1),n===null)return uu(o),n=o.memoizedState,n!==null&&(n=n.dehydrated,n!==null)?((o.mode&1)===0?o.lanes=1:n.data==="$!"?o.lanes=8:o.lanes=1073741824,null):(S=c.children,n=c.fallback,m?(c=o.mode,m=o.child,S={mode:"hidden",children:S},(c&1)===0&&m!==null?(m.childLanes=0,m.pendingProps=S):m=ml(S,c,0,null),n=bo(n,c,a,null),m.return=o,n.return=o,m.sibling=n,o.child=m,o.child.memoizedState=Mu(a),o.memoizedState=Du,n):Fu(o,S));if(f=n.memoizedState,f!==null&&(N=f.dehydrated,N!==null))return jb(n,o,S,c,N,f,a);if(m){m=c.fallback,S=o.mode,f=n.child,N=f.sibling;var O={mode:"hidden",children:c.children};return(S&1)===0&&o.child!==f?(c=o.child,c.childLanes=0,c.pendingProps=O,o.deletions=null):(c=$r(f,O),c.subtreeFlags=f.subtreeFlags&14680064),N!==null?m=$r(N,m):(m=bo(m,S,a,null),m.flags|=2),m.return=o,c.return=o,c.sibling=m,o.child=c,c=m,m=o.child,S=n.child.memoizedState,S=S===null?Mu(a):{baseLanes:S.baseLanes|a,cachePool:null,transitions:S.transitions},m.memoizedState=S,m.childLanes=n.childLanes&~a,o.memoizedState=Du,c}return m=n.child,n=m.sibling,c=$r(m,{mode:"visible",children:c.children}),(o.mode&1)===0&&(c.lanes=a),c.return=o,c.sibling=null,n!==null&&(a=o.deletions,a===null?(o.deletions=[n],o.flags|=16):a.push(n)),o.child=c,o.memoizedState=null,c}function Fu(n,o){return o=ml({mode:"visible",children:o},n.mode,0,null),o.return=n,n.child=o}function nl(n,o,a,c){return c!==null&&du(c),us(o,n.child,null,a),n=Fu(o,o.pendingProps.children),n.flags|=2,o.memoizedState=null,n}function jb(n,o,a,c,f,m,S){if(a)return o.flags&256?(o.flags&=-257,c=_u(Error(r(422))),nl(n,o,S,c)):o.memoizedState!==null?(o.child=n.child,o.flags|=128,null):(m=c.fallback,f=o.mode,c=ml({mode:"visible",children:c.children},f,0,null),m=bo(m,f,S,null),m.flags|=2,c.return=o,m.return=o,c.sibling=m,o.child=c,(o.mode&1)!==0&&us(o,n.child,null,S),o.child.memoizedState=Mu(S),o.memoizedState=Du,m);if((o.mode&1)===0)return nl(n,o,S,null);if(f.data==="$!"){if(c=f.nextSibling&&f.nextSibling.dataset,c)var N=c.dgst;return c=N,m=Error(r(419)),c=_u(m,c,void 0),nl(n,o,S,c)}if(N=(S&n.childLanes)!==0,Mt||N){if(c=ct,c!==null){switch(S&-S){case 4:f=2;break;case 16:f=8;break;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:f=32;break;case 536870912:f=268435456;break;default:f=0}f=(f&(c.suspendedLanes|S))!==0?0:f,f!==0&&f!==m.retryLane&&(m.retryLane=f,ir(n,f),bn(c,n,f,-1))}return ed(),c=_u(Error(r(421))),nl(n,o,S,c)}return f.data==="$?"?(o.flags|=128,o.child=n.child,o=Vb.bind(null,n),f._reactRetry=o,null):(n=m.treeContext,Yt=Tr(f.nextSibling),Qt=o,Ye=!0,yn=null,n!==null&&(tn[nn++]=or,tn[nn++]=sr,tn[nn++]=fo,or=n.id,sr=n.overflow,fo=o),o=Fu(o,c.children),o.flags|=4096,o)}function cm(n,o,a){n.lanes|=o;var c=n.alternate;c!==null&&(c.lanes|=o),mu(n.return,o,a)}function zu(n,o,a,c,f){var m=n.memoizedState;m===null?n.memoizedState={isBackwards:o,rendering:null,renderingStartTime:0,last:c,tail:a,tailMode:f}:(m.isBackwards=o,m.rendering=null,m.renderingStartTime=0,m.last=c,m.tail=a,m.tailMode=f)}function um(n,o,a){var c=o.pendingProps,f=c.revealOrder,m=c.tail;if(Nt(n,o,c.children,a),c=Ge.current,(c&2)!==0)c=c&1|2,o.flags|=128;else{if(n!==null&&(n.flags&128)!==0)e:for(n=o.child;n!==null;){if(n.tag===13)n.memoizedState!==null&&cm(n,a,o);else if(n.tag===19)cm(n,a,o);else if(n.child!==null){n.child.return=n,n=n.child;continue}if(n===o)break e;for(;n.sibling===null;){if(n.return===null||n.return===o)break e;n=n.return}n.sibling.return=n.return,n=n.sibling}c&=1}if(We(Ge,c),(o.mode&1)===0)o.memoizedState=null;else switch(f){case"forwards":for(a=o.child,f=null;a!==null;)n=a.alternate,n!==null&&Ya(n)===null&&(f=a),a=a.sibling;a=f,a===null?(f=o.child,o.child=null):(f=a.sibling,a.sibling=null),zu(o,!1,f,a,m);break;case"backwards":for(a=null,f=o.child,o.child=null;f!==null;){if(n=f.alternate,n!==null&&Ya(n)===null){o.child=f;break}n=f.sibling,f.sibling=a,a=f,f=n}zu(o,!0,a,null,m);break;case"together":zu(o,!1,null,null,void 0);break;default:o.memoizedState=null}return o.child}function rl(n,o){(o.mode&1)===0&&n!==null&&(n.alternate=null,o.alternate=null,o.flags|=2)}function lr(n,o,a){if(n!==null&&(o.dependencies=n.dependencies),yo|=o.lanes,(a&o.childLanes)===0)return null;if(n!==null&&o.child!==n.child)throw Error(r(153));if(o.child!==null){for(n=o.child,a=$r(n,n.pendingProps),o.child=a,a.return=o;n.sibling!==null;)n=n.sibling,a=a.sibling=$r(n,n.pendingProps),a.return=o;a.sibling=null}return o.child}function _b(n,o,a){switch(o.tag){case 3:im(o),cs();break;case 5:Ep(o);break;case 1:Dt(o.type)&&za(o);break;case 4:vu(o,o.stateNode.containerInfo);break;case 10:var c=o.type._context,f=o.memoizedProps.value;We(Wa,c._currentValue),c._currentValue=f;break;case 13:if(c=o.memoizedState,c!==null)return c.dehydrated!==null?(We(Ge,Ge.current&1),o.flags|=128,null):(a&o.child.childLanes)!==0?lm(n,o,a):(We(Ge,Ge.current&1),n=lr(n,o,a),n!==null?n.sibling:null);We(Ge,Ge.current&1);break;case 19:if(c=(a&o.childLanes)!==0,(n.flags&128)!==0){if(c)return um(n,o,a);o.flags|=128}if(f=o.memoizedState,f!==null&&(f.rendering=null,f.tail=null,f.lastEffect=null),We(Ge,Ge.current),c)break;return null;case 22:case 23:return o.lanes=0,rm(n,o,a)}return lr(n,o,a)}var dm,$u,fm,hm;dm=function(n,o){for(var a=o.child;a!==null;){if(a.tag===5||a.tag===6)n.appendChild(a.stateNode);else if(a.tag!==4&&a.child!==null){a.child.return=a,a=a.child;continue}if(a===o)break;for(;a.sibling===null;){if(a.return===null||a.return===o)return;a=a.return}a.sibling.return=a.return,a=a.sibling}},$u=function(){},fm=function(n,o,a,c){var f=n.memoizedProps;if(f!==c){n=o.stateNode,mo($n.current);var m=null;switch(a){case"input":f=gt(n,f),c=gt(n,c),m=[];break;case"select":f=Q({},f,{value:void 0}),c=Q({},c,{value:void 0}),m=[];break;case"textarea":f=Ko(n,f),c=Ko(n,c),m=[];break;default:typeof f.onClick!="function"&&typeof c.onClick=="function"&&(n.onclick=Da)}In(a,c);var S;a=null;for(z in f)if(!c.hasOwnProperty(z)&&f.hasOwnProperty(z)&&f[z]!=null)if(z==="style"){var N=f[z];for(S in N)N.hasOwnProperty(S)&&(a||(a={}),a[S]="")}else z!=="dangerouslySetInnerHTML"&&z!=="children"&&z!=="suppressContentEditableWarning"&&z!=="suppressHydrationWarning"&&z!=="autoFocus"&&(i.hasOwnProperty(z)?m||(m=[]):(m=m||[]).push(z,null));for(z in c){var O=c[z];if(N=f!=null?f[z]:void 0,c.hasOwnProperty(z)&&O!==N&&(O!=null||N!=null))if(z==="style")if(N){for(S in N)!N.hasOwnProperty(S)||O&&O.hasOwnProperty(S)||(a||(a={}),a[S]="");for(S in O)O.hasOwnProperty(S)&&N[S]!==O[S]&&(a||(a={}),a[S]=O[S])}else a||(m||(m=[]),m.push(z,a)),a=O;else z==="dangerouslySetInnerHTML"?(O=O?O.__html:void 0,N=N?N.__html:void 0,O!=null&&N!==O&&(m=m||[]).push(z,O)):z==="children"?typeof O!="string"&&typeof O!="number"||(m=m||[]).push(z,""+O):z!=="suppressContentEditableWarning"&&z!=="suppressHydrationWarning"&&(i.hasOwnProperty(z)?(O!=null&&z==="onScroll"&&Ke("scroll",n),m||N===O||(m=[])):(m=m||[]).push(z,O))}a&&(m=m||[]).push("style",a);var z=m;(o.updateQueue=z)&&(o.flags|=4)}},hm=function(n,o,a,c){a!==c&&(o.flags|=4)};function Oi(n,o){if(!Ye)switch(n.tailMode){case"hidden":o=n.tail;for(var a=null;o!==null;)o.alternate!==null&&(a=o),o=o.sibling;a===null?n.tail=null:a.sibling=null;break;case"collapsed":a=n.tail;for(var c=null;a!==null;)a.alternate!==null&&(c=a),a=a.sibling;c===null?o||n.tail===null?n.tail=null:n.tail.sibling=null:c.sibling=null}}function xt(n){var o=n.alternate!==null&&n.alternate.child===n.child,a=0,c=0;if(o)for(var f=n.child;f!==null;)a|=f.lanes|f.childLanes,c|=f.subtreeFlags&14680064,c|=f.flags&14680064,f.return=n,f=f.sibling;else for(f=n.child;f!==null;)a|=f.lanes|f.childLanes,c|=f.subtreeFlags,c|=f.flags,f.return=n,f=f.sibling;return n.subtreeFlags|=c,n.childLanes=a,o}function Ab(n,o,a){var c=o.pendingProps;switch(lu(o),o.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return xt(o),null;case 1:return Dt(o.type)&&Fa(),xt(o),null;case 3:return c=o.stateNode,hs(),qe(It),qe(yt),bu(),c.pendingContext&&(c.context=c.pendingContext,c.pendingContext=null),(n===null||n.child===null)&&(Ha(o)?o.flags|=4:n===null||n.memoizedState.isDehydrated&&(o.flags&256)===0||(o.flags|=1024,yn!==null&&(Xu(yn),yn=null))),$u(n,o),xt(o),null;case 5:xu(o);var f=mo(ki.current);if(a=o.type,n!==null&&o.stateNode!=null)fm(n,o,a,c,f),n.ref!==o.ref&&(o.flags|=512,o.flags|=2097152);else{if(!c){if(o.stateNode===null)throw Error(r(166));return xt(o),null}if(n=mo($n.current),Ha(o)){c=o.stateNode,a=o.type;var m=o.memoizedProps;switch(c[zn]=o,c[wi]=m,n=(o.mode&1)!==0,a){case"dialog":Ke("cancel",c),Ke("close",c);break;case"iframe":case"object":case"embed":Ke("load",c);break;case"video":case"audio":for(f=0;f<yi.length;f++)Ke(yi[f],c);break;case"source":Ke("error",c);break;case"img":case"image":case"link":Ke("error",c),Ke("load",c);break;case"details":Ke("toggle",c);break;case"input":On(c,m),Ke("invalid",c);break;case"select":c._wrapperState={wasMultiple:!!m.multiple},Ke("invalid",c);break;case"textarea":_n(c,m),Ke("invalid",c)}In(a,m),f=null;for(var S in m)if(m.hasOwnProperty(S)){var N=m[S];S==="children"?typeof N=="string"?c.textContent!==N&&(m.suppressHydrationWarning!==!0&&Ia(c.textContent,N,n),f=["children",N]):typeof N=="number"&&c.textContent!==""+N&&(m.suppressHydrationWarning!==!0&&Ia(c.textContent,N,n),f=["children",""+N]):i.hasOwnProperty(S)&&N!=null&&S==="onScroll"&&Ke("scroll",c)}switch(a){case"input":Oe(c),wr(c,m,!0);break;case"textarea":Oe(c),ua(c);break;case"select":case"option":break;default:typeof m.onClick=="function"&&(c.onclick=Da)}c=f,o.updateQueue=c,c!==null&&(o.flags|=4)}else{S=f.nodeType===9?f:f.ownerDocument,n==="http://www.w3.org/1999/xhtml"&&(n=Et(a)),n==="http://www.w3.org/1999/xhtml"?a==="script"?(n=S.createElement("div"),n.innerHTML="<script><\/script>",n=n.removeChild(n.firstChild)):typeof c.is=="string"?n=S.createElement(a,{is:c.is}):(n=S.createElement(a),a==="select"&&(S=n,c.multiple?S.multiple=!0:c.size&&(S.size=c.size))):n=S.createElementNS(n,a),n[zn]=o,n[wi]=c,dm(n,o,!1,!1),o.stateNode=n;e:{switch(S=ei(a,c),a){case"dialog":Ke("cancel",n),Ke("close",n),f=c;break;case"iframe":case"object":case"embed":Ke("load",n),f=c;break;case"video":case"audio":for(f=0;f<yi.length;f++)Ke(yi[f],n);f=c;break;case"source":Ke("error",n),f=c;break;case"img":case"image":case"link":Ke("error",n),Ke("load",n),f=c;break;case"details":Ke("toggle",n),f=c;break;case"input":On(n,c),f=gt(n,c),Ke("invalid",n);break;case"option":f=c;break;case"select":n._wrapperState={wasMultiple:!!c.multiple},f=Q({},c,{value:void 0}),Ke("invalid",n);break;case"textarea":_n(n,c),f=Ko(n,c),Ke("invalid",n);break;default:f=c}In(a,f),N=f;for(m in N)if(N.hasOwnProperty(m)){var O=N[m];m==="style"?tr(n,O):m==="dangerouslySetInnerHTML"?(O=O?O.__html:void 0,O!=null&&da(n,O)):m==="children"?typeof O=="string"?(a!=="textarea"||O!=="")&&Ln(n,O):typeof O=="number"&&Ln(n,""+O):m!=="suppressContentEditableWarning"&&m!=="suppressHydrationWarning"&&m!=="autoFocus"&&(i.hasOwnProperty(m)?O!=null&&m==="onScroll"&&Ke("scroll",n):O!=null&&_(n,m,O,S))}switch(a){case"input":Oe(n),wr(n,c,!1);break;case"textarea":Oe(n),ua(n);break;case"option":c.value!=null&&n.setAttribute("value",""+ye(c.value));break;case"select":n.multiple=!!c.multiple,m=c.value,m!=null?en(n,!!c.multiple,m,!1):c.defaultValue!=null&&en(n,!!c.multiple,c.defaultValue,!0);break;default:typeof f.onClick=="function"&&(n.onclick=Da)}switch(a){case"button":case"input":case"select":case"textarea":c=!!c.autoFocus;break e;case"img":c=!0;break e;default:c=!1}}c&&(o.flags|=4)}o.ref!==null&&(o.flags|=512,o.flags|=2097152)}return xt(o),null;case 6:if(n&&o.stateNode!=null)hm(n,o,n.memoizedProps,c);else{if(typeof c!="string"&&o.stateNode===null)throw Error(r(166));if(a=mo(ki.current),mo($n.current),Ha(o)){if(c=o.stateNode,a=o.memoizedProps,c[zn]=o,(m=c.nodeValue!==a)&&(n=Qt,n!==null))switch(n.tag){case 3:Ia(c.nodeValue,a,(n.mode&1)!==0);break;case 5:n.memoizedProps.suppressHydrationWarning!==!0&&Ia(c.nodeValue,a,(n.mode&1)!==0)}m&&(o.flags|=4)}else c=(a.nodeType===9?a:a.ownerDocument).createTextNode(c),c[zn]=o,o.stateNode=c}return xt(o),null;case 13:if(qe(Ge),c=o.memoizedState,n===null||n.memoizedState!==null&&n.memoizedState.dehydrated!==null){if(Ye&&Yt!==null&&(o.mode&1)!==0&&(o.flags&128)===0)gp(),cs(),o.flags|=98560,m=!1;else if(m=Ha(o),c!==null&&c.dehydrated!==null){if(n===null){if(!m)throw Error(r(318));if(m=o.memoizedState,m=m!==null?m.dehydrated:null,!m)throw Error(r(317));m[zn]=o}else cs(),(o.flags&128)===0&&(o.memoizedState=null),o.flags|=4;xt(o),m=!1}else yn!==null&&(Xu(yn),yn=null),m=!0;if(!m)return o.flags&65536?o:null}return(o.flags&128)!==0?(o.lanes=a,o):(c=c!==null,c!==(n!==null&&n.memoizedState!==null)&&c&&(o.child.flags|=8192,(o.mode&1)!==0&&(n===null||(Ge.current&1)!==0?it===0&&(it=3):ed())),o.updateQueue!==null&&(o.flags|=4),xt(o),null);case 4:return hs(),$u(n,o),n===null&&vi(o.stateNode.containerInfo),xt(o),null;case 10:return pu(o.type._context),xt(o),null;case 17:return Dt(o.type)&&Fa(),xt(o),null;case 19:if(qe(Ge),m=o.memoizedState,m===null)return xt(o),null;if(c=(o.flags&128)!==0,S=m.rendering,S===null)if(c)Oi(m,!1);else{if(it!==0||n!==null&&(n.flags&128)!==0)for(n=o.child;n!==null;){if(S=Ya(n),S!==null){for(o.flags|=128,Oi(m,!1),c=S.updateQueue,c!==null&&(o.updateQueue=c,o.flags|=4),o.subtreeFlags=0,c=a,a=o.child;a!==null;)m=a,n=c,m.flags&=14680066,S=m.alternate,S===null?(m.childLanes=0,m.lanes=n,m.child=null,m.subtreeFlags=0,m.memoizedProps=null,m.memoizedState=null,m.updateQueue=null,m.dependencies=null,m.stateNode=null):(m.childLanes=S.childLanes,m.lanes=S.lanes,m.child=S.child,m.subtreeFlags=0,m.deletions=null,m.memoizedProps=S.memoizedProps,m.memoizedState=S.memoizedState,m.updateQueue=S.updateQueue,m.type=S.type,n=S.dependencies,m.dependencies=n===null?null:{lanes:n.lanes,firstContext:n.firstContext}),a=a.sibling;return We(Ge,Ge.current&1|2),o.child}n=n.sibling}m.tail!==null&&tt()>ys&&(o.flags|=128,c=!0,Oi(m,!1),o.lanes=4194304)}else{if(!c)if(n=Ya(S),n!==null){if(o.flags|=128,c=!0,a=n.updateQueue,a!==null&&(o.updateQueue=a,o.flags|=4),Oi(m,!0),m.tail===null&&m.tailMode==="hidden"&&!S.alternate&&!Ye)return xt(o),null}else 2*tt()-m.renderingStartTime>ys&&a!==1073741824&&(o.flags|=128,c=!0,Oi(m,!1),o.lanes=4194304);m.isBackwards?(S.sibling=o.child,o.child=S):(a=m.last,a!==null?a.sibling=S:o.child=
Download .txt
gitextract_6yb3d2nb/

├── .gitattributes
├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── quesiton.md
│   └── workflows/
│       └── deploy.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .python-version
├── LICENSE
├── README.md
├── README_en.md
├── README_es.md
├── api/
│   ├── __init__.py
│   ├── main.py
│   ├── routers/
│   │   ├── __init__.py
│   │   ├── crawler.py
│   │   ├── data.py
│   │   └── websocket.py
│   ├── schemas/
│   │   ├── __init__.py
│   │   └── crawler.py
│   ├── services/
│   │   ├── __init__.py
│   │   └── crawler_manager.py
│   └── webui/
│       ├── assets/
│       │   ├── index-DvClRayq.js
│       │   └── index-OiBmsgXF.css
│       └── index.html
├── base/
│   ├── __init__.py
│   └── base_crawler.py
├── cache/
│   ├── __init__.py
│   ├── abs_cache.py
│   ├── cache_factory.py
│   ├── local_cache.py
│   └── redis_cache.py
├── cmd_arg/
│   ├── __init__.py
│   └── arg.py
├── config/
│   ├── __init__.py
│   ├── base_config.py
│   ├── bilibili_config.py
│   ├── db_config.py
│   ├── dy_config.py
│   ├── ks_config.py
│   ├── tieba_config.py
│   ├── weibo_config.py
│   ├── xhs_config.py
│   └── zhihu_config.py
├── constant/
│   ├── __init__.py
│   ├── baidu_tieba.py
│   └── zhihu.py
├── database/
│   ├── __init__.py
│   ├── db.py
│   ├── db_session.py
│   ├── models.py
│   └── mongodb_store_base.py
├── docs/
│   ├── .vitepress/
│   │   ├── config.mjs
│   │   └── theme/
│   │       ├── DynamicAds.vue
│   │       ├── MyLayout.vue
│   │       ├── custom.css
│   │       └── index.js
│   ├── CDP模式使用指南.md
│   ├── data_storage_guide.md
│   ├── excel_export_guide.md
│   ├── hit_stopwords.txt
│   ├── index.md
│   ├── mediacrawlerpro订阅.md
│   ├── 代理使用.md
│   ├── 作者介绍.md
│   ├── 原生环境管理文档.md
│   ├── 常见问题.md
│   ├── 开发者咨询.md
│   ├── 微信交流群.md
│   ├── 快代理使用文档.md
│   ├── 手机号登录说明.md
│   ├── 捐赠名单.md
│   ├── 知识付费介绍.md
│   ├── 词云图使用配置.md
│   ├── 豌豆HTTP使用文档.md
│   ├── 项目代码结构.md
│   └── 项目架构文档.md
├── libs/
│   ├── douyin.js
│   └── zhihu.js
├── main.py
├── media_platform/
│   ├── __init__.py
│   ├── bilibili/
│   │   ├── __init__.py
│   │   ├── client.py
│   │   ├── core.py
│   │   ├── exception.py
│   │   ├── field.py
│   │   ├── help.py
│   │   └── login.py
│   ├── douyin/
│   │   ├── __init__.py
│   │   ├── client.py
│   │   ├── core.py
│   │   ├── exception.py
│   │   ├── field.py
│   │   ├── help.py
│   │   └── login.py
│   ├── kuaishou/
│   │   ├── __init__.py
│   │   ├── client.py
│   │   ├── core.py
│   │   ├── exception.py
│   │   ├── field.py
│   │   ├── graphql/
│   │   │   ├── comment_list.graphql
│   │   │   ├── search_query.graphql
│   │   │   ├── video_detail.graphql
│   │   │   ├── vision_profile.graphql
│   │   │   ├── vision_profile_photo_list.graphql
│   │   │   ├── vision_profile_user_list.graphql
│   │   │   └── vision_sub_comment_list.graphql
│   │   ├── graphql.py
│   │   ├── help.py
│   │   └── login.py
│   ├── tieba/
│   │   ├── __init__.py
│   │   ├── client.py
│   │   ├── core.py
│   │   ├── field.py
│   │   ├── help.py
│   │   ├── login.py
│   │   └── test_data/
│   │       ├── note_comments.html
│   │       ├── note_detail.html
│   │       ├── note_sub_comments.html
│   │       ├── search_keyword_notes.html
│   │       └── tieba_note_list.html
│   ├── weibo/
│   │   ├── __init__.py
│   │   ├── client.py
│   │   ├── core.py
│   │   ├── exception.py
│   │   ├── field.py
│   │   ├── help.py
│   │   └── login.py
│   ├── xhs/
│   │   ├── __init__.py
│   │   ├── client.py
│   │   ├── core.py
│   │   ├── exception.py
│   │   ├── extractor.py
│   │   ├── field.py
│   │   ├── help.py
│   │   ├── login.py
│   │   ├── playwright_sign.py
│   │   └── xhs_sign.py
│   └── zhihu/
│       ├── __init__.py
│       ├── client.py
│       ├── core.py
│       ├── exception.py
│       ├── field.py
│       ├── help.py
│       └── login.py
├── model/
│   ├── __init__.py
│   ├── m_baidu_tieba.py
│   ├── m_bilibili.py
│   ├── m_douyin.py
│   ├── m_kuaishou.py
│   ├── m_weibo.py
│   ├── m_xiaohongshu.py
│   └── m_zhihu.py
├── mypy.ini
├── package.json
├── proxy/
│   ├── __init__.py
│   ├── base_proxy.py
│   ├── providers/
│   │   ├── __init__.py
│   │   ├── jishu_http_proxy.py
│   │   ├── kuaidl_proxy.py
│   │   └── wandou_http_proxy.py
│   ├── proxy_ip_pool.py
│   ├── proxy_mixin.py
│   └── types.py
├── pyproject.toml
├── recv_sms.py
├── requirements.txt
├── store/
│   ├── __init__.py
│   ├── bilibili/
│   │   ├── __init__.py
│   │   ├── _store_impl.py
│   │   └── bilibilli_store_media.py
│   ├── douyin/
│   │   ├── __init__.py
│   │   ├── _store_impl.py
│   │   └── douyin_store_media.py
│   ├── excel_store_base.py
│   ├── kuaishou/
│   │   ├── __init__.py
│   │   └── _store_impl.py
│   ├── tieba/
│   │   ├── __init__.py
│   │   └── _store_impl.py
│   ├── weibo/
│   │   ├── __init__.py
│   │   ├── _store_impl.py
│   │   └── weibo_store_media.py
│   ├── xhs/
│   │   ├── __init__.py
│   │   ├── _store_impl.py
│   │   └── xhs_store_media.py
│   └── zhihu/
│       ├── __init__.py
│       └── _store_impl.py
├── test/
│   ├── __init__.py
│   ├── test_db_sync.py
│   ├── test_expiring_local_cache.py
│   ├── test_mongodb_integration.py
│   ├── test_proxy_ip_pool.py
│   ├── test_redis_cache.py
│   └── test_utils.py
├── tests/
│   ├── __init__.py
│   ├── conftest.py
│   ├── test_excel_store.py
│   └── test_store_factory.py
├── tools/
│   ├── __init__.py
│   ├── app_runner.py
│   ├── async_file_writer.py
│   ├── browser_launcher.py
│   ├── cdp_browser.py
│   ├── crawler_util.py
│   ├── easing.py
│   ├── file_header_manager.py
│   ├── httpx_util.py
│   ├── slider_util.py
│   ├── time_util.py
│   ├── utils.py
│   └── words.py
└── var.py
Download .txt
SYMBOL INDEX (1825 symbols across 118 files)

FILE: api/main.py
  function serve_frontend (line 65) | async def serve_frontend():
  function health_check (line 79) | async def health_check():
  function check_environment (line 84) | async def check_environment():
  function get_platforms (line 133) | async def get_platforms():
  function get_config_options (line 149) | async def get_config_options():

FILE: api/routers/crawler.py
  function start_crawler (line 28) | async def start_crawler(request: CrawlerStartRequest):
  function stop_crawler (line 41) | async def stop_crawler():
  function get_crawler_status (line 54) | async def get_crawler_status():
  function get_logs (line 60) | async def get_logs(limit: int = 100):

FILE: api/routers/data.py
  function get_file_info (line 33) | def get_file_info(file_path: Path) -> dict:
  function list_data_files (line 62) | async def list_data_files(platform: Optional[str] = None, file_type: Opt...
  function get_file_content (line 99) | async def get_file_content(file_path: str, preview: bool = True, limit: ...
  function download_file (line 167) | async def download_file(file_path: str):
  function get_data_stats (line 191) | async def get_data_stats():

FILE: api/routers/websocket.py
  class ConnectionManager (line 29) | class ConnectionManager:
    method __init__ (line 32) | def __init__(self):
    method connect (line 35) | async def connect(self, websocket: WebSocket):
    method disconnect (line 39) | def disconnect(self, websocket: WebSocket):
    method broadcast (line 42) | async def broadcast(self, message: dict):
  function log_broadcaster (line 62) | async def log_broadcaster():
  function start_broadcaster (line 82) | def start_broadcaster():
  function websocket_logs (line 90) | async def websocket_logs(websocket: WebSocket):
  function websocket_status (line 138) | async def websocket_status(websocket: WebSocket):

FILE: api/schemas/crawler.py
  class PlatformEnum (line 24) | class PlatformEnum(str, Enum):
  class LoginTypeEnum (line 35) | class LoginTypeEnum(str, Enum):
  class CrawlerTypeEnum (line 42) | class CrawlerTypeEnum(str, Enum):
  class SaveDataOptionEnum (line 49) | class SaveDataOptionEnum(str, Enum):
  class CrawlerStartRequest (line 60) | class CrawlerStartRequest(BaseModel):
  class CrawlerStatusResponse (line 76) | class CrawlerStatusResponse(BaseModel):
  class LogEntry (line 85) | class LogEntry(BaseModel):
  class DataFileInfo (line 93) | class DataFileInfo(BaseModel):

FILE: api/services/crawler_manager.py
  class CrawlerManager (line 30) | class CrawlerManager:
    method __init__ (line 33) | def __init__(self):
    method logs (line 48) | def logs(self) -> List[LogEntry]:
    method get_log_queue (line 51) | def get_log_queue(self) -> asyncio.Queue:
    method _create_log_entry (line 57) | def _create_log_entry(self, message: str, level: str = "info") -> LogE...
    method _push_log (line 72) | async def _push_log(self, entry: LogEntry):
    method _parse_log_level (line 80) | def _parse_log_level(self, line: str) -> str:
    method start (line 93) | async def start(self, config: CrawlerStartRequest) -> bool:
    method stop (line 153) | async def stop(self) -> bool:
    method get_status (line 195) | def get_status(self) -> dict:
    method _build_command (line 205) | def _build_command(self, config: CrawlerStartRequest) -> list:
    method _read_output (line 235) | async def _read_output(self):

FILE: api/webui/assets/index-DvClRayq.js
  method _ (line 1) | set _(i){ce(t,e,i,r)}
  method _ (line 1) | get _(){return R(t,e,s)}
  function t1 (line 1) | function t1(t,e){for(var r=0;r<e.length;r++){const s=e[r];if(typeof s!="...
  function r (line 1) | function r(i){const l={};return i.integrity&&(l.integrity=i.integrity),i...
  function s (line 1) | function s(i){if(i.ep)return;i.ep=!0;const l=r(i);fetch(i.href,l)}
  function bf (line 1) | function bf(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.c...
  function n1 (line 9) | function n1(){if(Um)return je;Um=1;var t=Symbol.for("react.element"),e=S...
  function ac (line 9) | function ac(){return Bm||(Bm=1,ud.exports=n1()),ud.exports}
  function r1 (line 17) | function r1(){if(Hm)return Di;Hm=1;var t=ac(),e=Symbol.for("react.elemen...
  function o1 (line 17) | function o1(){return Vm||(Vm=1,cd.exports=r1()),cd.exports}
  function s1 (line 25) | function s1(){return Wm||(Wm=1,(function(t){function e($,H){var Q=$.leng...
  function i1 (line 25) | function i1(){return Km||(Km=1,fd.exports=s1()),fd.exports}
  function a1 (line 33) | function a1(){if(qm)return $t;qm=1;var t=ac(),e=i1();function r(n){for(v...
  function Fy (line 40) | function Fy(){if(Qm)return dd.exports;Qm=1;function t(){if(!(typeof __RE...
  function l1 (line 40) | function l1(){if(Ym)return Cl;Ym=1;var t=Fy();return Cl.createRoot=t.cre...
  method constructor (line 40) | constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(...
  method subscribe (line 40) | subscribe(t){return this.listeners.add(t),this.onSubscribe(),()=>{this.l...
  method hasListeners (line 40) | hasListeners(){return this.listeners.size>0}
  method onSubscribe (line 40) | onSubscribe(){}
  method onUnsubscribe (line 40) | onUnsubscribe(){}
  method constructor (line 40) | constructor(){ve(this,Kr,d1);ve(this,wf,!1)}
  method setTimeoutProvider (line 40) | setTimeoutProvider(t){ce(this,Kr,t)}
  method setTimeout (line 40) | setTimeout(t,e){return R(this,Kr).setTimeout(t,e)}
  method clearTimeout (line 40) | clearTimeout(t){R(this,Kr).clearTimeout(t)}
  method setInterval (line 40) | setInterval(t,e){return R(this,Kr).setInterval(t,e)}
  method clearInterval (line 40) | clearInterval(t){R(this,Kr).clearInterval(t)}
  function h1 (line 40) | function h1(t){setTimeout(t,0)}
  function Ot (line 40) | function Ot(){}
  function p1 (line 40) | function p1(t,e){return typeof t=="function"?t(e):t}
  function Id (line 40) | function Id(t){return typeof t=="number"&&t>=0&&t!==1/0}
  function zy (line 40) | function zy(t,e){return Math.max(t+(e||0)-Date.now(),0)}
  function to (line 40) | function to(t,e){return typeof t=="function"?t(e):t}
  function cn (line 40) | function cn(t,e){return typeof t=="function"?t(e):t}
  function Gm (line 40) | function Gm(t,e){const{type:r="all",exact:s,fetchStatus:i,predicate:l,qu...
  function Xm (line 40) | function Xm(t,e){const{exact:r,status:s,predicate:i,mutationKey:l}=t;if(...
  function Cf (line 40) | function Cf(t,e){return((e==null?void 0:e.queryKeyHashFn)||zo)(t)}
  function zo (line 40) | function zo(t){return JSON.stringify(t,(e,r)=>Dd(r)?Object.keys(r).sort(...
  function Wi (line 40) | function Wi(t,e){return t===e?!0:typeof t!=typeof e?!1:t&&e&&typeof t=="...
  function $y (line 40) | function $y(t,e){if(t===e)return t;const r=Jm(t)&&Jm(e);if(!r&&!(Dd(t)&&...
  function Vl (line 40) | function Vl(t,e){if(!e||Object.keys(t).length!==Object.keys(e).length)re...
  function Jm (line 40) | function Jm(t){return Array.isArray(t)&&t.length===Object.keys(t).length}
  function Dd (line 40) | function Dd(t){if(!Zm(t))return!1;const e=t.constructor;if(e===void 0)re...
  function Zm (line 40) | function Zm(t){return Object.prototype.toString.call(t)==="[object Objec...
  function g1 (line 40) | function g1(t){return new Promise(e=>{Co.setTimeout(e,t)})}
  function Md (line 40) | function Md(t,e,r){return typeof r.structuralSharing=="function"?r.struc...
  function y1 (line 40) | function y1(t,e,r=0){const s=[...t,e];return r&&s.length>r?s.slice(1):s}
  function v1 (line 40) | function v1(t,e,r=0){const s=[e,...t];return r&&s.length>r?s.slice(0,-1):s}
  function Uy (line 40) | function Uy(t,e){return!t.queryFn&&(e!=null&&e.initialPromise)?()=>e.ini...
  function By (line 40) | function By(t,e){return typeof t=="function"?t(...e):!!t}
  method constructor (line 40) | constructor(){super();ve(this,Po);ve(this,qr);ve(this,js);ce(this,js,e=>...
  method onSubscribe (line 40) | onSubscribe(){R(this,qr)||this.setEventListener(R(this,js))}
  method onUnsubscribe (line 40) | onUnsubscribe(){var e;this.hasListeners()||((e=R(this,qr))==null||e.call...
  method setEventListener (line 40) | setEventListener(e){var r;ce(this,js,e),(r=R(this,qr))==null||r.call(thi...
  method setFocused (line 40) | setFocused(e){R(this,Po)!==e&&(ce(this,Po,e),this.onFocus())}
  method onFocus (line 40) | onFocus(){const e=this.isFocused();this.listeners.forEach(r=>{r(e)})}
  method isFocused (line 40) | isFocused(){var e;return typeof R(this,Po)=="boolean"?R(this,Po):((e=glo...
  function Fd (line 40) | function Fd(){let t,e;const r=new Promise((i,l)=>{t=i,e=l});r.status="pe...
  function b1 (line 40) | function b1(){let t=[],e=0,r=d=>{d()},s=d=>{d()},i=w1;const l=d=>{e?t.pu...
  method constructor (line 40) | constructor(){super();ve(this,_s,!0);ve(this,Qr);ve(this,As);ce(this,As,...
  method onSubscribe (line 40) | onSubscribe(){R(this,Qr)||this.setEventListener(R(this,As))}
  method onUnsubscribe (line 40) | onUnsubscribe(){var e;this.hasListeners()||((e=R(this,Qr))==null||e.call...
  method setEventListener (line 40) | setEventListener(e){var r;ce(this,As,e),(r=R(this,Qr))==null||r.call(thi...
  method setOnline (line 40) | setOnline(e){R(this,_s)!==e&&(ce(this,_s,e),this.listeners.forEach(s=>{s...
  method isOnline (line 40) | isOnline(){return R(this,_s)}
  function C1 (line 40) | function C1(t){return Math.min(1e3*2**t,3e4)}
  function Hy (line 40) | function Hy(t){return(t??"online")==="online"?Wl.isOnline():!0}
  method constructor (line 40) | constructor(t){super("CancelledError"),this.revert=t==null?void 0:t.reve...
  function Vy (line 40) | function Vy(t){let e=!1,r=0,s;const i=Fd(),l=()=>i.status!=="pending",u=...
  method constructor (line 40) | constructor(){ve(this,To)}
  method destroy (line 40) | destroy(){this.clearGcTimeout()}
  method scheduleGc (line 40) | scheduleGc(){this.clearGcTimeout(),Id(this.gcTime)&&ce(this,To,Co.setTim...
  method updateGcTime (line 40) | updateGcTime(t){this.gcTime=Math.max(this.gcTime||0,t??(Fo?1/0:300*1e3))}
  method clearGcTimeout (line 40) | clearGcTimeout(){R(this,To)&&(Co.clearTimeout(R(this,To)),ce(this,To,voi...
  method constructor (line 40) | constructor(e){super();ve(this,En);ve(this,Oo);ve(this,Ls);ve(this,ln);v...
  method meta (line 40) | get meta(){return this.options.meta}
  method promise (line 40) | get promise(){var e;return(e=R(this,dt))==null?void 0:e.promise}
  method setOptions (line 40) | setOptions(e){if(this.options={...R(this,Gi),...e},this.updateGcTime(thi...
  method optionalRemove (line 40) | optionalRemove(){!this.observers.length&&this.state.fetchStatus==="idle"...
  method setData (line 40) | setData(e,r){const s=Md(this.state.data,e,this.options);return Te(this,E...
  method setState (line 40) | setState(e,r){Te(this,En,fr).call(this,{type:"setState",state:e,setState...
  method cancel (line 40) | cancel(e){var s,i;const r=(s=R(this,dt))==null?void 0:s.promise;return(i...
  method destroy (line 40) | destroy(){super.destroy(),this.cancel({silent:!0})}
  method reset (line 40) | reset(){this.destroy(),this.setState(R(this,Oo))}
  method isActive (line 40) | isActive(){return this.observers.some(e=>cn(e.options.enabled,this)!==!1)}
  method isDisabled (line 40) | isDisabled(){return this.getObserversCount()>0?!this.isActive():this.opt...
  method isStatic (line 40) | isStatic(){return this.getObserversCount()>0?this.observers.some(e=>to(e...
  method isStale (line 40) | isStale(){return this.getObserversCount()>0?this.observers.some(e=>e.get...
  method isStaleByTime (line 40) | isStaleByTime(e=0){return this.state.data===void 0?!0:e==="static"?!1:th...
  method onFocus (line 40) | onFocus(){var r;const e=this.observers.find(s=>s.shouldFetchOnWindowFocu...
  method onOnline (line 40) | onOnline(){var r;const e=this.observers.find(s=>s.shouldFetchOnReconnect...
  method addObserver (line 40) | addObserver(e){this.observers.includes(e)||(this.observers.push(e),this....
  method removeObserver (line 40) | removeObserver(e){this.observers.includes(e)&&(this.observers=this.obser...
  method getObserversCount (line 40) | getObserversCount(){return this.observers.length}
  method invalidate (line 40) | invalidate(){this.state.isInvalidated||Te(this,En,fr).call(this,{type:"i...
  method fetch (line 40) | async fetch(e,r){var h,p,y,v,C,w,E,b,k,T,j,_;if(this.state.fetchStatus!=...
  function Ky (line 40) | function Ky(t,e){return{fetchFailureCount:0,fetchFailureReason:null,fetc...
  function eg (line 40) | function eg(t,e){return{data:t,dataUpdatedAt:e??Date.now(),error:null,is...
  function tg (line 40) | function tg(t){const e=typeof t.initialData=="function"?t.initialData():...
  method constructor (line 40) | constructor(e,r){super();ve(this,$e);ve(this,Ut);ve(this,Ae);ve(this,Xi)...
  method bindMethods (line 40) | bindMethods(){this.refetch=this.refetch.bind(this)}
  method onSubscribe (line 40) | onSubscribe(){this.listeners.size===1&&(R(this,Ae).addObserver(this),ng(...
  method onUnsubscribe (line 40) | onUnsubscribe(){this.hasListeners()||this.destroy()}
  method shouldFetchOnReconnect (line 40) | shouldFetchOnReconnect(){return qd(R(this,Ae),this.options,this.options....
  method shouldFetchOnWindowFocus (line 40) | shouldFetchOnWindowFocus(){return qd(R(this,Ae),this.options,this.option...
  method destroy (line 40) | destroy(){this.listeners=new Set,Te(this,$e,Vd).call(this),Te(this,$e,Wd...
  method setOptions (line 40) | setOptions(e){const r=this.options,s=R(this,Ae);if(this.options=R(this,U...
  method getOptimisticResult (line 40) | getOptimisticResult(e){const r=R(this,Ut).getQueryCache().build(R(this,U...
  method getCurrentResult (line 40) | getCurrentResult(){return R(this,Pt)}
  method trackResult (line 40) | trackResult(e,r){return new Proxy(e,{get:(s,i)=>(this.trackProp(i),r==nu...
  method trackProp (line 40) | trackProp(e){R(this,Fs).add(e)}
  method getCurrentQuery (line 40) | getCurrentQuery(){return R(this,Ae)}
  method refetch (line 40) | refetch({...e}={}){return this.fetch({...e})}
  method fetchOptimistic (line 40) | fetchOptimistic(e){const r=R(this,Ut).defaultQueryOptions(e),s=R(this,Ut...
  method fetch (line 40) | fetch(e){return Te(this,$e,Ui).call(this,{...e,cancelRefetch:e.cancelRef...
  method createResult (line 40) | createResult(e,r){var G;const s=R(this,Ae),i=this.options,l=R(this,Pt),u...
  method updateResult (line 40) | updateResult(){const e=R(this,Pt),r=this.createResult(R(this,Ae),this.op...
  method onQueryUpdate (line 40) | onQueryUpdate(){this.updateResult(),this.hasListeners()&&Te(this,$e,Hd)....
  function N1 (line 40) | function N1(t,e){return cn(e.enabled,t)!==!1&&t.state.data===void 0&&!(t...
  function ng (line 40) | function ng(t,e){return N1(t,e)||t.state.data!==void 0&&qd(t,e,e.refetch...
  function qd (line 40) | function qd(t,e,r){if(cn(e.enabled,t)!==!1&&to(e.staleTime,t)!=="static"...
  function rg (line 40) | function rg(t,e,r,s){return(t!==e||cn(s.enabled,t)===!1)&&(!r.suspense||...
  function Nf (line 40) | function Nf(t,e){return cn(e.enabled,t)!==!1&&t.isStaleByTime(to(e.stale...
  function R1 (line 40) | function R1(t,e){return!Vl(t.getCurrentResult(),e)}
  function og (line 40) | function og(t){return{onFetch:(e,r)=>{var y,v,C,w,E;const s=e.options,i=...
  function sg (line 40) | function sg(t,{pages:e,pageParams:r}){const s=e.length-1;return e.length...
  function P1 (line 40) | function P1(t,{pages:e,pageParams:r}){var s;return e.length>0?(s=t.getPr...
  method constructor (line 40) | constructor(e){super();ve(this,Wn);ve(this,Zi);ve(this,Vn);ve(this,Tt);v...
  method setOptions (line 40) | setOptions(e){this.options=e,this.updateGcTime(this.options.gcTime)}
  method meta (line 40) | get meta(){return this.options.meta}
  method addObserver (line 40) | addObserver(e){R(this,Vn).includes(e)||(R(this,Vn).push(e),this.clearGcT...
  method removeObserver (line 40) | removeObserver(e){ce(this,Vn,R(this,Vn).filter(r=>r!==e)),this.scheduleG...
  method optionalRemove (line 40) | optionalRemove(){R(this,Vn).length||(this.state.status==="pending"?this....
  method continue (line 40) | continue(){var e;return((e=R(this,Do))==null?void 0:e.continue())??this....
  method execute (line 40) | async execute(e){var u,d,h,p,y,v,C,w,E,b,k,T,j,_,A,F,V,B,te,G;const r=()...
  function Qy (line 40) | function Qy(){return{context:void 0,data:void 0,error:null,failureCount:...
  method constructor (line 40) | constructor(e={}){super();ve(this,pr);ve(this,kn);ve(this,ea);this.confi...
  method build (line 40) | build(e,r,s){const i=new T1({client:e,mutationCache:this,mutationId:++Sl...
  method add (line 40) | add(e){R(this,pr).add(e);const r=El(e);if(typeof r=="string"){const s=R(...
  method remove (line 40) | remove(e){if(R(this,pr).delete(e)){const r=El(e);if(typeof r=="string"){...
  method canRun (line 40) | canRun(e){const r=El(e);if(typeof r=="string"){const s=R(this,kn).get(r)...
  method runNext (line 40) | runNext(e){var s;const r=El(e);if(typeof r=="string"){const i=(s=R(this,...
  method clear (line 40) | clear(){at.batch(()=>{R(this,pr).forEach(e=>{this.notify({type:"removed"...
  method getAll (line 40) | getAll(){return Array.from(R(this,pr))}
  method find (line 40) | find(e){const r={exact:!0,...e};return this.getAll().find(s=>Xm(r,s))}
  method findAll (line 40) | findAll(e={}){return this.getAll().filter(r=>Xm(e,r))}
  method notify (line 40) | notify(e){at.batch(()=>{this.listeners.forEach(r=>{r(e)})})}
  method resumePausedMutations (line 40) | resumePausedMutations(){const e=this.getAll().filter(r=>r.state.isPaused...
  function El (line 40) | function El(t){var e;return(e=t.options.scope)==null?void 0:e.id}
  method constructor (line 40) | constructor(r,s){super();ve(this,yr);ve(this,mr);ve(this,Xr);ve(this,Bt)...
  method bindMethods (line 40) | bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.b...
  method setOptions (line 40) | setOptions(r){var i;const s=this.options;this.options=R(this,mr).default...
  method onUnsubscribe (line 40) | onUnsubscribe(){var r;this.hasListeners()||(r=R(this,Bt))==null||r.remov...
  method onMutationUpdate (line 40) | onMutationUpdate(r){Te(this,yr,Dl).call(this),Te(this,yr,Qd).call(this,r)}
  method getCurrentResult (line 40) | getCurrentResult(){return R(this,Xr)}
  method reset (line 40) | reset(){var r;(r=R(this,Bt))==null||r.removeObserver(this),ce(this,Bt,vo...
  method mutate (line 40) | mutate(r,s){var i;return ce(this,gr,s),(i=R(this,Bt))==null||i.removeObs...
  method constructor (line 40) | constructor(e={}){super();ve(this,Kn);this.config=e,ce(this,Kn,new Map)}
  method build (line 40) | build(e,r,s){const i=r.queryKey,l=r.queryHash??Cf(i,r);let u=this.get(l)...
  method add (line 40) | add(e){R(this,Kn).has(e.queryHash)||(R(this,Kn).set(e.queryHash,e),this....
  method remove (line 40) | remove(e){const r=R(this,Kn).get(e.queryHash);r&&(e.destroy(),r===e&&R(t...
  method clear (line 40) | clear(){at.batch(()=>{this.getAll().forEach(e=>{this.remove(e)})})}
  method get (line 40) | get(e){return R(this,Kn).get(e)}
  method getAll (line 40) | getAll(){return[...R(this,Kn).values()]}
  method find (line 40) | find(e){const r={exact:!0,...e};return this.getAll().find(s=>Gm(r,s))}
  method findAll (line 40) | findAll(e={}){const r=this.getAll();return Object.keys(e).length>0?r.fil...
  method notify (line 40) | notify(e){at.batch(()=>{this.listeners.forEach(r=>{r(e)})})}
  method onFocus (line 40) | onFocus(){at.batch(()=>{this.getAll().forEach(e=>{e.onFocus()})})}
  method onOnline (line 40) | onOnline(){at.batch(()=>{this.getAll().forEach(e=>{e.onOnline()})})}
  method constructor (line 40) | constructor(t={}){ve(this,Ze);ve(this,Jr);ve(this,Zr);ve(this,zs);ve(thi...
  method mount (line 40) | mount(){Sl(this,eo)._++,R(this,eo)===1&&(ce(this,Us,kf.subscribe(async t...
  method unmount (line 40) | unmount(){var t,e;Sl(this,eo)._--,R(this,eo)===0&&((t=R(this,Us))==null|...
  method isFetching (line 40) | isFetching(t){return R(this,Ze).findAll({...t,fetchStatus:"fetching"}).l...
  method isMutating (line 40) | isMutating(t){return R(this,Jr).findAll({...t,status:"pending"}).length}
  method getQueryData (line 40) | getQueryData(t){var r;const e=this.defaultQueryOptions({queryKey:t});ret...
  method ensureQueryData (line 40) | ensureQueryData(t){const e=this.defaultQueryOptions(t),r=R(this,Ze).buil...
  method getQueriesData (line 40) | getQueriesData(t){return R(this,Ze).findAll(t).map(({queryKey:e,state:r}...
  method setQueryData (line 40) | setQueryData(t,e,r){const s=this.defaultQueryOptions({queryKey:t}),i=R(t...
  method setQueriesData (line 40) | setQueriesData(t,e,r){return at.batch(()=>R(this,Ze).findAll(t).map(({qu...
  method getQueryState (line 40) | getQueryState(t){var r;const e=this.defaultQueryOptions({queryKey:t});re...
  method removeQueries (line 40) | removeQueries(t){const e=R(this,Ze);at.batch(()=>{e.findAll(t).forEach(r...
  method resetQueries (line 40) | resetQueries(t,e){const r=R(this,Ze);return at.batch(()=>(r.findAll(t).f...
  method cancelQueries (line 40) | cancelQueries(t,e={}){const r={revert:!0,...e},s=at.batch(()=>R(this,Ze)...
  method invalidateQueries (line 40) | invalidateQueries(t,e={}){return at.batch(()=>(R(this,Ze).findAll(t).for...
  method refetchQueries (line 40) | refetchQueries(t,e={}){const r={...e,cancelRefetch:e.cancelRefetch??!0},...
  method fetchQuery (line 40) | fetchQuery(t){const e=this.defaultQueryOptions(t);e.retry===void 0&&(e.r...
  method prefetchQuery (line 40) | prefetchQuery(t){return this.fetchQuery(t).then(Ot).catch(Ot)}
  method fetchInfiniteQuery (line 40) | fetchInfiniteQuery(t){return t.behavior=og(t.pages),this.fetchQuery(t)}
  method prefetchInfiniteQuery (line 40) | prefetchInfiniteQuery(t){return this.fetchInfiniteQuery(t).then(Ot).catc...
  method ensureInfiniteQueryData (line 40) | ensureInfiniteQueryData(t){return t.behavior=og(t.pages),this.ensureQuer...
  method resumePausedMutations (line 40) | resumePausedMutations(){return Wl.isOnline()?R(this,Jr).resumePausedMuta...
  method getQueryCache (line 40) | getQueryCache(){return R(this,Ze)}
  method getMutationCache (line 40) | getMutationCache(){return R(this,Jr)}
  method getDefaultOptions (line 40) | getDefaultOptions(){return R(this,Zr)}
  method setDefaultOptions (line 40) | setDefaultOptions(t){ce(this,Zr,t)}
  method setQueryDefaults (line 40) | setQueryDefaults(t,e){R(this,zs).set(zo(t),{queryKey:t,defaultOptions:e})}
  method getQueryDefaults (line 40) | getQueryDefaults(t){const e=[...R(this,zs).values()],r={};return e.forEa...
  method setMutationDefaults (line 40) | setMutationDefaults(t,e){R(this,$s).set(zo(t),{mutationKey:t,defaultOpti...
  method getMutationDefaults (line 40) | getMutationDefaults(t){const e=[...R(this,$s).values()],r={};return e.fo...
  method defaultQueryOptions (line 40) | defaultQueryOptions(t){if(t._defaulted)return t;const e={...R(this,Zr).q...
  method defaultMutationOptions (line 40) | defaultMutationOptions(t){return t!=null&&t._defaulted?t:{...R(this,Zr)....
  method clear (line 40) | clear(){R(this,Ze).clear(),R(this,Jr).clear()}
  function D1 (line 40) | function D1(){let t=!1;return{clearReset:()=>{t=!1},reset:()=>{t=!0},isR...
  function W1 (line 40) | function W1(t,e,r){var v,C,w,E,b;const s=I1(),i=F1(),l=lc(),u=l.defaultQ...
  function ta (line 40) | function ta(t,e){return W1(t,k1)}
  function Xy (line 40) | function Xy(t,e){const r=lc(),[s]=x.useState(()=>new j1(r,t));x.useEffec...
  method constructor (line 40) | constructor(){this.subscribe=t=>(this.subscribers.push(t),()=>{let e=thi...
  function aS (line 40) | function aS(t,{insertAt:e}={}){if(typeof document>"u")return;let r=docum...
  function kl (line 41) | function kl(t){return t.label!==void 0}
  function Sn (line 41) | function Sn(...t){return t.filter(Boolean).join(" ")}
  function mS (line 41) | function mS(t){let[e,r]=t.split("-"),s=[];return e&&s.push(e),r&&s.push(...
  function pa (line 41) | function pa(){var Ee,Fe,He;return Q!=null&&Q.loading?oe.createElement("d...
  function lg (line 41) | function lg(){if(typeof window>"u"||typeof document>"u")return"ltr";let ...
  function yS (line 41) | function yS(t,e){let r={};return[t,e].forEach((s,i)=>{let l=i===1,u=l?"-...
  class XS (line 226) | class XS{constructor(e){this.capacity=e,this.regExpMap=new Map,this.regE...
    method constructor (line 226) | constructor(e){this.capacity=e,this.regExpMap=new Map,this.regExpQueue...
    method getRegExp (line 226) | getRegExp(e){const r=this.regExpMap.get(e);if(r!==void 0)return r;cons...
  method log (line 226) | log(t){this.output("log",t)}
  method warn (line 226) | warn(t){this.output("warn",t)}
  method error (line 226) | error(t){this.output("error",t)}
  method output (line 226) | output(t,e){var r,s;(s=(r=console==null?void 0:console[t])==null?void 0:...
  class Ql (line 226) | class Ql{constructor(e,r={}){this.init(e,r)}init(e,r={}){this.prefix=r.p...
    method constructor (line 226) | constructor(e,r={}){this.init(e,r)}
    method init (line 226) | init(e,r={}){this.prefix=r.prefix||"i18next:",this.logger=e||tC,this.o...
    method log (line 226) | log(...e){return this.forward(e,"log","",!0)}
    method warn (line 226) | warn(...e){return this.forward(e,"warn","",!0)}
    method error (line 226) | error(...e){return this.forward(e,"error","")}
    method deprecate (line 226) | deprecate(...e){return this.forward(e,"warn","WARNING DEPRECATED: ",!0)}
    method forward (line 226) | forward(e,r,s,i){return i&&!this.debug?null:(ke(e[0])&&(e[0]=`${s}${th...
    method create (line 226) | create(e){return new Ql(this.logger,{prefix:`${this.prefix}:${e}:`,......
    method clone (line 226) | clone(e){return e=e||this.options,e.prefix=e.prefix||this.prefix,new Q...
  class cc (line 226) | class cc{constructor(){this.observers={}}on(e,r){return e.split(" ").for...
    method constructor (line 226) | constructor(){this.observers={}}
    method on (line 226) | on(e,r){return e.split(" ").forEach(s=>{this.observers[s]||(this.obser...
    method off (line 226) | off(e,r){if(this.observers[e]){if(!r){delete this.observers[e];return}...
    method emit (line 226) | emit(e,...r){this.observers[e]&&Array.from(this.observers[e].entries()...
  class hg (line 226) | class hg extends cc{constructor(e,r={ns:["translation"],defaultNS:"trans...
    method constructor (line 226) | constructor(e,r={ns:["translation"],defaultNS:"translation"}){super(),...
    method addNamespaces (line 226) | addNamespaces(e){this.options.ns.indexOf(e)<0&&this.options.ns.push(e)}
    method removeNamespaces (line 226) | removeNamespaces(e){const r=this.options.ns.indexOf(e);r>-1&&this.opti...
    method getResource (line 226) | getResource(e,r,s,i={}){var p,y;const l=i.keySeparator!==void 0?i.keyS...
    method addResource (line 226) | addResource(e,r,s,i,l={silent:!1}){const u=l.keySeparator!==void 0?l.k...
    method addResources (line 226) | addResources(e,r,s,i={silent:!1}){for(const l in s)(ke(s[l])||Array.is...
    method addResourceBundle (line 226) | addResourceBundle(e,r,s,i,l,u={silent:!1,skipCopy:!1}){let d=[e,r];e.i...
    method removeResourceBundle (line 226) | removeResourceBundle(e,r){this.hasResourceBundle(e,r)&&delete this.dat...
    method hasResourceBundle (line 226) | hasResourceBundle(e,r){return this.getResource(e,r)!==void 0}
    method getResourceBundle (line 226) | getResourceBundle(e,r){return r||(r=this.options.defaultNS),this.getRe...
    method getDataByLanguage (line 226) | getDataByLanguage(e){return this.data[e]}
    method hasLanguageSomeTranslations (line 226) | hasLanguageSomeTranslations(e){const r=this.getDataByLanguage(e);retur...
    method toJSON (line 226) | toJSON(){return this.data}
  method addPostProcessor (line 226) | addPostProcessor(t){this.processors[t.name]=t}
  method handle (line 226) | handle(t,e,r,s,i){return t.forEach(l=>{var u;e=((u=this.processors[l])==...
  function nC (line 226) | function nC(){const t=[],e=Object.create(null);let r;return e.get=(s,i)=...
  function Xd (line 226) | function Xd(t,e){const{[iv]:r}=t(nC());return r.join((e==null?void 0:e.k...
  class Yl (line 226) | class Yl extends cc{constructor(e,r={}){super(),WS(["resourceStore","lan...
    method constructor (line 226) | constructor(e,r={}){super(),WS(["resourceStore","languageUtils","plura...
    method changeLanguage (line 226) | changeLanguage(e){e&&(this.language=e)}
    method exists (line 226) | exists(e,r={interpolation:{}}){const s={...r};if(e==null)return!1;cons...
    method extractFromKey (line 226) | extractFromKey(e,r){let s=r.nsSeparator!==void 0?r.nsSeparator:this.op...
    method translate (line 226) | translate(e,r,s){let i=typeof r=="object"?{...r}:r;if(typeof i!="objec...
    method extendTranslation (line 226) | extendTranslation(e,r,s,i,l){var h,p;if((h=this.i18nFormat)!=null&&h.p...
    method resolve (line 226) | resolve(e,r={}){let s,i,l,u,d;return ke(e)&&(e=[e]),e.forEach(h=>{if(t...
    method isValidLookup (line 226) | isValidLookup(e){return e!==void 0&&!(!this.options.returnNull&&e===nu...
    method getResource (line 226) | getResource(e,r,s,i={}){var l;return(l=this.i18nFormat)!=null&&l.getRe...
    method getUsedParamsDetails (line 226) | getUsedParamsDetails(e={}){const r=["defaultValue","ordinal","context"...
    method hasDefaultValue (line 226) | static hasDefaultValue(e){const r="defaultValue";for(const s in e)if(O...
  class mg (line 226) | class mg{constructor(e){this.options=e,this.supportedLngs=this.options.s...
    method constructor (line 226) | constructor(e){this.options=e,this.supportedLngs=this.options.supporte...
    method getScriptPartFromCode (line 226) | getScriptPartFromCode(e){if(e=Ki(e),!e||e.indexOf("-")<0)return null;c...
    method getLanguagePartFromCode (line 226) | getLanguagePartFromCode(e){if(e=Ki(e),!e||e.indexOf("-")<0)return e;co...
    method formatLanguageCode (line 226) | formatLanguageCode(e){if(ke(e)&&e.indexOf("-")>-1){let r;try{r=Intl.ge...
    method isSupportedCode (line 226) | isSupportedCode(e){return(this.options.load==="languageOnly"||this.opt...
    method getBestMatchFromCodes (line 226) | getBestMatchFromCodes(e){if(!e)return null;let r;return e.forEach(s=>{...
    method getFallbackCodes (line 226) | getFallbackCodes(e,r){if(!e)return[];if(typeof e=="function"&&(e=e(r))...
    method toResolveHierarchy (line 226) | toResolveHierarchy(e,r){const s=this.getFallbackCodes((r===!1?[]:r)||t...
  class rC (line 226) | class rC{constructor(e,r={}){this.languageUtils=e,this.options=r,this.lo...
    method constructor (line 226) | constructor(e,r={}){this.languageUtils=e,this.options=r,this.logger=Qn...
    method addRule (line 226) | addRule(e,r){this.rules[e]=r}
    method clearCache (line 226) | clearCache(){this.pluralRulesCache={}}
    method getRule (line 226) | getRule(e,r={}){const s=Ki(e==="dev"?"en":e),i=r.ordinal?"ordinal":"ca...
    method needsPlural (line 226) | needsPlural(e,r={}){let s=this.getRule(e,r);return s||(s=this.getRule(...
    method getPluralFormsOfKey (line 226) | getPluralFormsOfKey(e,r,s={}){return this.getSuffixes(e,s).map(i=>`${r...
    method getSuffixes (line 226) | getSuffixes(e,r={}){let s=this.getRule(e,r);return s||(s=this.getRule(...
    method getSuffix (line 226) | getSuffix(e,r,s={}){const i=this.getRule(e,s);return i?`${this.options...
  class xg (line 226) | class xg{constructor(e={}){var r;this.logger=Qn.create("interpolator"),t...
    method constructor (line 226) | constructor(e={}){var r;this.logger=Qn.create("interpolator"),this.opt...
    method init (line 226) | init(e={}){e.interpolation||(e.interpolation={escapeValue:!0});const{e...
    method reset (line 226) | reset(){this.options&&this.init(this.options)}
    method resetRegExp (line 226) | resetRegExp(){const e=(r,s)=>(r==null?void 0:r.source)===s?(r.lastInde...
    method interpolate (line 226) | interpolate(e,r,s,i){var w;let l,u,d;const h=this.options&&this.option...
    method nest (line 226) | nest(e,r,s={}){let i,l,u;const d=(h,p)=>{const y=this.nestingOptionsSe...
  class iC (line 226) | class iC{constructor(e={}){this.logger=Qn.create("formatter"),this.optio...
    method constructor (line 226) | constructor(e={}){this.logger=Qn.create("formatter"),this.options=e,th...
    method init (line 226) | init(e,r={interpolation:{}}){this.formatSeparator=r.interpolation.form...
    method add (line 226) | add(e,r){this.formats[e.toLowerCase().trim()]=r}
    method addCached (line 226) | addCached(e,r){this.formats[e.toLowerCase().trim()]=wg(r)}
    method format (line 226) | format(e,r,s,i={}){const l=r.split(this.formatSeparator);if(l.length>1...
  class lC (line 226) | class lC extends cc{constructor(e,r,s,i={}){var l,u;super(),this.backend...
    method constructor (line 226) | constructor(e,r,s,i={}){var l,u;super(),this.backend=e,this.store=r,th...
    method queueLoad (line 226) | queueLoad(e,r,s,i){const l={},u={},d={},h={};return e.forEach(p=>{let ...
    method loaded (line 226) | loaded(e,r,s){const i=e.split("|"),l=i[0],u=i[1];r&&this.emit("failedL...
    method read (line 226) | read(e,r,s,i=0,l=this.retryTimeout,u){if(!e.length)return u(null,{});i...
    method prepareLoading (line 226) | prepareLoading(e,r,s={},i){if(!this.backend)return this.logger.warn("N...
    method load (line 226) | load(e,r,s){this.prepareLoading(e,r,{},s)}
    method reload (line 226) | reload(e,r,s){this.prepareLoading(e,r,{reload:!0},s)}
    method loadOne (line 226) | loadOne(e,r=""){const s=e.split("|"),i=s[0],l=s[1];this.read(i,l,"read...
    method saveMissing (line 226) | saveMissing(e,r,s,i,l,u={},d=()=>{}){var h,p,y,v,C;if((p=(h=this.servi...
  class Vi (line 226) | class Vi extends cc{constructor(e={},r){if(super(),this.options=Sg(e),th...
    method constructor (line 226) | constructor(e={},r){if(super(),this.options=Sg(e),this.services={},thi...
    method init (line 226) | init(e={},r){this.isInitializing=!0,typeof e=="function"&&(r=e,e={}),e...
    method loadResources (line 226) | loadResources(e,r=Nl){var l,u;let s=r;const i=ke(e)?e:this.language;if...
    method reloadResources (line 226) | reloadResources(e,r,s){const i=Mi();return typeof e=="function"&&(s=e,...
    method use (line 226) | use(e){if(!e)throw new Error("You are passing an undefined module! Ple...
    method setResolvedLanguage (line 226) | setResolvedLanguage(e){if(!(!e||!this.languages)&&!(["cimode","dev"].i...
    method changeLanguage (line 226) | changeLanguage(e,r){this.isLanguageChangingTo=e;const s=Mi();this.emit...
    method getFixedT (line 226) | getFixedT(e,r,s){const i=(l,u,...d)=>{let h;typeof u!="object"?h=this....
    method t (line 226) | t(...e){var r;return(r=this.translator)==null?void 0:r.translate(...e)}
    method exists (line 226) | exists(...e){var r;return(r=this.translator)==null?void 0:r.exists(...e)}
    method setDefaultNamespace (line 226) | setDefaultNamespace(e){this.options.defaultNS=e}
    method hasLoadedNamespace (line 226) | hasLoadedNamespace(e,r={}){if(!this.isInitialized)return this.logger.w...
    method loadNamespaces (line 226) | loadNamespaces(e,r){const s=Mi();return this.options.ns?(ke(e)&&(e=[e]...
    method loadLanguages (line 226) | loadLanguages(e,r){const s=Mi();ke(e)&&(e=[e]);const i=this.options.pr...
    method dir (line 226) | dir(e){var i,l;if(e||(e=this.resolvedLanguage||(((i=this.languages)==n...
    method createInstance (line 226) | static createInstance(e={},r){const s=new Vi(e,r);return s.createInsta...
    method cloneInstance (line 226) | cloneInstance(e={},r=Nl){const s=e.forkResourceStore;s&&delete e.forkR...
    method toJSON (line 226) | toJSON(){return{options:this.options,store:this.store,language:this.la...
  method init (line 226) | init(t){yC(t.options.react),xC(t)}
  class CC (line 226) | class CC{constructor(){this.usedNamespaces={}}addUsedNamespaces(e){e.for...
    method constructor (line 226) | constructor(){this.usedNamespaces={}}
    method addUsedNamespaces (line 226) | addUsedNamespaces(e){e.forEach(r=>{this.usedNamespaces[r]||(this.usedN...
    method getUsedNamespaces (line 226) | getUsedNamespaces(){return Object.keys(this.usedNamespaces)}
  function EC (line 234) | function EC(){if(kg)return vd;kg=1;var t=ac();function e(v,C){return v==...
  function kC (line 234) | function kC(){return Ng||(Ng=1,yd.exports=EC()),yd.exports}
  function uv (line 234) | function uv(t){var e,r,s="";if(typeof t=="string"||typeof t=="number")s+...
  function dv (line 234) | function dv(){for(var t,e,r=0,s="",i=arguments.length;r<i;r++)(t=argumen...
  method get (line 234) | get(l){let u=r.get(l);if(u!==void 0)return u;if((u=s.get(l))!==void 0)re...
  method set (line 234) | set(l,u){r.has(l)?r.set(l,u):i(l,u)}
  function UC (line 234) | function UC(){let t=0,e,r,s="";for(;t<arguments.length;)(e=arguments[t++...
  function BC (line 234) | function BC(t,...e){let r,s,i,l=u;function u(h){const p=e.reduce((y,v)=>...
  function Be (line 234) | function Be(...t){return aE(dv(t))}
  function lE (line 234) | function lE(t){if(t===0)return"0 B";const e=1024,r=["B","KB","MB","GB","...
  function cE (line 234) | function cE(t){return(typeof t=="string"?new Date(t):typeof t=="number"?...
  function qi (line 234) | function qi({className:t,variant:e,...r}){return g.jsx("div",{className:...
  function hE (line 234) | function hE(t,e=fE){const r=oe.useSyncExternalStore(t.subscribe,oe.useCa...
  function pE (line 234) | function pE(){const t=localStorage.getItem(tf);if(t===null)return null;c...
  function Rl (line 234) | function Rl(t){t===null?localStorage.removeItem(tf):localStorage.setItem...
  function vv (line 234) | function vv(t,e){return function(){return t.apply(e,arguments)}}
  function ra (line 234) | function ra(t){return t!==null&&!Hs(t)&&t.constructor!==null&&!Hs(t.cons...
  function yE (line 234) | function yE(t){let e;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?e...
  function sa (line 234) | function sa(t,e,{allOwnKeys:r=!1}={}){if(t===null||typeof t>"u")return;l...
  function Sv (line 234) | function Sv(t,e){if(ra(t))return null;e=e.toLowerCase();const r=Object.k...
  function nf (line 234) | function nf(){const{caseless:t,skipUndefined:e}=Cv(this)&&this||{},r={},...
  function YE (line 234) | function YE(t){return!!(t&&Wt(t.append)&&t[xv]==="FormData"&&t[uc])}
  function Re (line 234) | function Re(t,e,r,s,i){Error.call(this),Error.captureStackTrace?Error.ca...
  function rf (line 234) | function rf(t){return U.isPlainObject(t)||U.isArray(t)}
  function Pv (line 234) | function Pv(t){return U.endsWith(t,"[]")?t.slice(0,-2):t}
  function Lg (line 234) | function Lg(t,e,r){return t?t.concat(e).map(function(i,l){return i=Pv(i)...
  function nk (line 234) | function nk(t){return U.isArray(t)&&!t.some(rf)}
  function hc (line 234) | function hc(t,e,r){if(!U.isObject(t))throw new TypeError("target must be...
  function Ig (line 234) | function Ig(t){const e={"!":"%21","'":"%27","(":"%28",")":"%29","~":"%7E...
  function If (line 234) | function If(t,e){this._pairs=[],t&&hc(t,this,e)}
  function ok (line 234) | function ok(t){return encodeURIComponent(t).replace(/%3A/gi,":").replace...
  function Ov (line 234) | function Ov(t,e,r){if(!e)return t;const s=r&&r.encode||ok;U.isFunction(r...
  class Dg (line 234) | class Dg{constructor(){this.handlers=[]}use(e,r,s){return this.handlers....
    method constructor (line 234) | constructor(){this.handlers=[]}
    method use (line 234) | use(e,r,s){return this.handlers.push({fulfilled:e,rejected:r,synchrono...
    method eject (line 234) | eject(e){this.handlers[e]&&(this.handlers[e]=null)}
    method clear (line 234) | clear(){this.handlers&&(this.handlers=[])}
    method forEach (line 234) | forEach(e){U.forEach(this.handlers,function(s){s!==null&&e(s)})}
  function hk (line 234) | function hk(t,e){return hc(t,new St.classes.URLSearchParams,{visitor:fun...
  function pk (line 234) | function pk(t){return U.matchAll(/\w+|\[(\w*)]/g,t).map(e=>e[0]==="[]"?"...
  function mk (line 234) | function mk(t){const e={},r=Object.keys(t);let s;const i=r.length;let l;...
  function _v (line 234) | function _v(t){function e(r,s,i,l){let u=r[l++];if(u==="__proto__")retur...
  function gk (line 234) | function gk(t,e,r){if(U.isString(t))try{return(e||JSON.parse)(t),U.trim(...
  function $i (line 235) | function $i(t){return t&&String(t).trim().toLowerCase()}
  function Fl (line 235) | function Fl(t){return t===!1||t==null?t:U.isArray(t)?t.map(Fl):String(t)}
  function xk (line 235) | function xk(t){const e=Object.create(null),r=/([^\s,;=]+)\s*(?:=\s*([^,;...
  function wd (line 235) | function wd(t,e,r,s,i){if(U.isFunction(s))return s.call(this,e,r);if(i&&...
  function bk (line 235) | function bk(t){return t.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(...
  function Sk (line 235) | function Sk(t,e){const r=U.toCamelCase(" "+e);["get","set","has"].forEac...
  method constructor (line 235) | constructor(e){e&&this.set(e)}
  method set (line 235) | set(e,r,s){const i=this;function l(d,h,p){const y=$i(h);if(!y)throw new ...
  method get (line 235) | get(e,r){if(e=$i(e),e){const s=U.findKey(this,e);if(s){const i=this[s];i...
  method has (line 235) | has(e,r){if(e=$i(e),e){const s=U.findKey(this,e);return!!(s&&this[s]!==v...
  method delete (line 235) | delete(e,r){const s=this;let i=!1;function l(u){if(u=$i(u),u){const d=U....
  method clear (line 235) | clear(e){const r=Object.keys(this);let s=r.length,i=!1;for(;s--;){const ...
  method normalize (line 235) | normalize(e){const r=this,s={};return U.forEach(this,(i,l)=>{const u=U.f...
  method concat (line 235) | concat(...e){return this.constructor.concat(this,...e)}
  method toJSON (line 235) | toJSON(e){const r=Object.create(null);return U.forEach(this,(s,i)=>{s!=n...
  method [Symbol.iterator] (line 235) | [Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator...
  method toString (line 235) | toString(){return Object.entries(this.toJSON()).map(([e,r])=>e+": "+r).j...
  method getSetCookie (line 236) | getSetCookie(){return this.get("set-cookie")||[]}
  method [Symbol.toStringTag] (line 236) | get[Symbol.toStringTag](){return"AxiosHeaders"}
  method from (line 236) | static from(e){return e instanceof this?e:new this(e)}
  method concat (line 236) | static concat(e,...r){const s=new this(e);return r.forEach(i=>s.set(i)),s}
  method accessor (line 236) | static accessor(e){const s=(this[Mg]=this[Mg]={accessors:{}}).accessors,...
  method set (line 236) | set(s){this[r]=s}
  function bd (line 236) | function bd(t,e){const r=this||ia,s=e||r,i=Kt.from(s.headers);let l=s.da...
  function Av (line 236) | function Av(t){return!!(t&&t.__CANCEL__)}
  function Ys (line 236) | function Ys(t,e,r){Re.call(this,t??"canceled",Re.ERR_CANCELED,e,r),this....
  function Lv (line 236) | function Lv(t,e,r){const s=r.config.validateStatus;!r.status||!s||s(r.st...
  function Ck (line 236) | function Ck(t){const e=/^([-+\w]{1,25})(:?\/\/|:)/.exec(t);return e&&e[1...
  function Ek (line 236) | function Ek(t,e){t=t||10;const r=new Array(t),s=new Array(t);let i=0,l=0...
  function kk (line 236) | function kk(t,e){let r=0,s=1e3/e,i,l;const u=(p,y=Date.now())=>{r=y,i=nu...
  method write (line 236) | write(t,e,r,s,i,l,u){if(typeof document>"u")return;const d=[`${t}=${enco...
  method read (line 236) | read(t){if(typeof document>"u")return null;const e=document.cookie.match...
  method remove (line 236) | remove(t){this.write(t,"",Date.now()-864e5,"/")}
  method write (line 236) | write(){}
  method read (line 236) | read(){return null}
  method remove (line 236) | remove(){}
  function Pk (line 236) | function Pk(t){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(t)}
  function Tk (line 236) | function Tk(t,e){return e?t.replace(/\/?\/$/,"")+"/"+e.replace(/^\/+/,""...
  function Iv (line 236) | function Iv(t,e,r){let s=!Pk(e);return t&&(s||r==!1)?Tk(t,e):e}
  function Uo (line 236) | function Uo(t,e){e=e||{};const r={};function s(p,y,v,C){return U.isPlain...
  function b (line 236) | function b(){w&&w(),E&&E(),i.cancelToken&&i.cancelToken.unsubscribe(y),i...
  function T (line 236) | function T(){if(!k)return;const _=Kt.from("getAllResponseHeaders"in k&&k...
  method pull (line 236) | async pull(h){try{const{done:p,value:y}=await i.next();if(p){d(),h.close...
  method cancel (line 236) | cancel(h){return d(h),i.return()}
  method duplex (line 236) | get duplex(){return E=!0,"half"}
  function $k (line 236) | function $k(t,e){t=U.isArray(t)?t:[t];const{length:r}=t;let s,i;const l=...
  function Sd (line 238) | function Sd(t){if(t.cancelToken&&t.cancelToken.throwIfRequested(),t.sign...
  function qg (line 238) | function qg(t){return Sd(t),t.headers=Kt.from(t.headers),t.data=bd.call(...
  function i (line 238) | function i(l,u){return"[Axios v"+zv+"] Transitional option '"+l+"'"+u+(s...
  function Uk (line 238) | function Uk(t,e,r){if(typeof t!="object")throw new Re("options must be a...
  method constructor (line 238) | constructor(e){this.defaults=e||{},this.interceptors={request:new Dg,res...
  method request (line 238) | async request(e,r){try{return await this._request(e,r)}catch(s){if(s ins...
  method _request (line 239) | _request(e,r){typeof e=="string"?(r=r||{},r.url=e):r=e||{},r=Uo(this.def...
  method getUri (line 239) | getUri(e){e=Uo(this.defaults,e);const r=Iv(e.baseURL,e.url,e.allowAbsolu...
  function r (line 239) | function r(s){return function(l,u,d){return this.request(Uo(d||{},{metho...
  method constructor (line 239) | constructor(e){if(typeof e!="function")throw new TypeError("executor mus...
  method throwIfRequested (line 239) | throwIfRequested(){if(this.reason)throw this.reason}
  method subscribe (line 239) | subscribe(e){if(this.reason){e(this.reason);return}this._listeners?this....
  method unsubscribe (line 239) | unsubscribe(e){if(!this._listeners)return;const r=this._listeners.indexO...
  method toAbortSignal (line 239) | toAbortSignal(){const e=new AbortController,r=s=>{e.abort(s)};return thi...
  method source (line 239) | static source(){let e;return{token:new $v(function(i){e=i}),cancel:e}}
  function Hk (line 239) | function Hk(t){return function(r){return t.apply(null,r)}}
  function Vk (line 239) | function Vk(t){return U.isObject(t)&&t.isAxiosError===!0}
  function Uv (line 239) | function Uv(t){const e=new Mo(t),r=vv(Mo.prototype.request,e);return U.e...
  function Kk (line 239) | function Kk(){const t=jt(r=>r.setStatus),e=jt(r=>r.setRunningInfo);retur...
  function qk (line 239) | function qk(){const t=lc(),e=jt(s=>s.setStatus),r=jt(s=>s.clearLogs);ret...
  function Qk (line 239) | function Qk(){const t=lc(),e=jt(r=>r.setStatus);return Xy({mutationFn:()...
  function Yk (line 239) | function Yk(){return ta({queryKey:["platforms"],queryFn:async()=>{const{...
  function Gk (line 239) | function Gk(){return ta({queryKey:["configOptions"],queryFn:async()=>{co...
  function af (line 239) | function af(t,[e,r]){return Math.min(r,Math.max(e,t))}
  function Me (line 239) | function Me(t,e,{checkForDefaultPrevented:r=!0}={}){return function(i){i...
  function Xk (line 239) | function Xk(t,e){const r=x.createContext(e),s=l=>{const{children:u,...d}...
  function aa (line 239) | function aa(t,e=[]){let r=[];function s(l,u){const d=x.createContext(u),...
  function Jk (line 239) | function Jk(...t){const e=t[0];if(t.length===1)return e;const r=()=>{con...
  function Yg (line 239) | function Yg(t,e){if(typeof t=="function")return t(e);t!=null&&(t.current...
  function Gs (line 239) | function Gs(...t){return e=>{let r=!1;const s=t.map(i=>{const l=Yg(i,e);...
  function Ve (line 239) | function Ve(...t){return x.useCallback(Gs(...t),t)}
  function Gg (line 239) | function Gg(t){const e=Zk(t),r=x.forwardRef((s,i)=>{const{children:l,......
  function Zk (line 239) | function Zk(t){const e=x.forwardRef((r,s)=>{const{children:i,...l}=r;if(...
  function tN (line 239) | function tN(t){return x.isValidElement(t)&&typeof t.type=="function"&&"_...
  function nN (line 239) | function nN(t,e){const r={...e};for(const s in e){const i=t[s],l=e[s];/^...
  function rN (line 239) | function rN(t){var s,i;let e=(s=Object.getOwnPropertyDescriptor(t.props,...
  function oN (line 239) | function oN(t){const e=t+"CollectionProvider",[r,s]=aa(e),[i,l]=r(e,{col...
  function Hv (line 239) | function Hv(t){const e=x.useContext(sN);return t||e||"ltr"}
  function iN (line 239) | function iN(t){const e=aN(t),r=x.forwardRef((s,i)=>{const{children:l,......
  function aN (line 239) | function aN(t){const e=x.forwardRef((r,s)=>{const{children:i,...l}=r;if(...
  function cN (line 239) | function cN(t){return x.isValidElement(t)&&typeof t.type=="function"&&"_...
  function uN (line 239) | function uN(t,e){const r={...e};for(const s in e){const i=t[s],l=e[s];/^...
  function dN (line 239) | function dN(t){var s,i;let e=(s=Object.getOwnPropertyDescriptor(t.props,...
  function hN (line 239) | function hN(t,e){t&&na.flushSync(()=>t.dispatchEvent(e))}
  function Vt (line 239) | function Vt(t){const e=x.useRef(t);return x.useEffect(()=>{e.current=t})...
  function pN (line 239) | function pN(t,e=globalThis==null?void 0:globalThis.document){const r=Vt(...
  function wN (line 239) | function wN(t,e=globalThis==null?void 0:globalThis.document){const r=Vt(...
  function bN (line 239) | function bN(t,e=globalThis==null?void 0:globalThis.document){const r=Vt(...
  function Jg (line 239) | function Jg(){const t=new CustomEvent(lf);document.dispatchEvent(t)}
  function Wv (line 239) | function Wv(t,e,r,{discrete:s}){const i=r.originalEvent.target,l=new Cus...
  function Kv (line 239) | function Kv(){x.useEffect(()=>{const t=document.querySelectorAll("[data-...
  function Zg (line 239) | function Zg(){const t=document.createElement("span");return t.setAttribu...
  method pause (line 239) | pause(){this.paused=!0}
  method resume (line 239) | resume(){this.paused=!1}
  function CN (line 239) | function CN(t,{select:e=!1}={}){const r=document.activeElement;for(const...
  function EN (line 239) | function EN(t){const e=qv(t),r=ty(e,t),s=ty(e.reverse(),t);return[r,s]}
  function qv (line 239) | function qv(t){const e=[],r=document.createTreeWalker(t,NodeFilter.SHOW_...
  function ty (line 239) | function ty(t,e){for(const r of t)if(!kN(r,{upTo:e}))return r}
  function kN (line 239) | function kN(t,{upTo:e}){if(getComputedStyle(t).visibility==="hidden")ret...
  function NN (line 239) | function NN(t){return t instanceof HTMLInputElement&&"select"in t}
  function Wr (line 239) | function Wr(t,{select:e=!1}={}){if(t&&t.focus){const r=document.activeEl...
  function RN (line 239) | function RN(){let t=[];return{add(e){const r=t[0];e!==r&&(r==null||r.pau...
  function ry (line 239) | function ry(t,e){const r=[...t],s=r.indexOf(e);return s!==-1&&r.splice(s...
  function PN (line 239) | function PN(t){return t.filter(e=>e.tagName!=="A")}
  function Ps (line 239) | function Ps(t){const[e,r]=x.useState(TN());return mt(()=>{r(s=>s??String...
  function cf (line 239) | function cf(t,e,r){return Xt(t,no(e,r))}
  function vr (line 239) | function vr(t,e){return typeof t=="function"?t(e):t}
  function xr (line 239) | function xr(t){return t.split("-")[0]}
  function Xs (line 239) | function Xs(t){return t.split("-")[1]}
  function Uf (line 239) | function Uf(t){return t==="x"?"y":"x"}
  function Bf (line 239) | function Bf(t){return t==="y"?"height":"width"}
  function Gn (line 239) | function Gn(t){return LN.has(xr(t))?"y":"x"}
  function Hf (line 239) | function Hf(t){return Uf(Gn(t))}
  function IN (line 239) | function IN(t,e,r){r===void 0&&(r=!1);const s=Xs(t),i=Hf(t),l=Bf(i);let ...
  function DN (line 239) | function DN(t){const e=Zl(t);return[uf(t),e,uf(e)]}
  function uf (line 239) | function uf(t){return t.replace(/start|end/g,e=>AN[e])}
  function zN (line 239) | function zN(t,e,r){switch(t){case"top":case"bottom":return r?e?sy:oy:e?o...
  function $N (line 239) | function $N(t,e,r,s){const i=Xs(t);let l=zN(xr(t),r==="start",s);return ...
  function Zl (line 239) | function Zl(t){return t.replace(/left|right|bottom|top/g,e=>_N[e])}
  function UN (line 239) | function UN(t){return{top:0,right:0,bottom:0,left:0,...t}}
  function Qv (line 239) | function Qv(t){return typeof t!="number"?UN(t):{top:t,right:t,bottom:t,l...
  function ec (line 239) | function ec(t){const{x:e,y:r,width:s,height:i}=t;return{width:s,height:i...
  function iy (line 239) | function iy(t,e,r){let{reference:s,floating:i}=t;const l=Gn(e),u=Hf(e),d...
  function Qi (line 239) | async function Qi(t,e){var r;e===void 0&&(e={});const{x:s,y:i,platform:l...
  method fn (line 239) | async fn(e){const{x:r,y:s,placement:i,rects:l,platform:u,elements:d,midd...
  method fn (line 239) | async fn(e){var r,s;const{placement:i,middlewareData:l,rects:u,initialPl...
  function ay (line 239) | function ay(t,e){return{top:t.top-e.height,right:t.right-e.width,bottom:...
  function ly (line 239) | function ly(t){return jN.some(e=>t[e]>=0)}
  method fn (line 239) | async fn(e){const{rects:r}=e,{strategy:s="referenceHidden",...i}=vr(t,e)...
  function KN (line 239) | async function KN(t,e){const{placement:r,platform:s,elements:i}=t,l=awai...
  method fn (line 239) | async fn(e){var r,s;const{x:i,y:l,placement:u,middlewareData:d}=e,h=awai...
  method fn (line 239) | async fn(e){const{x:r,y:s,placement:i}=e,{mainAxis:l=!0,crossAxis:u=!1,l...
  method fn (line 239) | fn(e){const{x:r,y:s,placement:i,rects:l,middlewareData:u}=e,{offset:d=0,...
  method fn (line 239) | async fn(e){var r,s;const{placement:i,rects:l,platform:u,elements:d}=e,{...
  function mc (line 239) | function mc(){return typeof window<"u"}
  function Js (line 239) | function Js(t){return Gv(t)?(t.nodeName||"").toLowerCase():"#document"}
  function Jt (line 239) | function Jt(t){var e;return(t==null||(e=t.ownerDocument)==null?void 0:e....
  function Zn (line 239) | function Zn(t){var e;return(e=(Gv(t)?t.ownerDocument:t.document)||window...
  function Gv (line 239) | function Gv(t){return mc()?t instanceof Node||t instanceof Jt(t).Node:!1}
  function Nn (line 239) | function Nn(t){return mc()?t instanceof Element||t instanceof Jt(t).Elem...
  function Jn (line 239) | function Jn(t){return mc()?t instanceof HTMLElement||t instanceof Jt(t)....
  function cy (line 239) | function cy(t){return!mc()||typeof ShadowRoot>"u"?!1:t instanceof Shadow...
  function la (line 239) | function la(t){const{overflow:e,overflowX:r,overflowY:s,display:i}=Rn(t)...
  function ZN (line 239) | function ZN(t){return JN.has(Js(t))}
  function gc (line 239) | function gc(t){return e2.some(e=>{try{return t.matches(e)}catch{return!1...
  function Vf (line 239) | function Vf(t){const e=Wf(),r=Nn(t)?Rn(t):t;return t2.some(s=>r[s]?r[s]!...
  function o2 (line 239) | function o2(t){let e=ro(t);for(;Jn(e)&&!Vs(e);){if(Vf(e))return e;if(gc(...
  function Wf (line 239) | function Wf(){return typeof CSS>"u"||!CSS.supports?!1:CSS.supports("-web...
  function Vs (line 239) | function Vs(t){return s2.has(Js(t))}
  function Rn (line 239) | function Rn(t){return Jt(t).getComputedStyle(t)}
  function yc (line 239) | function yc(t){return Nn(t)?{scrollLeft:t.scrollLeft,scrollTop:t.scrollT...
  function ro (line 239) | function ro(t){if(Js(t)==="html")return t;const e=t.assignedSlot||t.pare...
  function Xv (line 239) | function Xv(t){const e=ro(t);return Vs(e)?t.ownerDocument?t.ownerDocumen...
  function Yi (line 239) | function Yi(t,e,r){var s;e===void 0&&(e=[]),r===void 0&&(r=!0);const i=X...
  function df (line 239) | function df(t){return t.parent&&Object.getPrototypeOf(t.parent)?t.frameE...
  function Jv (line 239) | function Jv(t){const e=Rn(t);let r=parseFloat(e.width)||0,s=parseFloat(e...
  function Kf (line 239) | function Kf(t){return Nn(t)?t:t.contextElement}
  function Ts (line 239) | function Ts(t){const e=Kf(t);if(!Jn(e))return Xn(1);const r=e.getBoundin...
  function Zv (line 239) | function Zv(t){const e=Jt(t);return!Wf()||!e.visualViewport?i2:{x:e.visu...
  function a2 (line 239) | function a2(t,e,r){return e===void 0&&(e=!1),!r||e&&r!==Jt(t)?!1:e}
  function Bo (line 239) | function Bo(t,e,r,s){e===void 0&&(e=!1),r===void 0&&(r=!1);const i=t.get...
  function vc (line 239) | function vc(t,e){const r=yc(t).scrollLeft;return e?e.left+r:Bo(Zn(t)).le...
  function ex (line 239) | function ex(t,e){const r=t.getBoundingClientRect(),s=r.left+e.scrollLeft...
  function l2 (line 239) | function l2(t){let{elements:e,rect:r,offsetParent:s,strategy:i}=t;const ...
  function c2 (line 239) | function c2(t){return Array.from(t.getClientRects())}
  function u2 (line 239) | function u2(t){const e=Zn(t),r=yc(t),s=t.ownerDocument.body,i=Xt(e.scrol...
  function d2 (line 239) | function d2(t,e){const r=Jt(t),s=Zn(t),i=r.visualViewport;let l=s.client...
  function h2 (line 239) | function h2(t,e){const r=Bo(t,!0,e==="fixed"),s=r.top+t.clientTop,i=r.le...
  function dy (line 239) | function dy(t,e,r){let s;if(e==="viewport")s=d2(t,r);else if(e==="docume...
  function tx (line 239) | function tx(t,e){const r=ro(t);return r===e||!Nn(r)||Vs(r)?!1:Rn(r).posi...
  function p2 (line 239) | function p2(t,e){const r=e.get(t);if(r)return r;let s=Yi(t,[],!1).filter...
  function m2 (line 239) | function m2(t){let{element:e,boundary:r,rootBoundary:s,strategy:i}=t;con...
  function g2 (line 239) | function g2(t){const{width:e,height:r}=Jv(t);return{width:e,height:r}}
  function y2 (line 239) | function y2(t,e,r){const s=Jn(e),i=Zn(e),l=r==="fixed",u=Bo(t,!0,l,e);le...
  function Nd (line 239) | function Nd(t){return Rn(t).position==="static"}
  function fy (line 239) | function fy(t,e){if(!Jn(t)||Rn(t).position==="fixed")return null;if(e)re...
  function nx (line 239) | function nx(t,e){const r=Jt(t);if(gc(t))return r;if(!Jn(t)){let i=ro(t);...
  function x2 (line 239) | function x2(t){return Rn(t).direction==="rtl"}
  function rx (line 239) | function rx(t,e){return t.x===e.x&&t.y===e.y&&t.width===e.width&&t.heigh...
  function b2 (line 239) | function b2(t,e){let r=null,s;const i=Zn(t);function l(){var d;clearTime...
  function S2 (line 239) | function S2(t,e,r,s){s===void 0&&(s={});const{ancestorScroll:i=!0,ancest...
  function tc (line 239) | function tc(t,e){if(t===e)return!0;if(typeof t!=typeof e)return!1;if(typ...
  function ox (line 239) | function ox(t){return typeof window>"u"?1:(t.ownerDocument.defaultView||...
  function py (line 239) | function py(t,e){const r=ox(t);return Math.round(e*r)/r}
  function Rd (line 239) | function Rd(t){const e=x.useRef(t);return $l(()=>{e.current=t}),e}
  function _2 (line 239) | function _2(t){t===void 0&&(t={});const{placement:e="bottom",strategy:r=...
  function e (line 239) | function e(r){return{}.hasOwnProperty.call(r,"current")}
  method fn (line 239) | fn(r){const{element:s,padding:i}=typeof t=="function"?t(r):t;return s&&e...
  function H2 (line 239) | function H2(t){const[e,r]=x.useState(void 0);return mt(()=>{if(t){r({wid...
  function Q2 (line 239) | function Q2(t){return t!==null}
  method fn (line 239) | fn(e){var k,T,j;const{placement:r,rects:s,middlewareData:i}=e,u=((k=i.ar...
  function mx (line 239) | function mx(t){const[e,r="center"]=t.split("-");return[e,r]}
  function tR (line 239) | function tR(t){const e=nR(t),r=x.forwardRef((s,i)=>{const{children:l,......
  function nR (line 239) | function nR(t){const e=x.forwardRef((r,s)=>{const{children:i,...l}=r;if(...
  function oR (line 239) | function oR(t){return x.isValidElement(t)&&typeof t.type=="function"&&"_...
  function sR (line 239) | function sR(t,e){const r={...e};for(const s in e){const i=t[s],l=e[s];/^...
  function iR (line 239) | function iR(t){var s,i;let e=(s=Object.getOwnPropertyDescriptor(t.props,...
  function ff (line 239) | function ff({prop:t,defaultProp:e,onChange:r=()=>{},caller:s}){const[i,l...
  function lR (line 239) | function lR({defaultProp:t,onChange:e}){const[r,s]=x.useState(t),i=x.use...
  function cR (line 239) | function cR(t){return typeof t=="function"}
  function uR (line 239) | function uR(t){const e=x.useRef({value:t,previous:t});return x.useMemo((...
  function xx (line 239) | function xx(t,e){var r={};for(var s in t)Object.prototype.hasOwnProperty...
  function gR (line 239) | function gR(t,e,r){if(r||arguments.length===2)for(var s=0,i=e.length,l;s...
  function Td (line 239) | function Td(t,e){return typeof t=="function"?t(e):t&&(t.current=e),t}
  function xR (line 239) | function xR(t,e){var r=x.useState(function(){return{value:t,callback:e,f...
  function bR (line 239) | function bR(t,e){var r=xR(null,function(s){return t.forEach(function(i){...
  function SR (line 239) | function SR(t){return t}
  function CR (line 239) | function CR(t,e){e===void 0&&(e=SR);var r=[],s=!1,i={read:function(){if(...
  function ER (line 239) | function ER(t){t===void 0&&(t={});var e=CR(null);return e.options=qn({as...
  function kR (line 239) | function kR(t,e){return t.useMedium(e),wx}
  function RR (line 239) | function RR(){if(!document)return null;var t=document.createElement("sty...
  function PR (line 239) | function PR(t,e){t.styleSheet?t.styleSheet.cssText=e:t.appendChild(docum...
  function TR (line 239) | function TR(t){var e=document.head||document.getElementsByTagName("head"...
  function YR (line 279) | function YR(t){var e=x.useRef([]),r=x.useRef([0,0]),s=x.useRef(),i=x.use...
  function GR (line 279) | function GR(t){for(var e=null;t!==null;)t instanceof ShadowRoot&&(e=t.ho...
  function Xx (line 279) | function Xx(t){return t===""||t===void 0}
  function Jx (line 279) | function Jx(t){const e=Vt(t),r=x.useRef(""),s=x.useRef(0),i=x.useCallbac...
  function Zx (line 279) | function Zx(t,e,r){const i=e.length>1&&Array.from(e).every(p=>p===e[0])?...
  function vP (line 279) | function vP(t,e){return t.map((r,s)=>t[(e+s)%t.length])}
  function PP (line 279) | function PP(){const{i18n:t}=Zt(),e=_d.find(r=>r.code===t.language)||_d[0...
  function TP (line 279) | function TP(){return typeof window>"u"?"light":window.matchMedia("(prefe...
  function OP (line 279) | function OP(){if(typeof window>"u")return"light";const t=localStorage.ge...
  function Jf (line 279) | function Jf(t){const e=document.documentElement;t==="dark"?e.classList.a...
  function u0 (line 279) | function u0(t){return t==="system"?TP():t}
  function jP (line 279) | function jP(){const{theme:t,setTheme:e}=xf(),r=Ad.find(i=>i.value===t)||...
  function _P (line 279) | function _P({onShowDisclaimer:t}){const{t:e}=Zt(),{t:r}=Zt("license"),s=...
  function LP (line 279) | function LP({log:t}){const e=wy[t.level]||wy.info;return g.jsxs("div",{c...
  function DP (line 279) | function DP(t){return typeof t=="object"&&t!==null&&"then"in t}
  function h0 (line 279) | function h0(t){return t!=null&&typeof t=="object"&&"$$typeof"in t&&t.$$t...
  function p0 (line 279) | function p0(t){const e=FP(t),r=x.forwardRef((s,i)=>{let{children:l,...u}...
  function FP (line 279) | function FP(t){const e=x.forwardRef((r,s)=>{let{children:i,...l}=r;if(h0...
  function $P (line 279) | function $P(t){return x.isValidElement(t)&&typeof t.type=="function"&&"_...
  function UP (line 279) | function UP(t,e){const r={...e};for(const s in e){const i=t[s],l=e[s];/^...
  function BP (line 279) | function BP(t){var s,i;let e=(s=Object.getOwnPropertyDescriptor(t.props,...
  function VP (line 279) | function VP(t,e){return x.useReducer((r,s)=>e[r][s]??r,t)}
  function WP (line 279) | function WP(t){const[e,r]=x.useState(),s=x.useRef(null),i=x.useRef(t),l=...
  function Ll (line 279) | function Ll(t){return(t==null?void 0:t.animationName)||"none"}
  function KP (line 279) | function KP(t){var s,i;let e=(s=Object.getOwnPropertyDescriptor(t.props,...
  function qP (line 279) | function qP(t){const e=QP(t),r=x.forwardRef((s,i)=>{const{children:l,......
  function QP (line 279) | function QP(t){const e=x.forwardRef((r,s)=>{const{children:i,...l}=r;if(...
  function GP (line 279) | function GP(t){return x.isValidElement(t)&&typeof t.type=="function"&&"_...
  function XP (line 279) | function XP(t,e){const r={...e};for(const s in e){const i=t[s],l=e[s];/^...
  function JP (line 279) | function JP(t){var s,i;let e=(s=Object.getOwnPropertyDescriptor(t.props,...
  function th (line 279) | function th(t){return t?"open":"closed"}
  function xT (line 283) | function xT(t,e){return x.useReducer((r,s)=>e[r][s]??r,t)}
  function v (line 283) | function v(C,w){return PT(C,u.current,d,w)}
  function te (line 283) | function te(G){if(T.current){const W=G.clientX-T.current.left,le=G.clien...
  function ic (line 283) | function ic(t){return t?parseInt(t,10):0}
  function Q0 (line 283) | function Q0(t,e){const r=t/e;return isNaN(r)?0:r}
  function Ec (line 283) | function Ec(t){const e=Q0(t.viewport,t.content),r=t.scrollbar.paddingSta...
  function PT (line 283) | function PT(t,e,r,s="ltr"){const i=Ec(r),l=i/2,u=e||l,d=i-u,h=r.scrollba...
  function by (line 283) | function by(t,e,r="ltr"){const s=Ec(e),i=e.scrollbar.paddingStart+e.scro...
  function Y0 (line 283) | function Y0(t,e){return r=>{if(t[0]===t[1]||e[0]===e[1])return e[0];cons...
  function G0 (line 283) | function G0(t,e){return t>0&&t<e}
  function kc (line 283) | function kc(t,e){const r=Vt(t),s=x.useRef(0);return x.useEffect(()=>()=>...
  function Ws (line 283) | function Ws(t,e){const r=Vt(e);mt(()=>{let s=0;if(t){const i=new ResizeO...
  function _T (line 283) | function _T({data:t,columns:e}){const{t:r}=Zt("data"),[s,i]=x.useState("...
  function AT (line 283) | function AT({file:t,open:e,onOpenChange:r}){const{t:s}=Zt("data"),{data:...
  function DT (line 283) | function DT({file:t}){const{t:e}=Zt("data"),[r,s]=x.useState(!1),i=LT[t....
  function MT (line 283) | function MT(t){const e=t.match(/^(search_\w+?)_/);if(e)return e[1];const...
  function FT (line 283) | function FT(t){return{search_comments:"Comments",search_creators:"Creato...
  function zT (line 283) | function zT(){const{t}=Zt("data"),[e,r]=x.useState("all"),{data:s,isLoad...
  function $T (line 283) | function $T(){const{t}=Zt("data");return g.jsxs(I0,{children:[g.jsx(fT,{...
  function UT (line 283) | function UT(){const{t}=Zt("terminal"),[e,r]=x.useState(!1),s=jt(p=>p.log...
  function BT (line 291) | function BT(){const t=jt(r=>r.addLog),e=x.useRef(t);return x.useEffect((...
  function HT (line 291) | function HT(){return BT(),g.jsx("main",{className:"flex-1 flex flex-col ...
  function VT (line 291) | function VT(){const{t}=Zt("license");return g.jsx("footer",{className:"h...
  function GT (line 291) | function GT(t,e){const r=t.trim();if(!r.includes("/")&&!r.includes("."))...
  function XT (line 291) | function XT(t,e){return t.trim()?t.split(/[,\n]+/).map(s=>s.trim()).filt...
  function Sy (line 291) | function Sy({value:t,platform:e,type:r,onRemove:s,disabled:i}){const l=x...
  function JT (line 291) | function JT({item:t,expectedType:e,onRemove:r}){const s=t.type==="unknow...
  function Ld (line 294) | function Ld({title:t,description:e,icon:r,children:s,className:i=""}){re...
  function dr (line 294) | function dr({label:t,hint:e,children:r}){return g.jsxs("div",{className:...
  function ZT (line 294) | function ZT({value:t,onChange:e,placeholder:r,disabled:s}){const[i,l]=x....
  function eO (line 294) | function eO(){const{t}=Zt("config"),e=jt(b=>b.config),r=jt(b=>b.updateCo...
  function tO (line 294) | function tO(){return localStorage.getItem(rw)==="true"}
  function nO (line 294) | function nO({onCheckComplete:t}){const{t:e}=Zt("env"),[r,s]=x.useState("...
  function rO (line 294) | function rO(){return localStorage.getItem(ow)==="true"}
  function oO (line 294) | function oO({onAccept:t}){const{t:e}=Zt("license"),r=()=>{localStorage.s...
  function sO (line 311) | function sO(){const[t,e]=x.useState(()=>rO()),[r,s]=x.useState(()=>tO())...
  function lO (line 311) | function lO(t){return aO.call(iO.call(arguments,1),e=>{if(e)for(const r ...
  function cO (line 311) | function cO(t){return typeof t!="string"?!1:[/<\s*script.*?>/i,/<\s*\/\s...
  method create (line 311) | create(t,e,r,s){let i=arguments.length>4&&arguments[4]!==void 0?argument...
  method read (line 311) | read(t){const e=`${t}=`,r=document.cookie.split(";");for(let s=0;s<r.len...
  method remove (line 311) | remove(t,e){this.create(t,"",-1,e)}
  method lookup (line 311) | lookup(t){let{lookupCookie:e}=t;if(e&&typeof document<"u")return Ey.read...
  method cacheUserLanguage (line 311) | cacheUserLanguage(t,e){let{lookupCookie:r,cookieMinutes:s,cookieDomain:i...
  method lookup (line 311) | lookup(t){var s;let{lookupQuerystring:e}=t,r;if(typeof window<"u"){let{s...
  method lookup (line 311) | lookup(t){var i;let{lookupHash:e,lookupFromHashIndex:r}=t,s;if(typeof wi...
  method lookup (line 311) | lookup(t){let{lookupLocalStorage:e}=t;if(e&&ky())return window.localStor...
  method cacheUserLanguage (line 311) | cacheUserLanguage(t,e){let{lookupLocalStorage:r}=e;r&&ky()&&window.local...
  method lookup (line 311) | lookup(t){let{lookupSessionStorage:e}=t;if(e&&Ny())return window.session...
  method cacheUserLanguage (line 311) | cacheUserLanguage(t,e){let{lookupSessionStorage:r}=e;r&&Ny()&&window.ses...
  method lookup (line 311) | lookup(t){const e=[];if(typeof navigator<"u"){const{languages:r,userLang...
  method lookup (line 311) | lookup(t){let{htmlTag:e}=t,r;const s=e||(typeof document<"u"?document.do...
  method lookup (line 311) | lookup(t){var i;let{lookupFromPathIndex:e}=t;if(typeof window>"u")return...
  method lookup (line 311) | lookup(t){var i,l;let{lookupFromSubdomainIndex:e}=t;const r=typeof e=="n...
  class aw (line 311) | class aw{constructor(e){let r=arguments.length>1&&arguments[1]!==void 0?...
    method constructor (line 311) | constructor(e){let r=arguments.length>1&&arguments[1]!==void 0?argumen...
    method init (line 311) | init(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{l...
    method addDetector (line 311) | addDetector(e){return this.detectors[e.name]=e,this}
    method detect (line 311) | detect(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:...
    method cacheUserLanguage (line 311) | cacheUserLanguage(e){let r=arguments.length>1&&arguments[1]!==void 0?a...

FILE: base/base_crawler.py
  class AbstractCrawler (line 26) | class AbstractCrawler(ABC):
    method start (line 29) | async def start(self):
    method search (line 36) | async def search(self):
    method launch_browser (line 43) | async def launch_browser(self, chromium: BrowserType, playwright_proxy...
    method launch_browser_with_cdp (line 54) | async def launch_browser_with_cdp(self, playwright: Playwright, playwr...
  class AbstractLogin (line 67) | class AbstractLogin(ABC):
    method begin (line 70) | async def begin(self):
    method login_by_qrcode (line 74) | async def login_by_qrcode(self):
    method login_by_mobile (line 78) | async def login_by_mobile(self):
    method login_by_cookies (line 82) | async def login_by_cookies(self):
  class AbstractStore (line 86) | class AbstractStore(ABC):
    method store_content (line 89) | async def store_content(self, content_item: Dict):
    method store_comment (line 93) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 99) | async def store_creator(self, creator: Dict):
  class AbstractStoreImage (line 103) | class AbstractStoreImage(ABC):
    method store_image (line 107) | async def store_image(self, image_content_item: Dict):
  class AbstractStoreVideo (line 111) | class AbstractStoreVideo(ABC):
    method store_video (line 115) | async def store_video(self, video_content_item: Dict):
  class AbstractApiClient (line 119) | class AbstractApiClient(ABC):
    method request (line 122) | async def request(self, method, url, **kwargs):
    method update_cookies (line 126) | async def update_cookies(self, browser_context: BrowserContext):

FILE: cache/abs_cache.py
  class AbstractCache (line 31) | class AbstractCache(ABC):
    method get (line 34) | def get(self, key: str) -> Optional[Any]:
    method set (line 44) | def set(self, key: str, value: Any, expire_time: int) -> None:
    method keys (line 56) | def keys(self, pattern: str) -> List[str]:

FILE: cache/cache_factory.py
  class CacheFactory (line 28) | class CacheFactory:
    method create_cache (line 34) | def create_cache(cache_type: str, *args, **kwargs):

FILE: cache/local_cache.py
  class ExpiringLocalCache (line 34) | class ExpiringLocalCache(AbstractCache):
    method __init__ (line 36) | def __init__(self, cron_interval: int = 10):
    method __del__ (line 48) | def __del__(self):
    method get (line 56) | def get(self, key: str) -> Optional[Any]:
    method set (line 73) | def set(self, key: str, value: Any, expire_time: int) -> None:
    method keys (line 83) | def keys(self, pattern: str) -> List[str]:
    method _schedule_clear (line 98) | def _schedule_clear(self):
    method _clear (line 112) | def _clear(self):
    method _start_clear_cron (line 121) | async def _start_clear_cron(self):

FILE: cache/redis_cache.py
  class RedisCache (line 37) | class RedisCache(AbstractCache):
    method __init__ (line 39) | def __init__(self) -> None:
    method _connet_redis (line 44) | def _connet_redis() -> Redis:
    method get (line 56) | def get(self, key: str) -> Any:
    method set (line 67) | def set(self, key: str, value: Any, expire_time: int) -> None:
    method keys (line 77) | def keys(self, pattern: str) -> List[str]:

FILE: cmd_arg/arg.py
  class PlatformEnum (line 39) | class PlatformEnum(str, Enum):
  class LoginTypeEnum (line 51) | class LoginTypeEnum(str, Enum):
  class CrawlerTypeEnum (line 59) | class CrawlerTypeEnum(str, Enum):
  class SaveDataOptionEnum (line 67) | class SaveDataOptionEnum(str, Enum):
  class InitDbOptionEnum (line 80) | class InitDbOptionEnum(str, Enum):
  function _to_bool (line 88) | def _to_bool(value: bool | str) -> bool:
  function _coerce_enum (line 94) | def _coerce_enum(
  function _normalize_argv (line 114) | def _normalize_argv(argv: Optional[Sequence[str]]) -> Iterable[str]:
  function _inject_init_db_default (line 120) | def _inject_init_db_default(args: Sequence[str]) -> list[str]:
  function parse_cmd (line 138) | async def parse_cmd(argv: Optional[Sequence[str]] = None):

FILE: database/db.py
  function init_table_schema (line 35) | async def init_table_schema(db_type: str):
  function init_db (line 46) | async def init_db(db_type: str = None):
  function close (line 49) | async def close():

FILE: database/db_session.py
  function create_database_if_not_exists (line 31) | async def create_database_if_not_exists(db_type: str):
  function get_async_engine (line 53) | def get_async_engine(db_type: str = None):
  function create_tables (line 77) | async def create_tables(db_type: str = None):
  function get_session (line 88) | async def get_session() -> AsyncSession:

FILE: database/models.py
  class BilibiliVideo (line 25) | class BilibiliVideo(Base):
  class BilibiliVideoComment (line 50) | class BilibiliVideoComment(Base):
  class BilibiliUpInfo (line 68) | class BilibiliUpInfo(Base):
  class BilibiliContactInfo (line 83) | class BilibiliContactInfo(Base):
  class BilibiliUpDynamic (line 97) | class BilibiliUpDynamic(Base):
  class DouyinAweme (line 112) | class DouyinAweme(Base):
  class DouyinAwemeComment (line 141) | class DouyinAwemeComment(Base):
  class DyCreator (line 163) | class DyCreator(Base):
  class KuaishouVideo (line 179) | class KuaishouVideo(Base):
  class KuaishouVideoComment (line 199) | class KuaishouVideoComment(Base):
  class WeiboNote (line 213) | class WeiboNote(Base):
  class WeiboNoteComment (line 234) | class WeiboNoteComment(Base):
  class WeiboCreator (line 254) | class WeiboCreator(Base):
  class XhsCreator (line 269) | class XhsCreator(Base):
  class XhsNote (line 285) | class XhsNote(Base):
  class XhsNoteComment (line 311) | class XhsNoteComment(Base):
  class TiebaNote (line 329) | class TiebaNote(Base):
  class TiebaComment (line 350) | class TiebaComment(Base):
  class TiebaCreator (line 370) | class TiebaCreator(Base):
  class ZhihuContent (line 385) | class ZhihuContent(Base):
  class ZhihuComment (line 413) | class ZhihuComment(Base):
  class ZhihuCreator (line 433) | class ZhihuCreator(Base):

FILE: database/mongodb_store_base.py
  class MongoDBConnection (line 27) | class MongoDBConnection:
    method __new__ (line 34) | def __new__(cls):
    method get_client (line 39) | async def get_client(self) -> AsyncIOMotorClient:
    method get_db (line 47) | async def get_db(self) -> AsyncIOMotorDatabase:
    method _connect (line 55) | async def _connect(self):
    method close (line 79) | async def close(self):
  class MongoDBStoreBase (line 88) | class MongoDBStoreBase:
    method __init__ (line 91) | def __init__(self, collection_prefix: str):
    method get_collection (line 99) | async def get_collection(self, collection_suffix: str) -> AsyncIOMotor...
    method save_or_update (line 105) | async def save_or_update(self, collection_suffix: str, query: Dict, da...
    method find_one (line 115) | async def find_one(self, collection_suffix: str, query: Dict) -> Optio...
    method find_many (line 124) | async def find_many(self, collection_suffix: str, query: Dict, limit: ...
    method create_index (line 136) | async def create_index(self, collection_suffix: str, keys: List[tuple]...

FILE: libs/douyin.js
  function rc4_encrypt (line 3) | function rc4_encrypt(plaintext, key) {
  function le (line 31) | function le(e, r) {
  function de (line 35) | function de(e) {
  function pe (line 39) | function pe(e, r, t, n) {
  function he (line 44) | function he(e, r, t, n) {
  function reset (line 49) | function reset() {
  function write (line 62) | function write(e) {
  function sum (line 86) | function sum(e, t) {
  function _compress (line 113) | function _compress(t) {
  function _fill (line 153) | function _fill() {
  function SM3 (line 167) | function SM3() {
  function result_encrypt (line 179) | function result_encrypt(long_str, num = null) {
  function get_long_int (line 227) | function get_long_int(round, long_str) {
  function gener_random (line 232) | function gener_random(random, option) {
  function generate_rc4_bb_str (line 242) | function generate_rc4_bb_str(url_search_params, user_agent, window_env_s...
  function generate_random_str (line 406) | function generate_random_str() {
  function sign (line 414) | function sign(url_search_params, user_agent, arguments) {
  function sign_datail (line 429) | function sign_datail(params, userAgent) {
  function sign_reply (line 433) | function sign_reply(params, userAgent) {

FILE: libs/zhihu.js
  function i (line 15) | function i(e, t, n) {
  function Q (line 22) | function Q(e, t) {
  function B (line 26) | function B(e, t) {
  function G (line 30) | function G(e) {
  function array_0_16_offset (line 43) | function array_0_16_offset(e) {
  function array_16_48_offset (line 62) | function array_16_48_offset(e, t) {
  function encode_0_16 (line 73) | function encode_0_16(array_0_16) {
  function encode (line 84) | function encode(ar) {
  function get_init_array (line 100) | function get_init_array(encode_md5) {
  function get_zse_96 (line 116) | function get_zse_96(encode_md5) {
  function get_sign (line 155) | function get_sign(url, cookies) {

FILE: main.py
  class CrawlerFactory (line 50) | class CrawlerFactory:
    method create_crawler (line 62) | def create_crawler(platform: str) -> AbstractCrawler:
  function _flush_excel_if_needed (line 73) | def _flush_excel_if_needed() -> None:
  function _generate_wordcloud_if_needed (line 86) | async def _generate_wordcloud_if_needed() -> None:
  function main (line 100) | async def main() -> None:
  function async_cleanup (line 119) | async def async_cleanup() -> None:
  function _force_stop (line 144) | def _force_stop() -> None:

FILE: media_platform/bilibili/client.py
  class BilibiliClient (line 47) | class BilibiliClient(AbstractApiClient, ProxyRefreshMixin):
    method __init__ (line 49) | def __init__(
    method request (line 68) | async def request(self, method, url, **kwargs) -> Any:
    method pre_request_data (line 84) | async def pre_request_data(self, req_data: Dict) -> Dict:
    method get_wbi_keys (line 97) | async def get_wbi_keys(self) -> Tuple[str, str]:
    method get (line 119) | async def get(self, uri: str, params=None, enable_params_sign: bool = ...
    method post (line 128) | async def post(self, uri: str, data: dict) -> Dict:
    method pong (line 133) | async def pong(self) -> bool:
    method update_cookies (line 148) | async def update_cookies(self, browser_context: BrowserContext):
    method search_video_by_keyword (line 153) | async def search_video_by_keyword(
    method get_video_info (line 184) | async def get_video_info(self, aid: Union[int, None] = None, bvid: Uni...
    method get_video_play_url (line 202) | async def get_video_play_url(self, aid: int, cid: int) -> Dict:
    method get_video_media (line 224) | async def get_video_media(self, url: str) -> Union[bytes, None]:
    method get_video_comments (line 240) | async def get_video_comments(
    method get_video_all_comments (line 256) | async def get_video_all_comments(
    method get_video_all_level_two_comments (line 329) | async def get_video_all_level_two_comments(
    method get_video_level_two_comments (line 361) | async def get_video_level_two_comments(
    method get_creator_videos (line 388) | async def get_creator_videos(self, creator_id: str, pn: int, ps: int =...
    method get_creator_info (line 406) | async def get_creator_info(self, creator_id: int) -> Dict:
    method get_creator_fans (line 417) | async def get_creator_fans(
    method get_creator_followings (line 439) | async def get_creator_followings(
    method get_creator_dynamics (line 461) | async def get_creator_dynamics(self, creator_id: int, offset: str = ""):
    method get_creator_all_fans (line 477) | async def get_creator_all_fans(
    method get_creator_all_followings (line 511) | async def get_creator_all_followings(
    method get_creator_all_dynamics (line 545) | async def get_creator_all_dynamics(

FILE: media_platform/bilibili/core.py
  class BilibiliCrawler (line 57) | class BilibiliCrawler(AbstractCrawler):
    method __init__ (line 63) | def __init__(self):
    method start (line 69) | async def start(self):
    method search (line 132) | async def search(self):
    method get_pubtime_datetime (line 147) | async def get_pubtime_datetime(
    method search_by_keywords (line 179) | async def search_by_keywords(self):
    method search_by_keywords_in_time_range (line 236) | async def search_by_keywords_in_time_range(self, daily_limit: bool):
    method batch_get_video_comments (line 321) | async def batch_get_video_comments(self, video_id_list: List[str]):
    method get_comments (line 339) | async def get_comments(self, video_id: str, semaphore: asyncio.Semapho...
    method get_creator_videos (line 366) | async def get_creator_videos(self, creator_id: int):
    method get_specified_videos (line 383) | async def get_specified_videos(self, video_url_list: List[str]):
    method get_video_info_task (line 415) | async def get_video_info_task(self, aid: int, bvid: str, semaphore: as...
    method get_video_play_url_task (line 439) | async def get_video_play_url_task(self, aid: int, cid: int, semaphore:...
    method create_bilibili_client (line 458) | async def create_bilibili_client(self, httpx_proxy: Optional[str]) -> ...
    method launch_browser (line 481) | async def launch_browser(
    method launch_browser_with_cdp (line 520) | async def launch_browser_with_cdp(
    method close (line 551) | async def close(self):
    method get_bilibili_video (line 566) | async def get_bilibili_video(self, video_item: Dict, semaphore: asynci...
    method get_all_creator_details (line 603) | async def get_all_creator_details(self, creator_url_list: List[str]):
    method get_creator_details (line 633) | async def get_creator_details(self, creator_id: int, semaphore: asynci...
    method get_fans (line 652) | async def get_fans(self, creator_info: Dict, semaphore: asyncio.Semaph...
    method get_followings (line 675) | async def get_followings(self, creator_info: Dict, semaphore: asyncio....
    method get_dynamics (line 698) | async def get_dynamics(self, creator_info: Dict, semaphore: asyncio.Se...

FILE: media_platform/bilibili/exception.py
  class DataFetchError (line 29) | class DataFetchError(RequestError):
  class IPBlockError (line 33) | class IPBlockError(RequestError):

FILE: media_platform/bilibili/field.py
  class SearchOrderType (line 29) | class SearchOrderType(Enum):
  class CommentOrderType (line 46) | class CommentOrderType(Enum):

FILE: media_platform/bilibili/help.py
  class BilibiliSign (line 35) | class BilibiliSign:
    method __init__ (line 36) | def __init__(self, img_key: str, sub_key: str):
    method get_salt (line 46) | def get_salt(self) -> str:
    method sign (line 57) | def sign(self, req_data: Dict) -> Dict:
  function parse_video_info_from_url (line 80) | def parse_video_info_from_url(url: str) -> VideoUrlInfo:
  function parse_creator_info_from_url (line 107) | def parse_creator_info_from_url(url: str) -> CreatorUrlInfo:

FILE: media_platform/bilibili/login.py
  class BilibiliLogin (line 40) | class BilibiliLogin(AbstractLogin):
    method __init__ (line 41) | def __init__(self,
    method begin (line 54) | async def begin(self):
    method check_login_state (line 68) | async def check_login_state(self) -> bool:
    method login_by_qrcode (line 80) | async def login_by_qrcode(self):
    method login_by_mobile (line 116) | async def login_by_mobile(self):
    method login_by_cookies (line 119) | async def login_by_cookies(self):

FILE: media_platform/douyin/client.py
  class DouYinClient (line 43) | class DouYinClient(AbstractApiClient, ProxyRefreshMixin):
    method __init__ (line 45) | def __init__(
    method __process_req_params (line 64) | async def __process_req_params(
    method request (line 116) | async def request(self, method, url, **kwargs):
    method get (line 130) | async def get(self, uri: str, params: Optional[Dict] = None, headers: ...
    method post (line 138) | async def post(self, uri: str, data: dict, headers: Optional[Dict] = N...
    method pong (line 143) | async def pong(self, browser_context: BrowserContext) -> bool:
    method update_cookies (line 151) | async def update_cookies(self, browser_context: BrowserContext):
    method search_info_by_keyword (line 156) | async def search_info_by_keyword(
    method get_video_by_id (line 198) | async def get_video_by_id(self, aweme_id: str) -> Any:
    method get_aweme_comments (line 210) | async def get_aweme_comments(self, aweme_id: str, cursor: int = 0):
    method get_sub_comments (line 222) | async def get_sub_comments(self, aweme_id: str, comment_id: str, curso...
    method get_aweme_all_comments (line 240) | async def get_aweme_all_comments(
    method get_user_info (line 299) | async def get_user_info(self, sec_user_id: str):
    method get_user_aweme_posts (line 308) | async def get_user_aweme_posts(self, sec_user_id: str, max_cursor: str...
    method get_all_user_aweme_posts (line 321) | async def get_all_user_aweme_posts(self, sec_user_id: str, callback: O...
    method get_aweme_media (line 336) | async def get_aweme_media(self, url: str) -> Union[bytes, None]:
    method resolve_short_url (line 350) | async def resolve_short_url(self, short_url: str) -> str:

FILE: media_platform/douyin/core.py
  class DouYinCrawler (line 49) | class DouYinCrawler(AbstractCrawler):
    method __init__ (line 55) | def __init__(self) -> None:
    method start (line 60) | async def start(self) -> None:
    method search (line 117) | async def search(self) -> None:
    method get_specified_awemes (line 173) | async def get_specified_awemes(self):
    method get_aweme_detail (line 208) | async def get_aweme_detail(self, aweme_id: str, semaphore: asyncio.Sem...
    method batch_get_note_comments (line 224) | async def batch_get_note_comments(self, aweme_list: List[str]) -> None:
    method get_comments (line 240) | async def get_comments(self, aweme_id: str, semaphore: asyncio.Semapho...
    method get_creators_and_videos (line 260) | async def get_creators_and_videos(self) -> None:
    method fetch_creator_video_detail (line 286) | async def fetch_creator_video_detail(self, video_list: List[Dict]):
    method create_douyin_client (line 299) | async def create_douyin_client(self, httpx_proxy: Optional[str]) -> Do...
    method launch_browser (line 318) | async def launch_browser(
    method launch_browser_with_cdp (line 345) | async def launch_browser_with_cdp(
    method close (line 379) | async def close(self) -> None:
    method get_aweme_media (line 389) | async def get_aweme_media(self, aweme_item: Dict):
    method get_aweme_images (line 409) | async def get_aweme_images(self, aweme_item: Dict):
    method get_aweme_video (line 436) | async def get_aweme_video(self, aweme_item: Dict):

FILE: media_platform/douyin/exception.py
  class DataFetchError (line 24) | class DataFetchError(RequestError):
  class IPBlockError (line 28) | class IPBlockError(RequestError):

FILE: media_platform/douyin/field.py
  class SearchChannelType (line 24) | class SearchChannelType(Enum):
  class SearchSortType (line 32) | class SearchSortType(Enum):
  class PublishTimeType (line 38) | class PublishTimeType(Enum):

FILE: media_platform/douyin/help.py
  function get_web_id (line 39) | def get_web_id():
  function get_a_bogus (line 61) | async def get_a_bogus(url: str, params: str, post_data: dict, user_agent...
  function get_a_bogus_from_js (line 67) | def get_a_bogus_from_js(url: str, params: str, user_agent: str):
  function get_a_bogus_from_playwright (line 85) | async def get_a_bogus_from_playwright(params: str, post_data: dict, user...
  function parse_video_info_from_url (line 101) | def parse_video_info_from_url(url: str) -> VideoUrlInfo:
  function parse_creator_info_from_url (line 141) | def parse_creator_info_from_url(url: str) -> CreatorUrlInfo:

FILE: media_platform/douyin/login.py
  class DouYinLogin (line 37) | class DouYinLogin(AbstractLogin):
    method __init__ (line 39) | def __init__(self,
    method begin (line 53) | async def begin(self):
    method check_login_state (line 92) | async def check_login_state(self):
    method popup_login_dialog (line 111) | async def popup_login_dialog(self):
    method login_by_qrcode (line 124) | async def login_by_qrcode(self):
    method login_by_mobile (line 139) | async def login_by_mobile(self):
    method check_page_display_slider (line 171) | async def check_page_display_slider(self, move_step: int = 10, slider_...
    method move_slider (line 213) | async def move_slider(self, back_selector: str, gap_selector: str, mov...
    method login_by_cookies (line 266) | async def login_by_cookies(self):

FILE: media_platform/kuaishou/client.py
  class KuaiShouClient (line 43) | class KuaiShouClient(AbstractApiClient, ProxyRefreshMixin):
    method __init__ (line 44) | def __init__(
    method request (line 65) | async def request(self, method, url, **kwargs) -> Any:
    method get (line 77) | async def get(self, uri: str, params=None) -> Dict:
    method post (line 85) | async def post(self, uri: str, data: dict) -> Dict:
    method request_rest_v2 (line 91) | async def request_rest_v2(self, uri: str, data: dict) -> Dict:
    method pong (line 114) | async def pong(self) -> bool:
    method update_cookies (line 136) | async def update_cookies(self, browser_context: BrowserContext):
    method search_info_by_keyword (line 141) | async def search_info_by_keyword(
    method get_video_info (line 163) | async def get_video_info(self, photo_id: str) -> Dict:
    method get_video_comments (line 176) | async def get_video_comments(self, photo_id: str, pcursor: str = "") -...
    method get_video_sub_comments (line 188) | async def get_video_sub_comments(
    method get_creator_profile (line 204) | async def get_creator_profile(self, userId: str) -> Dict:
    method get_video_by_creater (line 212) | async def get_video_by_creater(self, userId: str, pcursor: str = "") -...
    method get_video_all_comments (line 220) | async def get_video_all_comments(
    method get_comments_all_sub_comments (line 256) | async def get_comments_all_sub_comments(
    method get_creator_info (line 307) | async def get_creator_info(self, user_id: str) -> Dict:
    method get_all_videos_by_creator (line 316) | async def get_all_videos_by_creator(

FILE: media_platform/kuaishou/core.py
  class KuaishouCrawler (line 51) | class KuaishouCrawler(AbstractCrawler):
    method __init__ (line 57) | def __init__(self):
    method start (line 63) | async def start(self):
    method search (line 128) | async def search(self):
    method get_specified_videos (line 183) | async def get_specified_videos(self):
    method get_video_info_task (line 207) | async def get_video_info_task(
    method batch_get_video_comments (line 234) | async def batch_get_video_comments(self, video_id_list: List[str]):
    method get_comments (line 260) | async def get_comments(self, video_id: str, semaphore: asyncio.Semapho...
    method create_ks_client (line 302) | async def create_ks_client(self, httpx_proxy: Optional[str]) -> KuaiSh...
    method launch_browser (line 325) | async def launch_browser(
    method launch_browser_with_cdp (line 357) | async def launch_browser_with_cdp(
    method get_creators_and_videos (line 392) | async def get_creators_and_videos(self) -> None:
    method fetch_creator_video_detail (line 424) | async def fetch_creator_video_detail(self, video_list: List[Dict]):
    method close (line 439) | async def close(self):

FILE: media_platform/kuaishou/exception.py
  class DataFetchError (line 24) | class DataFetchError(RequestError):
  class IPBlockError (line 28) | class IPBlockError(RequestError):

FILE: media_platform/kuaishou/graphql.py
  class KuaiShouGraphQL (line 26) | class KuaiShouGraphQL:
    method __init__ (line 29) | def __init__(self):
    method load_graphql_queries (line 33) | def load_graphql_queries(self):
    method get (line 41) | def get(self, query_name: str) -> str:

FILE: media_platform/kuaishou/help.py
  function parse_video_info_from_url (line 27) | def parse_video_info_from_url(url: str) -> VideoUrlInfo:
  function parse_creator_info_from_url (line 53) | def parse_creator_info_from_url(url: str) -> CreatorUrlInfo:

FILE: media_platform/kuaishou/login.py
  class KuaishouLogin (line 35) | class KuaishouLogin(AbstractLogin):
    method __init__ (line 36) | def __init__(self,
    method begin (line 49) | async def begin(self):
    method check_login_state (line 62) | async def check_login_state(self) -> bool:
    method login_by_qrcode (line 75) | async def login_by_qrcode(self):
    method login_by_mobile (line 111) | async def login_by_mobile(self):
    method login_by_cookies (line 114) | async def login_by_cookies(self):

FILE: media_platform/tieba/client.py
  class BaiduTieBaClient (line 39) | class BaiduTieBaClient(AbstractApiClient):
    method __init__ (line 41) | def __init__(
    method _sync_request (line 61) | def _sync_request(self, method, url, proxy=None, **kwargs):
    method _refresh_proxy_if_expired (line 92) | async def _refresh_proxy_if_expired(self) -> None:
    method request (line 111) | async def request(self, method, url, return_ori_content=False, proxy=N...
    method get (line 152) | async def get(self, uri: str, params=None, return_ori_content=False, *...
    method post (line 181) | async def post(self, uri: str, data: dict, **kwargs) -> Dict:
    method pong (line 194) | async def pong(self, browser_context: BrowserContext = None) -> bool:
    method update_cookies (line 230) | async def update_cookies(self, browser_context: BrowserContext):
    method get_notes_by_keyword (line 243) | async def get_notes_by_keyword(
    method get_note_by_id (line 302) | async def get_note_by_id(self, note_id: str) -> TiebaNote:
    method get_note_all_comments (line 338) | async def get_note_all_comments(
    method get_comments_all_sub_comments (line 410) | async def get_comments_all_sub_comments(
    method get_notes_by_tieba_name (line 492) | async def get_notes_by_tieba_name(self, tieba_name: str, page_num: int...
    method get_creator_info_by_url (line 530) | async def get_creator_info_by_url(self, creator_url: str) -> str:
    method get_notes_by_creator (line 562) | async def get_notes_by_creator(self, user_name: str, page_number: int)...
    method get_all_notes_by_creator_user_name (line 606) | async def get_all_notes_by_creator_user_name(

FILE: media_platform/tieba/core.py
  class TieBaCrawler (line 49) | class TieBaCrawler(AbstractCrawler):
    method __init__ (line 55) | def __init__(self) -> None:
    method start (line 61) | async def start(self) -> None:
    method search (line 144) | async def search(self) -> None:
    method get_specified_tieba_notes (line 206) | async def get_specified_tieba_notes(self):
    method get_specified_notes (line 243) | async def get_specified_notes(
    method get_note_detail_async_task (line 267) | async def get_note_detail_async_task(
    method batch_get_note_comments (line 307) | async def batch_get_note_comments(self, note_detail_list: List[TiebaNo...
    method get_comments_async_task (line 329) | async def get_comments_async_task(
    method get_creators_and_notes (line 357) | async def get_creators_and_notes(self) -> None:
    method _navigate_to_tieba_via_baidu (line 400) | async def _navigate_to_tieba_via_baidu(self):
    method _inject_anti_detection_scripts (line 489) | async def _inject_anti_detection_scripts(self):
    method create_tieba_client (line 545) | async def create_tieba_client(
    method launch_browser (line 592) | async def launch_browser(
    method launch_browser_with_cdp (line 636) | async def launch_browser_with_cdp(
    method close (line 669) | async def close(self):

FILE: media_platform/tieba/field.py
  class SearchSortType (line 24) | class SearchSortType(Enum):
  class SearchNoteType (line 34) | class SearchNoteType(Enum):

FILE: media_platform/tieba/help.py
  class TieBaExtractor (line 38) | class TieBaExtractor:
    method __init__ (line 39) | def __init__(self):
    method extract_search_note_list (line 43) | def extract_search_note_list(page_content: str) -> List[TiebaNote]:
    method extract_tieba_note_list (line 72) | def extract_tieba_note_list(self, page_content: str) -> List[TiebaNote]:
    method extract_note_detail (line 107) | def extract_note_detail(self, page_content: str) -> TiebaNote:
    method extract_tieba_note_parment_comments (line 144) | def extract_tieba_note_parment_comments(self, page_content: str, note_...
    method extract_tieba_note_sub_comments (line 181) | def extract_tieba_note_sub_comments(self, page_content: str, parent_co...
    method extract_creator_info (line 216) | def extract_creator_info(self, html_content: str) -> TiebaCreator:
    method extract_tieba_thread_id_list_from_creator_page (line 250) | def extract_tieba_thread_id_list_from_creator_page(
    method extract_ip_and_pub_time (line 272) | def extract_ip_and_pub_time(self, html_content: str) -> Tuple[str, str]:
    method extract_ip (line 287) | def extract_ip(html_content: str) -> str:
    method extract_gender (line 302) | def extract_gender(html_content: str) -> str:
    method extract_follow_and_fans (line 318) | def extract_follow_and_fans(selectors: List[Selector]) -> Tuple[str, s...
    method extract_registration_duration (line 335) | def extract_registration_duration(html_content: str) -> str:
    method extract_data_field_value (line 352) | def extract_data_field_value(selector: Selector) -> Dict:
  function test_extract_search_note_list (line 374) | def test_extract_search_note_list():
  function test_extract_note_detail (line 382) | def test_extract_note_detail():
  function test_extract_tieba_note_parment_comments (line 390) | def test_extract_tieba_note_parment_comments():
  function test_extract_tieba_note_sub_comments (line 398) | def test_extract_tieba_note_sub_comments():
  function test_extract_tieba_note_list (line 411) | def test_extract_tieba_note_list():
  function test_extract_creator_info (line 420) | def test_extract_creator_info():

FILE: media_platform/tieba/login.py
  class BaiduTieBaLogin (line 35) | class BaiduTieBaLogin(AbstractLogin):
    method __init__ (line 37) | def __init__(self,
    method check_login_state (line 51) | async def check_login_state(self) -> bool:
    method begin (line 66) | async def begin(self):
    method login_by_mobile (line 78) | async def login_by_mobile(self):
    method login_by_qrcode (line 82) | async def login_by_qrcode(self):
    method login_by_cookies (line 123) | async def login_by_cookies(self):

FILE: media_platform/weibo/client.py
  class WeiboClient (line 49) | class WeiboClient(ProxyRefreshMixin):
    method __init__ (line 51) | def __init__(
    method request (line 72) | async def request(self, method, url, **kwargs) -> Union[Response, Dict]:
    method get (line 103) | async def get(self, uri: str, params=None, headers=None, **kwargs) -> ...
    method post (line 113) | async def post(self, uri: str, data: dict) -> Dict:
    method pong (line 117) | async def pong(self) -> bool:
    method update_cookies (line 133) | async def update_cookies(self, browser_context: BrowserContext, urls: ...
    method get_note_by_keyword (line 152) | async def get_note_by_keyword(
    method get_note_comments (line 174) | async def get_note_comments(self, mid_id: str, max_id: int, max_id_typ...
    method get_note_all_comments (line 195) | async def get_note_all_comments(
    method get_comments_all_sub_comments (line 231) | async def get_comments_all_sub_comments(
    method get_note_info_by_id (line 258) | async def get_note_info_by_id(self, note_id: str) -> Dict:
    method get_note_image (line 280) | async def get_note_image(self, image_url: str) -> bytes:
    method get_creator_container_info (line 308) | async def get_creator_container_info(self, creator_id: str) -> Dict:
    method get_creator_info_by_id (line 326) | async def get_creator_info_by_id(self, creator_id: str) -> Dict:
    method get_notes_by_creator (line 346) | async def get_notes_by_creator(
    method get_all_notes_by_creator_id (line 372) | async def get_all_notes_by_creator_id(

FILE: media_platform/weibo/core.py
  class WeiboCrawler (line 54) | class WeiboCrawler(AbstractCrawler):
    method __init__ (line 60) | def __init__(self):
    method start (line 68) | async def start(self):
    method search (line 136) | async def search(self):
    method get_specified_notes (line 191) | async def get_specified_notes(self):
    method get_note_info_task (line 204) | async def get_note_info_task(self, note_id: str, semaphore: asyncio.Se...
    method batch_get_notes_comments (line 227) | async def batch_get_notes_comments(self, note_id_list: List[str]):
    method get_note_comments (line 245) | async def get_note_comments(self, note_id: str, semaphore: asyncio.Sem...
    method get_note_images (line 271) | async def get_note_images(self, mblog: Dict):
    method get_creators_and_notes (line 302) | async def get_creators_and_notes(self) -> None:
    method create_weibo_client (line 338) | async def create_weibo_client(self, httpx_proxy: Optional[str]) -> Wei...
    method launch_browser (line 357) | async def launch_browser(
    method launch_browser_with_cdp (line 386) | async def launch_browser_with_cdp(
    method get_note_full_text (line 417) | async def get_note_full_text(self, note_item: Dict) -> Dict:
    method batch_get_notes_full_text (line 457) | async def batch_get_notes_full_text(self, note_list: List[Dict]) -> Li...
    method close (line 472) | async def close(self):

FILE: media_platform/weibo/exception.py
  class DataFetchError (line 29) | class DataFetchError(RequestError):
  class IPBlockError (line 33) | class IPBlockError(RequestError):

FILE: media_platform/weibo/field.py
  class SearchType (line 28) | class SearchType(Enum):

FILE: media_platform/weibo/help.py
  function filter_search_result_card (line 29) | def filter_search_result_card(card_list: List[Dict]) -> List[Dict]:

FILE: media_platform/weibo/login.py
  class WeiboLogin (line 40) | class WeiboLogin(AbstractLogin):
    method __init__ (line 41) | def __init__(self,
    method begin (line 55) | async def begin(self):
    method check_login_state (line 70) | async def check_login_state(self, no_logged_in_session: str) -> bool:
    method login_by_qrcode (line 85) | async def login_by_qrcode(self):
    method login_by_mobile (line 121) | async def login_by_mobile(self):
    method login_by_cookies (line 124) | async def login_by_cookies(self):

FILE: media_platform/xhs/client.py
  class XiaoHongShuClient (line 45) | class XiaoHongShuClient(AbstractApiClient, ProxyRefreshMixin):
    method __init__ (line 47) | def __init__(
    method _pre_headers (line 73) | async def _pre_headers(self, url: str, params: Optional[Dict] = None, ...
    method request (line 115) | async def request(self, method, url, **kwargs) -> Union[str, Any]:
    method get (line 155) | async def get(self, uri: str, params: Optional[Dict] = None) -> Dict:
    method post (line 172) | async def post(self, uri: str, data: dict, **kwargs) -> Dict:
    method get_note_media (line 192) | async def get_note_media(self, url: str) -> Union[bytes, None]:
    method query_self (line 215) | async def query_self(self) -> Optional[Dict]:
    method pong (line 229) | async def pong(self) -> bool:
    method update_cookies (line 249) | async def update_cookies(self, browser_context: BrowserContext):
    method get_note_by_keyword (line 262) | async def get_note_by_keyword(
    method get_note_by_id (line 294) | async def get_note_by_id(
    method get_note_comments (line 331) | async def get_note_comments(
    method get_note_sub_comments (line 357) | async def get_note_sub_comments(
    method get_note_all_comments (line 389) | async def get_note_all_comments(
    method get_comments_all_sub_comments (line 438) | async def get_comments_all_sub_comments(
    method get_creator_info (line 521) | async def get_creator_info(
    method get_notes_by_creator (line 546) | async def get_notes_by_creator(
    method get_all_notes_by_creator (line 576) | async def get_all_notes_by_creator(
    method get_note_short_url (line 638) | async def get_note_short_url(self, note_id: str) -> Dict:
    method get_note_by_id_from_html (line 652) | async def get_note_by_id_from_html(

FILE: media_platform/xhs/core.py
  class XiaoHongShuCrawler (line 51) | class XiaoHongShuCrawler(AbstractCrawler):
    method __init__ (line 57) | def __init__(self) -> None:
    method start (line 64) | async def start(self) -> None:
    method search (line 125) | async def search(self) -> None:
    method get_creators_and_notes (line 184) | async def get_creators_and_notes(self) -> None:
    method fetch_creator_notes_detail (line 224) | async def fetch_creator_notes_detail(self, note_list: List[Dict]):
    method get_specified_notes (line 242) | async def get_specified_notes(self):
    method get_note_detail_async_task (line 270) | async def get_note_detail_async_task(
    method batch_get_note_comments (line 321) | async def batch_get_note_comments(self, note_list: List[str], xsec_tok...
    method get_comments (line 338) | async def get_comments(self, note_id: str, xsec_token: str, semaphore:...
    method create_xhs_client (line 356) | async def create_xhs_client(self, httpx_proxy: Optional[str]) -> XiaoH...
    method launch_browser (line 386) | async def launch_browser(
    method launch_browser_with_cdp (line 416) | async def launch_browser_with_cdp(
    method close (line 445) | async def close(self):
    method get_notice_media (line 455) | async def get_notice_media(self, note_detail: Dict):
    method get_note_images (line 462) | async def get_note_images(self, note_item: Dict):
    method get_notice_video (line 492) | async def get_notice_video(self, note_item: Dict):

FILE: media_platform/xhs/exception.py
  class DataFetchError (line 24) | class DataFetchError(RequestError):
  class IPBlockError (line 28) | class IPBlockError(RequestError):
  class NoteNotFoundError (line 32) | class NoteNotFoundError(RequestError):

FILE: media_platform/xhs/extractor.py
  class XiaoHongShuExtractor (line 27) | class XiaoHongShuExtractor:
    method __init__ (line 28) | def __init__(self):
    method extract_note_detail_from_html (line 31) | def extract_note_detail_from_html(self, note_id: str, html: str) -> Op...
    method extract_creator_info_from_html (line 52) | def extract_creator_info_from_html(self, html: str) -> Optional[Dict]:

FILE: media_platform/xhs/field.py
  class FeedType (line 25) | class FeedType(Enum):
  class NoteType (line 50) | class NoteType(Enum):
  class SearchSortType (line 55) | class SearchSortType(Enum):
  class SearchNoteType (line 65) | class SearchNoteType(Enum):
  class Note (line 75) | class Note(NamedTuple):

FILE: media_platform/xhs/help.py
  function sign (line 31) | def sign(a1="", b1="", x_s="", x_t=""):
  function get_b3_trace_id (line 62) | def get_b3_trace_id():
  function mrc (line 71) | def mrc(e):
  function tripletToBase64 (line 197) | def tripletToBase64(e):
  function encodeChunk (line 206) | def encodeChunk(e, t, r):
  function b64Encode (line 215) | def b64Encode(e):
  function encodeUtf8 (line 235) | def encodeUtf8(e):
  function base36encode (line 252) | def base36encode(number, alphabet='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'):
  function base36decode (line 274) | def base36decode(number):
  function get_search_id (line 278) | def get_search_id():
  function get_img_url_by_trace_id (line 291) | def get_img_url_by_trace_id(trace_id: str, format_type: str = "png"):
  function get_img_urls_by_trace_id (line 295) | def get_img_urls_by_trace_id(trace_id: str, format_type: str = "png"):
  function get_trace_id (line 299) | def get_trace_id(img_url: str):
  function parse_note_info_from_note_url (line 304) | def parse_note_info_from_note_url(url: str) -> NoteUrlInfo:
  function parse_creator_info_from_url (line 319) | def parse_creator_info_from_url(url: str) -> CreatorUrlInfo:

FILE: media_platform/xhs/login.py
  class XiaoHongShuLogin (line 36) | class XiaoHongShuLogin(AbstractLogin):
    method __init__ (line 38) | def __init__(self,
    method check_login_state (line 52) | async def check_login_state(self, no_logged_in_session: str) -> bool:
    method begin (line 87) | async def begin(self):
    method login_by_mobile (line 99) | async def login_by_mobile(self):
    method login_by_qrcode (line 167) | async def login_by_qrcode(self):
    method login_by_cookies (line 213) | async def login_by_cookies(self):

FILE: media_platform/xhs/playwright_sign.py
  function _build_sign_string (line 32) | def _build_sign_string(uri: str, data: Optional[Union[Dict, str]] = None...
  function _md5_hex (line 77) | def _md5_hex(s: str) -> str:
  function _build_xs_payload (line 82) | def _build_xs_payload(x3_value: str, data_type: str = "object") -> str:
  function _build_xs_common (line 94) | def _build_xs_common(a1: str, b1: str, x_s: str, x_t: str) -> str:
  function get_b1_from_localstorage (line 115) | async def get_b1_from_localstorage(page: Page) -> str:
  function call_mnsv2 (line 124) | async def call_mnsv2(page: Page, sign_str: str, md5_str: str) -> str:
  function sign_xs_with_playwright (line 146) | async def sign_xs_with_playwright(
  function sign_with_playwright (line 171) | async def sign_with_playwright(
  function pre_headers_with_playwright (line 203) | async def pre_headers_with_playwright(

FILE: media_platform/xhs/xhs_sign.py
  function _right_shift_unsigned (line 79) | def _right_shift_unsigned(num: int, bit: int = 0) -> int:
  function mrc (line 86) | def mrc(e: str) -> int:
  function _triplet_to_base64 (line 94) | def _triplet_to_base64(e: int) -> str:
  function _encode_chunk (line 104) | def _encode_chunk(data: list, start: int, end: int) -> str:
  function encode_utf8 (line 113) | def encode_utf8(s: str) -> list:
  function b64_encode (line 128) | def b64_encode(data: list) -> str:
  function get_trace_id (line 150) | def get_trace_id() -> str:

FILE: media_platform/zhihu/client.py
  class ZhiHuClient (line 47) | class ZhiHuClient(AbstractApiClient, ProxyRefreshMixin):
    method __init__ (line 49) | def __init__(
    method _pre_headers (line 67) | async def _pre_headers(self, url: str) -> Dict:
    method request (line 85) | async def request(self, method, url, **kwargs) -> Union[str, Any]:
    method get (line 126) | async def get(self, uri: str, params=None, **kwargs) -> Union[Response...
    method pong (line 143) | async def pong(self) -> bool:
    method update_cookies (line 163) | async def update_cookies(self, browser_context: BrowserContext):
    method get_current_user_info (line 176) | async def get_current_user_info(self) -> Dict:
    method get_note_by_keyword (line 185) | async def get_note_by_keyword(
    method get_root_comments (line 227) | async def get_root_comments(
    method get_child_comments (line 258) | async def get_child_comments(
    method get_note_all_comments (line 284) | async def get_note_all_comments(
    method get_comments_all_sub_comments (line 329) | async def get_comments_all_sub_comments(
    method get_creator_info (line 382) | async def get_creator_info(self, url_token: str) -> Optional[ZhihuCrea...
    method get_creator_answers (line 395) | async def get_creator_answers(self, url_token: str, offset: int = 0, l...
    method get_creator_articles (line 417) | async def get_creator_articles(self, url_token: str, offset: int = 0, ...
    method get_creator_videos (line 438) | async def get_creator_videos(self, url_token: str, offset: int = 0, li...
    method get_all_anwser_by_creator (line 458) | async def get_all_anwser_by_creator(self, creator: ZhihuCreator, crawl...
    method get_all_articles_by_creator (line 488) | async def get_all_articles_by_creator(
    method get_all_videos_by_creator (line 522) | async def get_all_videos_by_creator(
    method get_answer_info (line 556) | async def get_answer_info(
    method get_article_info (line 574) | async def get_article_info(self, article_id: str) -> Optional[ZhihuCon...
    method get_video_info (line 587) | async def get_video_info(self, video_id: str) -> Optional[ZhihuContent]:

FILE: media_platform/zhihu/core.py
  class ZhihuCrawler (line 52) | class ZhihuCrawler(AbstractCrawler):
    method __init__ (line 58) | def __init__(self) -> None:
    method start (line 66) | async def start(self) -> None:
    method search (line 145) | async def search(self) -> None:
    method batch_get_content_comments (line 196) | async def batch_get_content_comments(self, content_list: List[ZhihuCon...
    method get_comments (line 220) | async def get_comments(
    method get_creators_and_notes (line 247) | async def get_creators_and_notes(self) -> None:
    method get_note_detail (line 302) | async def get_note_detail(
    method get_specified_notes (line 360) | async def get_specified_notes(self):
    method create_zhihu_client (line 391) | async def create_zhihu_client(self, httpx_proxy: Optional[str]) -> Zhi...
    method launch_browser (line 419) | async def launch_browser(
    method launch_browser_with_cdp (line 453) | async def launch_browser_with_cdp(
    method close (line 486) | async def close(self):

FILE: media_platform/zhihu/exception.py
  class DataFetchError (line 24) | class DataFetchError(RequestError):
  class IPBlockError (line 28) | class IPBlockError(RequestError):
  class ForbiddenError (line 31) | class ForbiddenError(RequestError):

FILE: media_platform/zhihu/field.py
  class SearchTime (line 27) | class SearchTime(Enum):
  class SearchType (line 40) | class SearchType(Enum):
  class SearchSort (line 50) | class SearchSort(Enum):

FILE: media_platform/zhihu/help.py
  function sign (line 37) | def sign(url: str, cookies: str) -> Dict:
  class ZhihuExtractor (line 55) | class ZhihuExtractor:
    method __init__ (line 56) | def __init__(self):
    method extract_contents_from_search (line 59) | def extract_contents_from_search(self, json_data: Dict) -> List[ZhihuC...
    method _extract_content_list (line 76) | def _extract_content_list(self, content_list: List[Dict]) -> List[Zhih...
    method _extract_answer_content (line 100) | def _extract_answer_content(self, answer: Dict) -> ZhihuContent:
    method _extract_article_content (line 130) | def _extract_article_content(self, article: Dict) -> ZhihuContent:
    method _extract_zvideo_content (line 160) | def _extract_zvideo_content(self, zvideo: Dict) -> ZhihuContent:
    method _extract_content_or_comment_author (line 195) | def _extract_content_or_comment_author(author: Dict) -> ZhihuCreator:
    method extract_comments (line 222) | def extract_comments(self, page_content: ZhihuContent, comments: List[...
    method _extract_comment (line 241) | def _extract_comment(self, page_content: ZhihuContent, comment: Dict) ...
    method _extract_comment_ip_location (line 272) | def _extract_comment_ip_location(comment_tags: List[Dict]) -> str:
    method extract_offset (line 291) | def extract_offset(paging_info: Dict) -> str:
    method _foramt_gender_text (line 311) | def _foramt_gender_text(gender: int) -> str:
    method extract_creator (line 328) | def extract_creator(self, user_url_token: str, html_content: str) -> O...
    method extract_content_list_from_creator (line 373) | def extract_content_list_from_creator(self, anwser_list: List[Dict]) -...
    method extract_answer_content_from_html (line 390) | def extract_answer_content_from_html(self, html_content: str) -> Optio...
    method extract_article_content_from_html (line 409) | def extract_article_content_from_html(self, html_content: str) -> Opti...
    method extract_zvideo_content_from_html (line 428) | def extract_zvideo_content_from_html(self, html_content: str) -> Optio...
  function judge_zhihu_url (line 457) | def judge_zhihu_url(note_detail_url: str) -> str:

FILE: media_platform/zhihu/login.py
  class ZhiHuLogin (line 36) | class ZhiHuLogin(AbstractLogin):
    method __init__ (line 38) | def __init__(self,
    method check_login_state (line 52) | async def check_login_state(self) -> bool:
    method begin (line 65) | async def begin(self):
    method login_by_mobile (line 77) | async def login_by_mobile(self):
    method login_by_qrcode (line 81) | async def login_by_qrcode(self):
    method login_by_cookies (line 115) | async def login_by_cookies(self):

FILE: model/m_baidu_tieba.py
  class TiebaNote (line 27) | class TiebaNote(BaseModel):
  class TiebaComment (line 47) | class TiebaComment(BaseModel):
  class TiebaCreator (line 68) | class TiebaCreator(BaseModel):

FILE: model/m_bilibili.py
  class VideoUrlInfo (line 35) | class VideoUrlInfo(BaseModel):
  class CreatorUrlInfo (line 41) | class CreatorUrlInfo(BaseModel):

FILE: model/m_douyin.py
  class VideoUrlInfo (line 26) | class VideoUrlInfo(BaseModel):
  class CreatorUrlInfo (line 32) | class CreatorUrlInfo(BaseModel):

FILE: model/m_kuaishou.py
  class VideoUrlInfo (line 26) | class VideoUrlInfo(BaseModel):
  class CreatorUrlInfo (line 32) | class CreatorUrlInfo(BaseModel):

FILE: model/m_xiaohongshu.py
  class NoteUrlInfo (line 27) | class NoteUrlInfo(BaseModel):
  class CreatorUrlInfo (line 33) | class CreatorUrlInfo(BaseModel):

FILE: model/m_zhihu.py
  class ZhihuContent (line 27) | class ZhihuContent(BaseModel):
  class ZhihuComment (line 51) | class ZhihuComment(BaseModel):
  class ZhihuCreator (line 73) | class ZhihuCreator(BaseModel):

FILE: proxy/base_proxy.py
  class IpGetError (line 38) | class IpGetError(Exception):
  class ProxyProvider (line 42) | class ProxyProvider(ABC):
    method get_proxy (line 44) | async def get_proxy(self, num: int) -> List[IpInfoModel]:
  class IpCache (line 54) | class IpCache:
    method __init__ (line 55) | def __init__(self):
    method set_ip (line 58) | def set_ip(self, ip_key: str, ip_value_info: str, ex: int):
    method load_all_ip (line 68) | def load_all_ip(self, proxy_brand_name: str) -> List[IpInfoModel]:

FILE: proxy/providers/jishu_http_proxy.py
  class JiSuHttpProxy (line 36) | class JiSuHttpProxy(ProxyProvider):
    method __init__ (line 38) | def __init__(self, key: str, crypto: str, time_validity_period: int):
    method get_proxy (line 57) | async def get_proxy(self, num: int) -> List[IpInfoModel]:
  function new_jisu_http_proxy (line 99) | def new_jisu_http_proxy() -> JiSuHttpProxy:

FILE: proxy/providers/kuaidl_proxy.py
  class KuaidailiProxyModel (line 41) | class KuaidailiProxyModel(BaseModel):
  function parse_kuaidaili_proxy (line 47) | def parse_kuaidaili_proxy(proxy_info: str) -> KuaidailiProxyModel:
  class KuaiDaiLiProxy (line 72) | class KuaiDaiLiProxy(ProxyProvider):
    method __init__ (line 73) | def __init__(self, kdl_user_name: str, kdl_user_pwd: str, kdl_secret_i...
    method get_proxy (line 96) | async def get_proxy(self, num: int) -> List[IpInfoModel]:
  function new_kuai_daili_proxy (line 150) | def new_kuai_daili_proxy() -> KuaiDaiLiProxy:

FILE: proxy/providers/wandou_http_proxy.py
  class WanDouHttpProxy (line 36) | class WanDouHttpProxy(ProxyProvider):
    method __init__ (line 38) | def __init__(self, app_key: str, num: int = 100):
    method get_proxy (line 52) | async def get_proxy(self, num: int) -> List[IpInfoModel]:
  function new_wandou_http_proxy (line 110) | def new_wandou_http_proxy() -> WanDouHttpProxy:

FILE: proxy/proxy_ip_pool.py
  class ProxyIpPool (line 42) | class ProxyIpPool:
    method __init__ (line 44) | def __init__(
    method load_proxies (line 61) | async def load_proxies(self) -> None:
    method _is_valid_proxy (line 69) | async def _is_valid_proxy(self, proxy: IpInfoModel) -> bool:
    method get_proxy (line 98) | async def get_proxy(self) -> IpInfoModel:
    method is_current_proxy_expired (line 116) | def is_current_proxy_expired(self, buffer_seconds: int = 30) -> bool:
    method get_or_refresh_proxy (line 128) | async def get_or_refresh_proxy(self, buffer_seconds: int = 30) -> IpIn...
    method _reload_proxies (line 144) | async def _reload_proxies(self):
  function create_ip_pool (line 159) | async def create_ip_pool(ip_pool_count: int, enable_validate_ip: bool) -...

FILE: proxy/proxy_mixin.py
  class ProxyRefreshMixin (line 34) | class ProxyRefreshMixin:
    method init_proxy_pool (line 49) | def init_proxy_pool(self, proxy_ip_pool: Optional["ProxyIpPool"]) -> N...
    method _refresh_proxy_if_expired (line 57) | async def _refresh_proxy_if_expired(self) -> None:

FILE: proxy/types.py
  class ProviderNameEnum (line 32) | class ProviderNameEnum(Enum):
  class IpInfoModel (line 37) | class IpInfoModel(BaseModel):
    method is_expired (line 47) | def is_expired(self, buffer_seconds: int = 30) -> bool:

FILE: recv_sms.py
  class SmsNotification (line 38) | class SmsNotification(BaseModel):
  function extract_verification_code (line 46) | def extract_verification_code(message: str) -> str:
  function receive_sms_notification (line 56) | def receive_sms_notification(sms: SmsNotification):
  function not_found (line 83) | async def not_found():

FILE: store/bilibili/__init__.py
  class BiliStoreFactory (line 34) | class BiliStoreFactory:
    method create_store (line 47) | def create_store() -> AbstractStore:
  function update_bilibili_video (line 54) | async def update_bilibili_video(video_item: Dict):
  function update_up_info (line 85) | async def update_up_info(video_item: Dict):
  function batch_update_bilibili_video_comments (line 104) | async def batch_update_bilibili_video_comments(video_id: str, comments: ...
  function update_bilibili_video_comment (line 111) | async def update_bilibili_video_comment(video_id: str, comment_item: Dict):
  function store_video (line 136) | async def store_video(aid, video_content, extension_file_name):
  function batch_update_bilibili_creator_fans (line 151) | async def batch_update_bilibili_creator_fans(creator_info: Dict, fans_li...
  function batch_update_bilibili_creator_followings (line 164) | async def batch_update_bilibili_creator_followings(creator_info: Dict, f...
  function batch_update_bilibili_creator_dynamics (line 177) | async def batch_update_bilibili_creator_dynamics(creator_info: Dict, dyn...
  function update_bilibili_creator_contact (line 203) | async def update_bilibili_creator_contact(creator_info: Dict, fan_info: ...
  function update_bilibili_creator_dynamic (line 219) | async def update_bilibili_creator_dynamic(creator_info: Dict, dynamic_in...

FILE: store/bilibili/_store_impl.py
  class BiliCsvStoreImplement (line 46) | class BiliCsvStoreImplement(AbstractStore):
    method __init__ (line 47) | def __init__(self):
    method store_content (line 53) | async def store_content(self, content_item: Dict):
    method store_comment (line 67) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 81) | async def store_creator(self, creator: Dict):
    method store_contact (line 95) | async def store_contact(self, contact_item: Dict):
    method store_dynamic (line 109) | async def store_dynamic(self, dynamic_item: Dict):
  class BiliDbStoreImplement (line 124) | class BiliDbStoreImplement(AbstractStore):
    method store_content (line 125) | async def store_content(self, content_item: Dict):
    method store_comment (line 152) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 181) | async def store_creator(self, creator: Dict):
    method store_contact (line 209) | async def store_contact(self, contact_item: Dict):
    method store_dynamic (line 237) | async def store_dynamic(self, dynamic_item):
  class BiliJsonStoreImplement (line 262) | class BiliJsonStoreImplement(AbstractStore):
    method __init__ (line 263) | def __init__(self):
    method store_content (line 269) | async def store_content(self, content_item: Dict):
    method store_comment (line 283) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 297) | async def store_creator(self, creator: Dict):
    method store_contact (line 311) | async def store_contact(self, contact_item: Dict):
    method store_dynamic (line 325) | async def store_dynamic(self, dynamic_item: Dict):
  class BiliJsonlStoreImplement (line 341) | class BiliJsonlStoreImplement(AbstractStore):
    method __init__ (line 342) | def __init__(self):
    method store_content (line 348) | async def store_content(self, content_item: Dict):
    method store_comment (line 354) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 360) | async def store_creator(self, creator: Dict):
    method store_contact (line 366) | async def store_contact(self, contact_item: Dict):
    method store_dynamic (line 372) | async def store_dynamic(self, dynamic_item: Dict):
  class BiliSqliteStoreImplement (line 379) | class BiliSqliteStoreImplement(BiliDbStoreImplement):
  class BiliMongoStoreImplement (line 383) | class BiliMongoStoreImplement(AbstractStore):
    method __init__ (line 386) | def __init__(self):
    method store_content (line 389) | async def store_content(self, content_item: Dict):
    method store_comment (line 406) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 423) | async def store_creator(self, creator_item: Dict):
  class BiliExcelStoreImplement (line 441) | class BiliExcelStoreImplement:
    method __new__ (line 444) | def __new__(cls, *args, **kwargs):

FILE: store/bilibili/bilibilli_store_media.py
  class BilibiliVideo (line 34) | class BilibiliVideo(AbstractStoreVideo):
    method __init__ (line 35) | def __init__(self):
    method store_video (line 41) | async def store_video(self, video_content_item: Dict):
    method make_save_file_name (line 53) | def make_save_file_name(self, aid: str, extension_file_name: str) -> str:
    method save_video (line 66) | async def save_video(self, aid: int, video_content: str, extension_fil...

FILE: store/douyin/__init__.py
  class DouyinStoreFactory (line 33) | class DouyinStoreFactory:
    method create_store (line 46) | def create_store() -> AbstractStore:
  function _extract_note_image_list (line 53) | def _extract_note_image_list(aweme_detail: Dict) -> List[str]:
  function _extract_comment_image_list (line 77) | def _extract_comment_image_list(comment_item: Dict) -> List[str]:
  function _extract_content_cover_url (line 101) | def _extract_content_cover_url(aweme_detail: Dict) -> str:
  function _extract_video_download_url (line 121) | def _extract_video_download_url(aweme_detail: Dict) -> str:
  function _extract_music_download_url (line 141) | def _extract_music_download_url(aweme_detail: Dict) -> str:
  function update_douyin_aweme (line 157) | async def update_douyin_aweme(aweme_item: Dict):
  function batch_update_dy_aweme_comments (line 191) | async def batch_update_dy_aweme_comments(aweme_id: str, comments: List[D...
  function update_dy_aweme_comment (line 198) | async def update_dy_aweme_comment(aweme_id: str, comment_item: Dict):
  function save_creator (line 231) | async def save_creator(user_id: str, creator: Dict):
  function update_dy_aweme_image (line 252) | async def update_dy_aweme_image(aweme_id, pic_content, extension_file_na...
  function update_dy_aweme_video (line 267) | async def update_dy_aweme_video(aweme_id, video_content, extension_file_...

FILE: store/douyin/_store_impl.py
  class DouyinCsvStoreImplement (line 43) | class DouyinCsvStoreImplement(AbstractStore):
    method __init__ (line 44) | def __init__(self):
    method store_content (line 50) | async def store_content(self, content_item: Dict):
    method store_comment (line 64) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 78) | async def store_creator(self, creator: Dict):
  class DouyinDbStoreImplement (line 93) | class DouyinDbStoreImplement(AbstractStore):
    method store_content (line 94) | async def store_content(self, content_item: Dict):
    method store_comment (line 115) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 135) | async def store_creator(self, creator: Dict):
  class DouyinJsonStoreImplement (line 156) | class DouyinJsonStoreImplement(AbstractStore):
    method __init__ (line 157) | def __init__(self):
    method store_content (line 163) | async def store_content(self, content_item: Dict):
    method store_comment (line 177) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 191) | async def store_creator(self, creator: Dict):
  class DouyinJsonlStoreImplement (line 207) | class DouyinJsonlStoreImplement(AbstractStore):
    method __init__ (line 208) | def __init__(self):
    method store_content (line 214) | async def store_content(self, content_item: Dict):
    method store_comment (line 220) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 226) | async def store_creator(self, creator: Dict):
  class DouyinSqliteStoreImplement (line 233) | class DouyinSqliteStoreImplement(DouyinDbStoreImplement):
  class DouyinMongoStoreImplement (line 237) | class DouyinMongoStoreImplement(AbstractStore):
    method __init__ (line 240) | def __init__(self):
    method store_content (line 243) | async def store_content(self, content_item: Dict):
    method store_comment (line 260) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 277) | async def store_creator(self, creator_item: Dict):
  class DouyinExcelStoreImplement (line 295) | class DouyinExcelStoreImplement:
    method __new__ (line 298) | def __new__(cls, *args, **kwargs):

FILE: store/douyin/douyin_store_media.py
  class DouYinImage (line 30) | class DouYinImage(AbstractStoreImage):
    method __init__ (line 31) | def __init__(self):
    method store_image (line 37) | async def store_image(self, image_content_item: Dict):
    method make_save_file_name (line 49) | def make_save_file_name(self, aweme_id: str, extension_file_name: str)...
    method save_image (line 62) | async def save_image(self, aweme_id: str, pic_content: str, extension_...
  class DouYinVideo (line 81) | class DouYinVideo(AbstractStoreVideo):
    method __init__ (line 82) | def __init__(self):
    method store_video (line 88) | async def store_video(self, video_content_item: Dict):
    method make_save_file_name (line 100) | def make_save_file_name(self, aweme_id: str, extension_file_name: str)...
    method save_video (line 113) | async def save_video(self, aweme_id: str, video_content: str, extensio...

FILE: store/excel_store_base.py
  class ExcelStoreBase (line 52) | class ExcelStoreBase(AbstractStore):
    method get_instance (line 64) | def get_instance(cls, platform: str, crawler_type: str) -> "ExcelStore...
    method flush_all (line 82) | def flush_all(cls):
    method __init__ (line 96) | def __init__(self, platform: str, crawler_type: str = "search"):
    method _apply_header_style (line 147) | def _apply_header_style(self, sheet, row_num: int = 1):
    method _auto_adjust_column_width (line 171) | def _auto_adjust_column_width(self, sheet):
    method _write_headers (line 193) | def _write_headers(self, sheet, headers: List[str]):
    method _write_row (line 206) | def _write_row(self, sheet, data: Dict[str, Any], headers: List[str]):
    method store_content (line 237) | async def store_content(self, content_item: Dict):
    method store_comment (line 259) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 279) | async def store_creator(self, creator: Dict):
    method store_contact (line 299) | async def store_contact(self, contact_item: Dict):
    method store_dynamic (line 323) | async def store_dynamic(self, dynamic_item: Dict):
    method flush (line 347) | def flush(self):

FILE: store/kuaishou/__init__.py
  class KuaishouStoreFactory (line 33) | class KuaishouStoreFactory:
    method create_store (line 46) | def create_store() -> AbstractStore:
  function update_kuaishou_video (line 54) | async def update_kuaishou_video(video_item: Dict):
  function batch_update_ks_video_comments (line 82) | async def batch_update_ks_video_comments(video_id: str, comments: List[D...
  function update_ks_video_comment (line 90) | async def update_ks_video_comment(video_id: str, comment_item: Dict):
  function save_creator (line 113) | async def save_creator(user_id: str, creator: Dict):

FILE: store/kuaishou/_store_impl.py
  function calculate_number_of_files (line 45) | def calculate_number_of_files(file_store_path: str) -> int:
  class KuaishouCsvStoreImplement (line 60) | class KuaishouCsvStoreImplement(AbstractStore):
    method __init__ (line 61) | def __init__(self, **kwargs):
    method store_content (line 65) | async def store_content(self, content_item: Dict):
    method store_comment (line 76) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 87) | async def store_creator(self, creator: Dict):
  class KuaishouDbStoreImplement (line 91) | class KuaishouDbStoreImplement(AbstractStore):
    method store_creator (line 92) | async def store_creator(self, creator: Dict):
    method store_content (line 95) | async def store_content(self, content_item: Dict):
    method store_comment (line 116) | async def store_comment(self, comment_item: Dict):
  class KuaishouJsonStoreImplement (line 139) | class KuaishouJsonStoreImplement(AbstractStore):
    method __init__ (line 140) | def __init__(self, **kwargs):
    method store_content (line 144) | async def store_content(self, content_item: Dict):
    method store_comment (line 155) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 166) | async def store_creator(self, creator: Dict):
  class KuaishouJsonlStoreImplement (line 170) | class KuaishouJsonlStoreImplement(AbstractStore):
    method __init__ (line 171) | def __init__(self, **kwargs):
    method store_content (line 175) | async def store_content(self, content_item: Dict):
    method store_comment (line 178) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 181) | async def store_creator(self, creator: Dict):
  class KuaishouSqliteStoreImplement (line 185) | class KuaishouSqliteStoreImplement(KuaishouDbStoreImplement):
    method store_creator (line 186) | async def store_creator(self, creator: Dict):
  class KuaishouMongoStoreImplement (line 190) | class KuaishouMongoStoreImplement(AbstractStore):
    method __init__ (line 193) | def __init__(self):
    method store_content (line 196) | async def store_content(self, content_item: Dict):
    method store_comment (line 213) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 230) | async def store_creator(self, creator_item: Dict):
  class KuaishouExcelStoreImplement (line 248) | class KuaishouExcelStoreImplement:
    method __new__ (line 251) | def __new__(cls, *args, **kwargs):

FILE: store/tieba/__init__.py
  class TieBaStoreFactory (line 30) | class TieBaStoreFactory:
    method create_store (line 43) | def create_store() -> AbstractStore:
  function batch_update_tieba_notes (line 51) | async def batch_update_tieba_notes(note_list: List[TiebaNote]):
  function update_tieba_note (line 66) | async def update_tieba_note(note_item: TiebaNote):
  function batch_update_tieba_note_comments (line 83) | async def batch_update_tieba_note_comments(note_id: str, comments: List[...
  function update_tieba_note_comment (line 99) | async def update_tieba_note_comment(note_id: str, comment_item: TiebaCom...
  function save_creator (line 115) | async def save_creator(user_info: TiebaCreator):

FILE: store/tieba/_store_impl.py
  function calculate_number_of_files (line 46) | def calculate_number_of_files(file_store_path: str) -> int:
  class TieBaCsvStoreImplement (line 61) | class TieBaCsvStoreImplement(AbstractStore):
    method __init__ (line 62) | def __init__(self, **kwargs):
    method store_content (line 66) | async def store_content(self, content_item: Dict):
    method store_comment (line 77) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 88) | async def store_creator(self, creator: Dict):
  class TieBaDbStoreImplement (line 100) | class TieBaDbStoreImplement(AbstractStore):
    method store_content (line 101) | async def store_content(self, content_item: Dict):
    method store_comment (line 120) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 139) | async def store_creator(self, creator: Dict):
  class TieBaJsonStoreImplement (line 159) | class TieBaJsonStoreImplement(AbstractStore):
    method __init__ (line 160) | def __init__(self, **kwargs):
    method store_content (line 164) | async def store_content(self, content_item: Dict):
    method store_comment (line 175) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 186) | async def store_creator(self, creator: Dict):
  class TieBaJsonlStoreImplement (line 198) | class TieBaJsonlStoreImplement(AbstractStore):
    method __init__ (line 199) | def __init__(self, **kwargs):
    method store_content (line 203) | async def store_content(self, content_item: Dict):
    method store_comment (line 206) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 209) | async def store_creator(self, creator: Dict):
  class TieBaSqliteStoreImplement (line 213) | class TieBaSqliteStoreImplement(TieBaDbStoreImplement):
  class TieBaMongoStoreImplement (line 220) | class TieBaMongoStoreImplement(AbstractStore):
    method __init__ (line 223) | def __init__(self):
    method store_content (line 226) | async def store_content(self, content_item: Dict):
    method store_comment (line 243) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 260) | async def store_creator(self, creator_item: Dict):
  class TieBaExcelStoreImplement (line 278) | class TieBaExcelStoreImplement:
    method __new__ (line 281) | def __new__(cls, *args, **kwargs):

FILE: store/weibo/__init__.py
  class WeibostoreFactory (line 34) | class WeibostoreFactory:
    method create_store (line 47) | def create_store() -> AbstractStore:
  function batch_update_weibo_notes (line 54) | async def batch_update_weibo_notes(note_list: List[Dict]):
  function update_weibo_note (line 69) | async def update_weibo_note(note_item: Dict):
  function batch_update_weibo_note_comments (line 111) | async def batch_update_weibo_note_comments(note_id: str, comments: List[...
  function update_weibo_note_comment (line 127) | async def update_weibo_note_comment(note_id: str, comment_item: Dict):
  function update_weibo_note_image (line 166) | async def update_weibo_note_image(picid: str, pic_content, extension_fil...
  function save_creator (line 180) | async def save_creator(user_id: str, user_info: Dict):

FILE: store/weibo/_store_impl.py
  function calculate_number_of_files (line 46) | def calculate_number_of_files(file_store_path: str) -> int:
  class WeiboCsvStoreImplement (line 61) | class WeiboCsvStoreImplement(AbstractStore):
    method __init__ (line 62) | def __init__(self, **kwargs):
    method store_content (line 66) | async def store_content(self, content_item: Dict):
    method store_comment (line 77) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 88) | async def store_creator(self, creator: Dict):
  class WeiboDbStoreImplement (line 100) | class WeiboDbStoreImplement(AbstractStore):
    method store_content (line 102) | async def store_content(self, content_item: Dict):
    method store_comment (line 129) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 162) | async def store_creator(self, creator: Dict):
  class WeiboJsonStoreImplement (line 190) | class WeiboJsonStoreImplement(AbstractStore):
    method __init__ (line 191) | def __init__(self, **kwargs):
    method store_content (line 195) | async def store_content(self, content_item: Dict):
    method store_comment (line 206) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 217) | async def store_creator(self, creator: Dict):
  class WeiboJsonlStoreImplement (line 229) | class WeiboJsonlStoreImplement(AbstractStore):
    method __init__ (line 230) | def __init__(self, **kwargs):
    method store_content (line 234) | async def store_content(self, content_item: Dict):
    method store_comment (line 237) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 240) | async def store_creator(self, creator: Dict):
  class WeiboSqliteStoreImplement (line 244) | class WeiboSqliteStoreImplement(WeiboDbStoreImplement):
  class WeiboMongoStoreImplement (line 251) | class WeiboMongoStoreImplement(AbstractStore):
    method __init__ (line 254) | def __init__(self):
    method store_content (line 257) | async def store_content(self, content_item: Dict):
    method store_comment (line 274) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 291) | async def store_creator(self, creator_item: Dict):
  class WeiboExcelStoreImplement (line 309) | class WeiboExcelStoreImplement:
    method __new__ (line 312) | def __new__(cls, *args, **kwargs):

FILE: store/weibo/weibo_store_media.py
  class WeiboStoreImage (line 34) | class WeiboStoreImage(AbstractStoreImage):
    method __init__ (line 35) | def __init__(self):
    method store_image (line 41) | async def store_image(self, image_content_item: Dict):
    method make_save_file_name (line 53) | def make_save_file_name(self, picid: str, extension_file_name: str) ->...
    method save_image (line 66) | async def save_image(self, picid: str, pic_content: str, extension_fil...

FILE: store/xhs/__init__.py
  class XhsStoreFactory (line 33) | class XhsStoreFactory:
    method create_store (line 46) | def create_store() -> AbstractStore:
  function get_video_url_arr (line 53) | def get_video_url_arr(note_item: Dict) -> List:
  function update_xhs_note (line 87) | async def update_xhs_note(note_item: Dict):
  function batch_update_xhs_note_comments (line 135) | async def batch_update_xhs_note_comments(note_id: str, comments: List[Di...
  function update_xhs_note_comment (line 151) | async def update_xhs_note_comment(note_id: str, comment_item: Dict):
  function save_creator (line 184) | async def save_creator(user_id: str, creator: Dict):
  function update_xhs_note_image (line 233) | async def update_xhs_note_image(note_id, pic_content, extension_file_name):
  function update_xhs_note_video (line 248) | async def update_xhs_note_video(note_id, video_content, extension_file_n...

FILE: store/xhs/_store_impl.py
  class XhsCsvStoreImplement (line 42) | class XhsCsvStoreImplement(AbstractStore):
    method __init__ (line 43) | def __init__(self, **kwargs):
    method store_content (line 47) | async def store_content(self, content_item: Dict):
    method store_comment (line 55) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 64) | async def store_creator(self, creator_item: Dict):
    method flush (line 67) | def flush(self):
  class XhsJsonStoreImplement (line 71) | class XhsJsonStoreImplement(AbstractStore):
    method __init__ (line 72) | def __init__(self, **kwargs):
    method store_content (line 76) | async def store_content(self, content_item: Dict):
    method store_comment (line 84) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 92) | async def store_creator(self, creator_item: Dict):
    method flush (line 95) | def flush(self):
  class XhsJsonlStoreImplement (line 104) | class XhsJsonlStoreImplement(AbstractStore):
    method __init__ (line 105) | def __init__(self, **kwargs):
    method store_content (line 109) | async def store_content(self, content_item: Dict):
    method store_comment (line 112) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 115) | async def store_creator(self, creator_item: Dict):
    method flush (line 118) | def flush(self):
  class XhsDbStoreImplement (line 122) | class XhsDbStoreImplement(AbstractStore):
    method __init__ (line 123) | def __init__(self, **kwargs):
    method store_content (line 126) | async def store_content(self, content_item: Dict):
    method add_content (line 136) | async def add_content(self, session: AsyncSession, content_item: Dict):
    method update_content (line 165) | async def update_content(self, session: AsyncSession, content_item: Di...
    method content_is_exist (line 179) | async def content_is_exist(self, session: AsyncSession, note_id: str) ...
    method store_comment (line 184) | async def store_comment(self, comment_item: Dict):
    method add_comment (line 196) | async def add_comment(self, session: AsyncSession, comment_item: Dict):
    method update_comment (line 217) | async def update_comment(self, session: AsyncSession, comment_item: Di...
    method comment_is_exist (line 228) | async def comment_is_exist(self, session: AsyncSession, comment_id: st...
    method store_creator (line 233) | async def store_creator(self, creator_item: Dict):
    method add_creator (line 243) | async def add_creator(self, session: AsyncSession, creator_item: Dict):
    method update_creator (line 262) | async def update_creator(self, session: AsyncSession, creator_item: Di...
    method creator_is_exist (line 278) | async def creator_is_exist(self, session: AsyncSession, user_id: str) ...
    method get_all_content (line 283) | async def get_all_content(self) -> List[Dict]:
    method get_all_comments (line 289) | async def get_all_comments(self) -> List[Dict]:
  class XhsSqliteStoreImplement (line 296) | class XhsSqliteStoreImplement(XhsDbStoreImplement):
    method __init__ (line 297) | def __init__(self, **kwargs):
  class XhsMongoStoreImplement (line 301) | class XhsMongoStoreImplement(AbstractStore):
    method __init__ (line 304) | def __init__(self, **kwargs):
    method store_content (line 308) | async def store_content(self, content_item: Dict):
    method store_comment (line 325) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 342) | async def store_creator(self, creator_item: Dict):
  class XhsExcelStoreImplement (line 360) | class XhsExcelStoreImplement:
    method __new__ (line 363) | def __new__(cls, *args, **kwargs):

FILE: store/xhs/xhs_store_media.py
  class XiaoHongShuImage (line 34) | class XiaoHongShuImage(AbstractStoreImage):
    method __init__ (line 35) | def __init__(self):
    method store_image (line 41) | async def store_image(self, image_content_item: Dict):
    method make_save_file_name (line 53) | def make_save_file_name(self, notice_id: str, extension_file_name: str...
    method save_image (line 66) | async def save_image(self, notice_id: str, pic_content: str, extension...
  class XiaoHongShuVideo (line 85) | class XiaoHongShuVideo(AbstractStoreVideo):
    method __init__ (line 86) | def __init__(self):
    method store_video (line 92) | async def store_video(self, video_content_item: Dict):
    method make_save_file_name (line 104) | def make_save_file_name(self, notice_id: str, extension_file_name: str...
    method save_video (line 117) | async def save_video(self, notice_id: str, video_content: str, extensi...

FILE: store/zhihu/__init__.py
  class ZhihuStoreFactory (line 38) | class ZhihuStoreFactory:
    method create_store (line 51) | def create_store() -> AbstractStore:
  function batch_update_zhihu_contents (line 57) | async def batch_update_zhihu_contents(contents: List[ZhihuContent]):
  function update_zhihu_content (line 72) | async def update_zhihu_content(content_item: ZhihuContent):
  function batch_update_zhihu_note_comments (line 89) | async def batch_update_zhihu_note_comments(comments: List[ZhihuComment]):
  function update_zhihu_content_comment (line 105) | async def update_zhihu_content_comment(comment_item: ZhihuComment):
  function save_creator (line 120) | async def save_creator(creator: ZhihuCreator):

FILE: store/zhihu/_store_impl.py
  function calculate_number_of_files (line 45) | def calculate_number_of_files(file_store_path: str) -> int:
  class ZhihuCsvStoreImplement (line 60) | class ZhihuCsvStoreImplement(AbstractStore):
    method __init__ (line 61) | def __init__(self, **kwargs):
    method store_content (line 65) | async def store_content(self, content_item: Dict):
    method store_comment (line 76) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 87) | async def store_creator(self, creator: Dict):
  class ZhihuDbStoreImplement (line 99) | class ZhihuDbStoreImplement(AbstractStore):
    method store_content (line 100) | async def store_content(self, content_item: Dict):
    method store_comment (line 122) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 144) | async def store_creator(self, creator: Dict):
  class ZhihuJsonStoreImplement (line 167) | class ZhihuJsonStoreImplement(AbstractStore):
    method __init__ (line 168) | def __init__(self, **kwargs):
    method store_content (line 172) | async def store_content(self, content_item: Dict):
    method store_comment (line 183) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 194) | async def store_creator(self, creator: Dict):
  class ZhihuJsonlStoreImplement (line 206) | class ZhihuJsonlStoreImplement(AbstractStore):
    method __init__ (line 207) | def __init__(self, **kwargs):
    method store_content (line 211) | async def store_content(self, content_item: Dict):
    method store_comment (line 214) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 217) | async def store_creator(self, creator: Dict):
  class ZhihuSqliteStoreImplement (line 221) | class ZhihuSqliteStoreImplement(ZhihuDbStoreImplement):
  class ZhihuMongoStoreImplement (line 228) | class ZhihuMongoStoreImplement(AbstractStore):
    method __init__ (line 231) | def __init__(self):
    method store_content (line 234) | async def store_content(self, content_item: Dict):
    method store_comment (line 251) | async def store_comment(self, comment_item: Dict):
    method store_creator (line 268) | async def store_creator(self, creator_item: Dict):
  class ZhihuExcelStoreImplement (line 286) | class ZhihuExcelStoreImplement:
    method __new__ (line 289) | def __new__(cls, *args, **kwargs):

FILE: test/test_db_sync.py
  function get_mysql_engine (line 35) | def get_mysql_engine():
  function get_sqlite_engine (line 40) | def get_sqlite_engine():
  function get_db_schema (line 45) | def get_db_schema(engine):
  function get_orm_schema (line 56) | def get_orm_schema():
  function compare_schemas (line 66) | def compare_schemas(db_schema, orm_schema):
  function print_diff (line 101) | def print_diff(db_name, diff):
  function sync_database (line 133) | def sync_database(engine, diff):
  function main (line 181) | def main():

FILE: test/test_expiring_local_cache.py
  class TestExpiringLocalCache (line 33) | class TestExpiringLocalCache(unittest.TestCase):
    method setUp (line 35) | def setUp(self):
    method test_set_and_get (line 38) | def test_set_and_get(self):
    method test_expired_key (line 42) | def test_expired_key(self):
    method test_clear (line 47) | def test_clear(self):
    method tearDown (line 54) | def tearDown(self):

FILE: test/test_mongodb_integration.py
  class TestMongoDBRealConnection (line 33) | class TestMongoDBRealConnection(unittest.TestCase):
    method setUpClass (line 36) | def setUpClass(cls):
    method setUp (line 46) | def setUp(self):
    method tearDown (line 54) | def tearDown(self):
    method tearDownClass (line 60) | def tearDownClass(cls):
    method test_real_connection (line 89) | def test_real_connection(self):
    method test_real_save_and_query (line 103) | def test_real_save_and_query(self):
    method test_real_update (line 132) | def test_real_update(self):
    method test_real_find_many (line 170) | def test_real_find_many(self):
    method test_real_create_index (line 205) | def test_real_create_index(self):
    method test_xhs_store_implementation (line 222) | def test_xhs_store_implementation(self):
    method test_douyin_store_implementation (line 274) | def test_douyin_store_implementation(self):
    method test_concurrent_operations (line 319) | def test_concurrent_operations(self):
  function run_integration_tests (line 351) | def run_integration_tests():

FILE: test/test_proxy_ip_pool.py
  class TestIpPool (line 33) | class TestIpPool(IsolatedAsyncioTestCase):
    method test_ip_pool (line 34) | async def test_ip_pool(self):
    method test_ip_expiration (line 42) | async def test_ip_expiration(self):
    method test_proxy_pool_auto_refresh (line 94) | async def test_proxy_pool_auto_refresh(self):
    method test_ip_expiration_standalone (line 136) | async def test_ip_expiration_standalone(self):

FILE: test/test_redis_cache.py
  class TestRedisCache (line 33) | class TestRedisCache(unittest.TestCase):
    method setUp (line 35) | def setUp(self):
    method test_set_and_get (line 38) | def test_set_and_get(self):
    method test_expired_key (line 42) | def test_expired_key(self):
    method test_keys (line 47) | def test_keys(self):
    method tearDown (line 54) | def tearDown(self):

FILE: test/test_utils.py
  function test_convert_cookies (line 26) | def test_convert_cookies():

FILE: tests/conftest.py
  function project_root_path (line 33) | def project_root_path():
  function sample_xhs_note (line 39) | def sample_xhs_note():
  function sample_xhs_comment (line 66) | def sample_xhs_comment():
  function sample_xhs_creator (line 85) | def sample_xhs_creator():

FILE: tests/test_excel_store.py
  class TestExcelStoreBase (line 40) | class TestExcelStoreBase:
    method clear_singleton_state (line 44) | def clear_singleton_state(self):
    method temp_dir (line 51) | def temp_dir(self):
    method excel_store (line 59) | def excel_store(self, temp_dir, monkeypatch):
    method test_initialization (line 67) | def test_initialization(self, excel_store):
    method test_store_content (line 77) | async def test_store_content(self, excel_store):
    method test_store_comment (line 96) | async def test_store_comment(self, excel_store):
    method test_store_creator (line 114) | async def test_store_creator(self, excel_store):
    method test_multiple_items (line 131) | async def test_multiple_items(self, excel_store):
    method test_flush (line 144) | def test_flush(self, excel_store):
    method test_header_formatting (line 163) | def test_header_formatting(self, excel_store):
    method test_empty_sheets_removed (line 173) | def test_empty_sheets_removed(self, excel_store):
  function test_excel_import_availability (line 191) | def test_excel_import_availability():
  class TestSingletonPattern (line 199) | class TestSingletonPattern:
    method setup_and_teardown (line 203) | def setup_and_teardown(self, tmp_path, monkeypatch):
    method test_get_instance_returns_same_instance (line 213) | def test_get_instance_returns_same_instance(self):
    method test_get_instance_different_params_returns_different_instances (line 220) | def test_get_instance_different_params_returns_different_instances(self):
    method test_singleton_preserves_data (line 231) | async def test_singleton_preserves_data(self):
    method test_flush_all_saves_all_instances (line 245) | def test_flush_all_saves_all_instances(self, tmp_path):
    method test_flush_all_clears_instances (line 265) | def test_flush_all_clears_instances(self):

FILE: tests/test_store_factory.py
  class TestXhsStoreFactory (line 38) | class TestXhsStoreFactory:
    method test_create_csv_store (line 42) | def test_create_csv_store(self):
    method test_create_json_store (line 48) | def test_create_json_store(self):
    method test_create_db_store (line 54) | def test_create_db_store(self):
    method test_create_sqlite_store (line 60) | def test_create_sqlite_store(self):
    method test_create_mongodb_store (line 66) | def test_create_mongodb_store(self):
    method test_create_excel_store (line 72) | def test_create_excel_store(self):
    method test_create_jsonl_store (line 79) | def test_create_jsonl_store(self):
    method test_invalid_store_option (line 85) | def test_invalid_store_option(self):
    method test_all_stores_registered (line 92) | def test_all_stores_registered(self):

FILE: tools/app_runner.py
  function run (line 32) | def run(

FILE: tools/async_file_writer.py
  class AsyncFileWriter (line 30) | class AsyncFileWriter:
    method __init__ (line 31) | def __init__(self, platform: str, crawler_type: str):
    method _get_file_path (line 37) | def _get_file_path(self, file_type: str, item_type: str) -> str:
    method write_to_csv (line 46) | async def write_to_csv(self, item: Dict, item_type: str):
    method write_to_jsonl (line 56) | async def write_to_jsonl(self, item: Dict, item_type: str):
    method write_single_item_to_json (line 62) | async def write_single_item_to_json(self, item: Dict, item_type: str):
    method generate_wordcloud_from_comments (line 82) | async def generate_wordcloud_from_comments(self):

FILE: tools/browser_launcher.py
  class BrowserLauncher (line 34) | class BrowserLauncher:
    method __init__ (line 40) | def __init__(self):
    method detect_browser_paths (line 45) | def detect_browser_paths(self) -> List[str]:
    method find_available_port (line 104) | def find_available_port(self, start_port: int = 9222) -> int:
    method launch_browser (line 119) | def launch_browser(self, browser_path: str, debug_port: int, headless:...
    method wait_for_browser_ready (line 191) | def wait_for_browser_ready(self, debug_port: int, timeout: int = 30) -...
    method get_browser_info (line 214) | def get_browser_info(self, browser_path: str) -> Tuple[str, str]:
    method cleanup (line 241) | def cleanup(self):

FILE: tools/cdp_browser.py
  class CDPBrowserManager (line 35) | class CDPBrowserManager:
    method __init__ (line 40) | def __init__(self):
    method _register_cleanup_handlers (line 47) | def _register_cleanup_handlers(self):
    method launch_and_connect (line 97) | async def launch_and_connect(
    method _get_browser_path (line 136) | async def _get_browser_path(self) -> str:
    method _test_cdp_connection (line 166) | async def _test_cdp_connection(self, debug_port: int) -> bool:
    method _launch_browser (line 189) | async def _launch_browser(self, browser_path: str, headless: bool):
    method _get_browser_websocket_url (line 227) | async def _get_browser_websocket_url(self, debug_port: int) -> str:
    method _connect_via_cdp (line 252) | async def _connect_via_cdp(self, playwright: Playwright):
    method _create_browser_context (line 276) | async def _create_browser_context(
    method add_stealth_script (line 316) | async def add_stealth_script(self, script_path: str = "libs/stealth.mi...
    method add_cookies (line 329) | async def add_cookies(self, cookies: list):
    method get_cookies (line 340) | async def get_cookies(self) -> list:
    method cleanup (line 353) | async def cleanup(self, force: bool = False):
    method is_connected (line 422) | def is_connected(self) -> bool:
    method get_browser_info (line 428) | async def get_browser_info(self) -> Dict[str, Any]:

FILE: tools/crawler_util.py
  function find_login_qrcode (line 43) | async def find_login_qrcode(page: Page, selector: str) -> str:
  function find_qrcode_img_from_canvas (line 66) | async def find_qrcode_img_from_canvas(page: Page, canvas_selector: str) ...
  function show_qrcode (line 88) | def show_qrcode(qr_code) -> None:  # type: ignore
  function get_user_agent (line 105) | def get_user_agent() -> str:
  function get_mobile_user_agent (line 131) | def get_mobile_user_agent() -> str:
  function convert_cookies (line 138) | def convert_cookies(cookies: Optional[List[Cookie]]) -> Tuple[str, Dict]:
  function convert_str_cookie_to_dict (line 148) | def convert_str_cookie_to_dict(cookie_str: str) -> Dict:
  function match_interact_info_count (line 166) | def match_interact_info_count(count_str: str) -> int:
  function format_proxy_info (line 178) | def format_proxy_info(ip_proxy_info) -> Tuple[Optional[Dict], Optional[s...
  function extract_text_from_html (line 204) | def extract_text_from_html(html: str) -> str:
  function extract_url_params_to_dict (line 215) | def extract_url_params_to_dict(url: str) -> Dict:

FILE: tools/easing.py
  function ease_in_quad (line 32) | def ease_in_quad(x):
  function ease_out_quad (line 36) | def ease_out_quad(x):
  function ease_out_quart (line 40) | def ease_out_quart(x):
  function ease_out_expo (line 44) | def ease_out_expo(x):
  function ease_out_bounce (line 51) | def ease_out_bounce(x):
  function ease_out_elastic (line 67) | def ease_out_elastic(x):
  function get_tracks (line 77) | def get_tracks(distance, seconds, ease_func) -> Tuple[List[int], List[in...

FILE: tools/file_header_manager.py
  function get_file_relative_path (line 53) | def get_file_relative_path(file_path: str, project_root: str) -> str:
  function generate_copyright_header (line 67) | def generate_copyright_header(relative_path: str) -> str:
  function has_copyright_header (line 90) | def has_copyright_header(content: str) -> bool:
  function has_disclaimer (line 104) | def has_disclaimer(content: str) -> bool:
  function find_insert_position (line 117) | def find_insert_position(lines: List[str]) -> Tuple[int, bool]:
  function process_file (line 147) | def process_file(file_path: str, project_root: str, dry_run: bool = Fals...
  function find_python_files (line 213) | def find_python_files(root_dir: str, exclude_patterns: List[str] = None)...
  function main (line 240) | def main():

FILE: tools/httpx_util.py
  function make_async_client (line 6) | def make_async_client(**kwargs) -> httpx.AsyncClient:

FILE: tools/slider_util.py
  class Slide (line 34) | class Slide:
    method __init__ (line 39) | def __init__(self, gap, bg, gap_size=None, bg_size=None, out=None):
    method check_is_img_path (line 55) | def check_is_img_path(img, img_type, resize):
    method clear_white (line 84) | def clear_white(img):
    method template_match (line 108) | def template_match(self, tpl, target):
    method image_edge_detection (line 126) | def image_edge_detection(img):
    method discern (line 130) | def discern(self):
  function get_track_simple (line 145) | def get_track_simple(distance) -> List[int]:
  function get_tracks (line 178) | def get_tracks(distance: int, level: str = "easy") -> List[int]:

FILE: tools/time_util.py
  function get_current_timestamp (line 30) | def get_current_timestamp() -> int:
  function get_current_time (line 38) | def get_current_time() -> str:
  function get_current_time_hour (line 45) | def get_current_time_hour() -> str:
  function get_current_date (line 52) | def get_current_date() -> str:
  function get_time_str_from_unix_time (line 60) | def get_time_str_from_unix_time(unixtime):
  function get_date_str_from_unix_time (line 71) | def get_date_str_from_unix_time(unixtime):
  function get_unix_time_from_time_str (line 82) | def get_unix_time_from_time_str(time_str):
  function get_unix_timestamp (line 97) | def get_unix_timestamp():
  function rfc2822_to_china_datetime (line 101) | def rfc2822_to_china_datetime(rfc2822_time):
  function rfc2822_to_timestamp (line 113) | def rfc2822_to_timestamp(rfc2822_time):

FILE: tools/utils.py
  function init_loging_config (line 29) | def init_loging_config():
  function str2bool (line 47) | def str2bool(v):

FILE: tools/words.py
  class AsyncWordCloudGenerator (line 36) | class AsyncWordCloudGenerator:
    method __init__ (line 37) | def __init__(self):
    method load_stop_words (line 46) | def load_stop_words(self):
    method generate_word_frequency_and_cloud (line 50) | async def generate_word_frequency_and_cloud(self, data, save_words_pre...
    method generate_word_cloud (line 67) | async def generate_word_cloud(self, word_freq, save_words_prefix):
Condensed preview — 212 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (3,328K chars).
[
  {
    "path": ".gitattributes",
    "chars": 93,
    "preview": "*.js linguist-language=python\n*.css linguist-language=python\n*.html linguist-language=python\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 554,
    "preview": "# 默认:仓库所有文件都需要 @NanmiCoder 审核\n*                                   @NanmiCoder\n\n\n.github/workflows/**               @Nanm"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 696,
    "preview": "---  \nname: MediaCrawler Bug反馈  \nabout: 创建一个问题Bug以帮助MediaCrawler开源项目改进 \ntitle: '[BUG] '  \nlabels: bug  \nassignees: ''  \n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/quesiton.md",
    "chars": 716,
    "preview": "---  \nname: MediaCrawler使用问题咨询  \nabout: 提交使用过程中遇到的问题  \ntitle: '[问题] '  \nlabels: question  \nassignees: ''  \n---  \n\n## ⚠️ "
  },
  {
    "path": ".github/workflows/deploy.yml",
    "chars": 1606,
    "preview": "# 构建 VitePress 站点并将其部署到 GitHub Pages 的示例工作流程\n#\nname: Deploy VitePress site to Pages\n\non:\n  # 在针对 `main` 分支的推送上运行。如果你\n  #"
  },
  {
    "path": ".gitignore",
    "chars": 3260,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 1346,
    "preview": "# Pre-commit hooks configuration for MediaCrawler project\n# See https://pre-commit.com for more information\n\nrepos:\n  # "
  },
  {
    "path": ".python-version",
    "chars": 5,
    "preview": "3.11\n"
  },
  {
    "path": "LICENSE",
    "chars": 3411,
    "preview": "NON-COMMERCIAL LEARNING LICENSE 1.1\n\nCopyright (c) [2024] [relakkes@gmail.com]\n\nWHEREAS:\n1. The copyright owner owns and"
  },
  {
    "path": "README.md",
    "chars": 8086,
    "preview": "# 🔥 MediaCrawler - 自媒体平台爬虫 🕷️\n\n<div align=\"center\">\n\n<a href=\"https://trendshift.io/repositories/8291\" target=\"_blank\">\n"
  },
  {
    "path": "README_en.md",
    "chars": 15382,
    "preview": "# 🔥 MediaCrawler - Social Media Platform Crawler 🕷️\n\n<div align=\"center\">\n\n<a href=\"https://trendshift.io/repositories/8"
  },
  {
    "path": "README_es.md",
    "chars": 17402,
    "preview": "# 🔥 MediaCrawler - Rastreador de Plataformas de Redes Sociales 🕷️\n\n<div align=\"center\">\n\n<a href=\"https://trendshift.io/"
  },
  {
    "path": "api/__init__.py",
    "chars": 555,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "api/main.py",
    "chars": 6141,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "api/routers/__init__.py",
    "chars": 728,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "api/routers/crawler.py",
    "chars": 2294,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "api/routers/data.py",
    "chars": 7727,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "api/routers/websocket.py",
    "chars": 4529,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "api/schemas/__init__.py",
    "chars": 877,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "api/schemas/crawler.py",
    "chars": 2515,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "api/services/__init__.py",
    "chars": 638,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "api/services/crawler_manager.py",
    "chars": 10282,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "api/webui/assets/index-DvClRayq.js",
    "chars": 514782,
    "preview": "var $m=t=>{throw TypeError(t)};var ld=(t,e,r)=>e.has(t)||$m(\"Cannot \"+r);var R=(t,e,r)=>(ld(t,e,\"read from private field"
  },
  {
    "path": "api/webui/assets/index-OiBmsgXF.css",
    "chars": 47996,
    "preview": "*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: "
  },
  {
    "path": "api/webui/index.html",
    "chars": 767,
    "preview": "<!doctype html>\n<html lang=\"zh-CN\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href"
  },
  {
    "path": "base/__init__.py",
    "chars": 520,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "base/base_crawler.py",
    "chars": 3281,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "cache/__init__.py",
    "chars": 521,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "cache/abs_cache.py",
    "chars": 1685,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "cache/cache_factory.py",
    "chars": 1323,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "cache/local_cache.py",
    "chars": 3830,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "cache/redis_cache.py",
    "chars": 3588,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "cmd_arg/__init__.py",
    "chars": 544,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "cmd_arg/arg.py",
    "chars": 13432,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "config/__init__.py",
    "chars": 576,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "config/base_config.py",
    "chars": 5224,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "config/bilibili_config.py",
    "chars": 2044,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "config/db_config.py",
    "chars": 2468,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "config/dy_config.py",
    "chars": 1871,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "config/ks_config.py",
    "chars": 1368,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "config/tieba_config.py",
    "chars": 874,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "config/weibo_config.py",
    "chars": 1203,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "config/xhs_config.py",
    "chars": 1269,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "config/zhihu_config.py",
    "chars": 949,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "constant/__init__.py",
    "chars": 550,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "constant/baidu_tieba.py",
    "chars": 592,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "constant/zhihu.py",
    "chars": 704,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "database/__init__.py",
    "chars": 523,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "database/db.py",
    "chars": 1626,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "database/db_session.py",
    "chars": 4131,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "database/models.py",
    "chars": 20979,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "database/mongodb_store_base.py",
    "chars": 5718,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "docs/.vitepress/config.mjs",
    "chars": 2807,
    "preview": "import {defineConfig} from 'vitepress'\nimport {withMermaid} from 'vitepress-plugin-mermaid'\n\n// https://vitepress.dev/re"
  },
  {
    "path": "docs/.vitepress/theme/DynamicAds.vue",
    "chars": 1753,
    "preview": "<!-- 在vitepress右侧的目录导航中插入动态广告组件-->\n\n<script setup>\nimport { ref, onMounted, onUnmounted } from 'vue'\n\nconst ads = ref([]"
  },
  {
    "path": "docs/.vitepress/theme/MyLayout.vue",
    "chars": 291,
    "preview": "<!--.vitepress/theme/MyLayout.vue-->\n<script setup>\nimport DefaultTheme from 'vitepress/theme'\nimport DynamicAds from '."
  },
  {
    "path": "docs/.vitepress/theme/custom.css",
    "chars": 253,
    "preview": "/* .vitepress/theme/custom.css */\n/**\n * Component: Sidebar\n * ---------------------------------------------------------"
  },
  {
    "path": "docs/.vitepress/theme/index.js",
    "chars": 199,
    "preview": "// .vitepress/theme/index.js\nimport DefaultTheme from 'vitepress/theme'\nimport MyLayout from './MyLayout.vue'\n\nexport de"
  },
  {
    "path": "docs/CDP模式使用指南.md",
    "chars": 3972,
    "preview": "# CDP模式使用指南\n\n## 概述\n\nCDP(Chrome DevTools Protocol)模式是一种高级的反检测爬虫技术,通过控制用户现有的Chrome/Edge浏览器来进行网页爬取。与传统的Playwright自动化相比,CDP模"
  },
  {
    "path": "docs/data_storage_guide.md",
    "chars": 1853,
    "preview": "# 数据保存指南 / Data Storage Guide\n\n\n### 💾 数据保存\n\nMediaCrawler 支持多种数据存储方式,您可以根据需求选择最适合的方案:\n\n#### 存储方式\n\n- **CSV 文件**:支持保存到 CSV "
  },
  {
    "path": "docs/excel_export_guide.md",
    "chars": 6094,
    "preview": "# Excel Export Guide\n\n## Overview\n\nMediaCrawler now supports exporting crawled data to formatted Excel files (.xlsx) wit"
  },
  {
    "path": "docs/hit_stopwords.txt",
    "chars": 2355,
    "preview": "\\n\n———\n》),\n)÷(1-\n”,\n)、\n=(\n:\n→\n℃ \n&\n*\n一一\n~~~~\n’\n. \n『\n.一\n./\n-- \n』\n=″\n【\n[*]\n}>\n[⑤]]\n[①D]\nc]\nng昉\n*\n//\n[\n]\n[②e]\n[②g]\n={\n}\n,也 "
  },
  {
    "path": "docs/index.md",
    "chars": 2462,
    "preview": "# MediaCrawler使用方法\n\n## 项目文档\n\n- [项目架构文档](项目架构文档.md) - 系统架构、模块设计、数据流向(含 Mermaid 图表)\n\n## 推荐:使用 uv 管理依赖\n\n### 1. 前置依赖\n- 安装 [u"
  },
  {
    "path": "docs/mediacrawlerpro订阅.md",
    "chars": 1181,
    "preview": "# 订阅MediaCrawlerPro版本源码访问权限\n\n## 获取Pro版本的访问权限\n> MediaCrawler开源超过一年了,相信该仓库帮过不少朋友低门槛的学习和了解爬虫。维护真的耗费了大量精力和人力 <br>\n> \n> 所以Pro"
  },
  {
    "path": "docs/代理使用.md",
    "chars": 216,
    "preview": "# 代理 IP 使用说明\n> 还是得跟大家再次强调下,不要对一些自媒体平台进行大规模爬虫或其他非法行为,要踩缝纫机的哦🤣\n\n## 简易的流程图\n\n![代理 IP 使用流程图](static/images/代理IP%20流程图.drawio."
  },
  {
    "path": "docs/作者介绍.md",
    "chars": 741,
    "preview": "# 关于作者\n> 大家都叫我阿江,网名:程序员阿江-Relakkes,目前是一名独立开发者,专注于 AI Agent 和爬虫相关的开发工作,All in AI。\n\n- [Github万星开源自媒体爬虫仓库MediaCrawler作者](ht"
  },
  {
    "path": "docs/原生环境管理文档.md",
    "chars": 1385,
    "preview": "# 本地原生环境管理\n\n## 推荐方案:使用 uv 管理依赖\n\n### 1. 前置依赖\n- 安装 [uv](https://docs.astral.sh/uv/getting-started/installation),并使用 `uv --"
  },
  {
    "path": "docs/常见问题.md",
    "chars": 1412,
    "preview": "# 常见程序运行出错问题\n\n## 缺少node环境导致的问题\nQ: 爬取抖音和知乎报错: `execjs._exceptions.ProgramError: SyntaxError: 缺少 ';'` <br>\nA: 该错误为缺少 nodej"
  },
  {
    "path": "docs/开发者咨询.md",
    "chars": 240,
    "preview": "# 开发者咨询\n\n## 咨询价格\n\n提供200/小时的咨询服务,最低收费为1小时,帮你快速解决项目中遇到的问题\n\n##### 支持的提问类别\n- MediaCrawler项目源码解读、安装、部署、使用问题\n- 爬虫项目开发问题\n- Pyth"
  },
  {
    "path": "docs/微信交流群.md",
    "chars": 232,
    "preview": "# MediaCrawler项目微信交流群\n\n👏👏👏 汇聚爬虫技术爱好者,共同学习,共同进步。\n\n❗️❗️❗️群内禁止广告,禁止发各类违规和MediaCrawler不相关的问题\n\n## 加群方式\n> 备注:github,会有拉群小助手自动拉"
  },
  {
    "path": "docs/快代理使用文档.md",
    "chars": 1010,
    "preview": "## 快代理使用文档(支持个人和企业用户)\n\n## 准备代理 IP 信息\n点击 <a href=\"https://www.kuaidaili.com/?ref=ldwkjqipvz6c\">快代理</a> 官网注册并实名认证(国内使用代理 I"
  },
  {
    "path": "docs/手机号登录说明.md",
    "chars": 676,
    "preview": "# 关于手机号+验证码登录的说明\n> 配置过程相当复杂,不建议采用该种方式\n\n当在浏览器模拟人为发起手机号登录请求时,使用短信转发软件将验证码发送至爬虫端回填,完成自动登录\n\n准备工作:\n\n- 安卓机1台(IOS没去研究,理论上监控短信也是"
  },
  {
    "path": "docs/捐赠名单.md",
    "chars": 2220,
    "preview": "## 捐赠MediaCrawler开源项目\n> 捐赠时请务必备注您的昵称,我会在捐赠名单中表达对您的感谢\n\n## 赞赏二维码\n\n<table align=\"center\">\n  <tr>\n    <td align=\"center\">\n  "
  },
  {
    "path": "docs/知识付费介绍.md",
    "chars": 507,
    "preview": "# 知识付费介绍\n开源是一种无私奉献,从MediaCrawler开源到现在有一年多,它并没有带给我多少实质性的东西,就拿收入来说,赞助费、赞赏等等全部加起来还没有之前一个月的薪水。\n\n后面搞了MediaCrawler源码剖析课程之后,收入稍"
  },
  {
    "path": "docs/词云图使用配置.md",
    "chars": 1074,
    "preview": "# 关于词云图相关操作\n\n## 1.如何正确调用词云图\n> ps:保存格式为json或jsonl文件时,才会生成词云图。其他存储方式添加词云图将在近期添加。\n\n需要修改的配置项(./config/base_config.py):\n\n```p"
  },
  {
    "path": "docs/豌豆HTTP使用文档.md",
    "chars": 799,
    "preview": "## 豌豆HTTP代理使用文档 (只支持企业用户)\n\n## 准备代理 IP 信息\n点击 <a href=\"https://h.wandouip.com?invite_code=rtnifi\">豌豆HTTP代理</a> 官网注册并实名认证(国"
  },
  {
    "path": "docs/项目代码结构.md",
    "chars": 2366,
    "preview": "# 项目代码结构\n\n```\nMediaCrawler\n├── base\n│   └── base_crawler.py         # 项目的抽象基类\n├── cache\n│   ├── abs_cache.py            "
  },
  {
    "path": "docs/项目架构文档.md",
    "chars": 18729,
    "preview": "# MediaCrawler 项目架构文档\n\n## 1. 项目概述\n\n### 1.1 项目简介\n\nMediaCrawler 是一个多平台自媒体爬虫框架,采用 Python 异步编程实现,支持爬取主流社交媒体平台的内容、评论和创作者信息。\n\n"
  },
  {
    "path": "libs/douyin.js",
    "chars": 15100,
    "preview": "// All the content in this article is only for learning and communication use, not for any other purpose, strictly prohi"
  },
  {
    "path": "libs/zhihu.js",
    "chars": 6395,
    "preview": "// copy from https://github.com/tiam-bloom/zhihuQuestionAnswer/blob/main/zhihuvmp.js thanks to tiam-bloom\n// 仅供学习交流使用,严禁"
  },
  {
    "path": "main.py",
    "chars": 4998,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/__init__.py",
    "chars": 530,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/bilibili/__init__.py",
    "chars": 660,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/bilibili/client.py",
    "chars": 21313,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/bilibili/core.py",
    "chars": 34623,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/bilibili/exception.py",
    "chars": 835,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/bilibili/field.py",
    "chars": 1071,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/bilibili/help.py",
    "chars": 5110,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/bilibili/login.py",
    "chars": 4846,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/douyin/__init__.py",
    "chars": 571,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/douyin/client.py",
    "chars": 15310,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/douyin/core.py",
    "chars": 21082,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/douyin/exception.py",
    "chars": 734,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/douyin/field.py",
    "chars": 1134,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/douyin/help.py",
    "chars": 6675,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/douyin/login.py",
    "chars": 13097,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/kuaishou/__init__.py",
    "chars": 599,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/kuaishou/client.py",
    "chars": 13004,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/kuaishou/core.py",
    "chars": 18698,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/kuaishou/exception.py",
    "chars": 736,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/kuaishou/field.py",
    "chars": 562,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/kuaishou/graphql/comment_list.graphql",
    "chars": 715,
    "preview": "query commentListQuery($photoId: String, $pcursor: String) {\n  visionCommentList(photoId: $photoId, pcursor: $pcursor) {"
  },
  {
    "path": "media_platform/kuaishou/graphql/search_query.graphql",
    "chars": 1618,
    "preview": "fragment photoContent on PhotoEntity {\n  __typename\n  id\n  duration\n  caption\n  originCaption\n  likeCount\n  viewCount\n  "
  },
  {
    "path": "media_platform/kuaishou/graphql/video_detail.graphql",
    "chars": 1479,
    "preview": "query visionVideoDetail($photoId: String, $type: String, $page: String, $webPageArea: String) {\n  visionVideoDetail(phot"
  },
  {
    "path": "media_platform/kuaishou/graphql/vision_profile.graphql",
    "chars": 440,
    "preview": "query visionProfile($userId: String) {\n  visionProfile(userId: $userId) {\n    result\n    hostName\n    userProfile {\n    "
  },
  {
    "path": "media_platform/kuaishou/graphql/vision_profile_photo_list.graphql",
    "chars": 1550,
    "preview": "fragment photoContent on PhotoEntity {\n  __typename\n  id\n  duration\n  caption\n  originCaption\n  likeCount\n  viewCount\n  "
  },
  {
    "path": "media_platform/kuaishou/graphql/vision_profile_user_list.graphql",
    "chars": 290,
    "preview": "query visionProfileUserList($pcursor: String, $ftype: Int) {\n  visionProfileUserList(pcursor: $pcursor, ftype: $ftype) {"
  },
  {
    "path": "media_platform/kuaishou/graphql/vision_sub_comment_list.graphql",
    "chars": 469,
    "preview": "mutation visionSubCommentList($photoId: String, $rootCommentId: String, $pcursor: String) {\n  visionSubCommentList(photo"
  },
  {
    "path": "media_platform/kuaishou/graphql.py",
    "chars": 1453,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/kuaishou/help.py",
    "chars": 3624,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/kuaishou/login.py",
    "chars": 4628,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/tieba/__init__.py",
    "chars": 593,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/tieba/client.py",
    "chars": 27353,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/tieba/core.py",
    "chars": 27241,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/tieba/field.py",
    "chars": 917,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/tieba/help.py",
    "chars": 20213,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/tieba/login.py",
    "chars": 5240,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/tieba/test_data/note_comments.html",
    "chars": 609163,
    "preview": "<html><head><script type=\"text/javascript\" async=\"\" src=\"https://pos.baidu.com/auto_dup?di=0&amp;uuid=3d1a88cacb4a77f3&a"
  },
  {
    "path": "media_platform/tieba/test_data/note_detail.html",
    "chars": 563671,
    "preview": "<html><script type=\"text/javascript\" async=\"\" src=\"https://pos.baidu.com/auto_dup?di=0&amp;uuid=5ded6665513a5df6&amp;dri"
  },
  {
    "path": "media_platform/tieba/test_data/note_sub_comments.html",
    "chars": 19388,
    "preview": "<li class=\"lzl_single_post j_lzl_s_p first_no_border\" data-field='{&quot;spid&quot;:150726504693,&quot;showname&quot;:&q"
  },
  {
    "path": "media_platform/tieba/test_data/search_keyword_notes.html",
    "chars": 7398,
    "preview": "<div class=\"s_post_list\">\n\t<div class=\"s_post\"><span class=\"p_title\"><a data-tid=\"9117888152\" data-fid=\"26976424\" class="
  },
  {
    "path": "media_platform/tieba/test_data/tieba_note_list.html",
    "chars": 392420,
    "preview": "<!DOCTYPE html>\n<!--STATUS OK-->\n<html>\n<head>\n    <meta charset=\"UTF-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content"
  },
  {
    "path": "media_platform/weibo/__init__.py",
    "chars": 730,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/weibo/client.py",
    "chars": 16157,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/weibo/core.py",
    "chars": 20772,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/weibo/exception.py",
    "chars": 832,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/weibo/field.py",
    "chars": 822,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/weibo/help.py",
    "chars": 1354,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/weibo/login.py",
    "chars": 5087,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/xhs/__init__.py",
    "chars": 594,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/xhs/client.py",
    "chars": 24613,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/xhs/core.py",
    "chars": 23176,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/xhs/exception.py",
    "chars": 817,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/xhs/extractor.py",
    "chars": 1966,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/xhs/field.py",
    "chars": 1902,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/xhs/help.py",
    "chars": 11611,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/xhs/login.py",
    "chars": 10555,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/xhs/playwright_sign.py",
    "chars": 7144,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/xhs/xhs_sign.py",
    "chars": 6432,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/zhihu/__init__.py",
    "chars": 593,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/zhihu/client.py",
    "chars": 19897,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/zhihu/core.py",
    "chars": 20084,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/zhihu/exception.py",
    "chars": 790,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/zhihu/field.py",
    "chars": 1487,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/zhihu/help.py",
    "chars": 16036,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "media_platform/zhihu/login.py",
    "chars": 4576,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "model/__init__.py",
    "chars": 547,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "model/m_baidu_tieba.py",
    "chars": 3284,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "model/m_bilibili.py",
    "chars": 1140,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "model/m_douyin.py",
    "chars": 936,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "model/m_kuaishou.py",
    "chars": 920,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "model/m_weibo.py",
    "chars": 546,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "model/m_xiaohongshu.py",
    "chars": 1022,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "model/m_zhihu.py",
    "chars": 4172,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "mypy.ini",
    "chars": 143,
    "preview": "[mypy]\nwarn_return_any = True\nwarn_unused_configs = True\n\n[mypy-cv2]\nignore_missing_imports = True\n\n[mypy-execjs]\nignore"
  },
  {
    "path": "package.json",
    "chars": 272,
    "preview": "{\n  \"scripts\": {\n    \"docs:dev\": \"vitepress dev docs\",\n    \"docs:build\": \"vitepress build docs\",\n    \"docs:preview\": \"vi"
  },
  {
    "path": "proxy/__init__.py",
    "chars": 673,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "proxy/base_proxy.py",
    "chars": 2619,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "proxy/providers/__init__.py",
    "chars": 780,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "proxy/providers/jishu_http_proxy.py",
    "chars": 4535,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "proxy/providers/kuaidl_proxy.py",
    "chars": 6366,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "proxy/providers/wandou_http_proxy.py",
    "chars": 4601,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "proxy/proxy_ip_pool.py",
    "chars": 5653,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "proxy/proxy_mixin.py",
    "chars": 2469,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "proxy/types.py",
    "chars": 1827,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "pyproject.toml",
    "chars": 1147,
    "preview": "[project]\nname = \"mediacrawler\"\nauthor = \"程序员阿江-Relakkes <relakkes@gmail.com>\"\nversion = \"0.1.0\"\ndescription = \"A social"
  },
  {
    "path": "recv_sms.py",
    "chars": 2354,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "requirements.txt",
    "chars": 488,
    "preview": "httpx==0.28.1\nPillow==12.1.0\nplaywright==1.45.0\ntenacity==8.2.2\ntyper>=0.12.3\nopencv-python\naiomysql==0.2.0\nredis~=4.6.0"
  },
  {
    "path": "store/__init__.py",
    "chars": 621,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/bilibili/__init__.py",
    "chars": 9304,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/bilibili/_store_impl.py",
    "chars": 14443,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/bilibili/bilibilli_store_media.py",
    "chars": 2415,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/douyin/__init__.py",
    "chars": 9807,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/douyin/_store_impl.py",
    "chars": 8900,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/douyin/douyin_store_media.py",
    "chars": 3951,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/excel_store_base.py",
    "chars": 13312,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/kuaishou/__init__.py",
    "chars": 5244,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/kuaishou/_store_impl.py",
    "chars": 8063,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/tieba/__init__.py",
    "chars": 3465,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/tieba/_store_impl.py",
    "chars": 8876,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/weibo/__init__.py",
    "chars": 6506,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/weibo/_store_impl.py",
    "chars": 10271,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/weibo/weibo_store_media.py",
    "chars": 2393,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/xhs/__init__.py",
    "chars": 9125,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/xhs/_store_impl.py",
    "chars": 13835,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/xhs/xhs_store_media.py",
    "chars": 4089,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/zhihu/__init__.py",
    "chars": 3855,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "store/zhihu/_store_impl.py",
    "chars": 9482,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "test/__init__.py",
    "chars": 546,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "test/test_db_sync.py",
    "chars": 9669,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "test/test_expiring_local_cache.py",
    "chars": 1583,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "test/test_mongodb_integration.py",
    "chars": 11950,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "test/test_proxy_ip_pool.py",
    "chars": 12261,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "test/test_redis_cache.py",
    "chars": 1576,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "test/test_utils.py",
    "chars": 840,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "tests/__init__.py",
    "chars": 50,
    "preview": "# -*- coding: utf-8 -*-\n# MediaCrawler Test Suite\n"
  },
  {
    "path": "tests/conftest.py",
    "chars": 2794,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "tests/test_excel_store.py",
    "chars": 9357,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "tests/test_store_factory.py",
    "chars": 3329,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "tools/__init__.py",
    "chars": 521,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  },
  {
    "path": "tools/app_runner.py",
    "chars": 3364,
    "preview": "# -*- coding: utf-8 -*-\n# Copyright (c) 2025 relakkes@gmail.com\n#\n# This file is part of MediaCrawler project.\n# Reposit"
  }
]

// ... and 12 more files (download for full content)

About this extraction

This page contains the full source code of the NanmiCoder/MediaCrawler GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 212 files (3.0 MB), approximately 806.3k tokens, and a symbol index with 1825 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!