vibesurf 0.1.26__tar.gz → 0.1.27__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of vibesurf might be problematic. Click here for more details.
- vibesurf-0.1.27/LICENSE +22 -0
- {vibesurf-0.1.26/vibesurf.egg-info → vibesurf-0.1.27}/PKG-INFO +26 -5
- {vibesurf-0.1.26 → vibesurf-0.1.27}/README.md +24 -4
- vibesurf-0.1.27/README_zh.md +97 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/pyproject.toml +1 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/tests/test_browser.py +71 -1
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/_version.py +3 -3
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/agents/vibe_surf_agent.py +4 -5
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/browser/agent_browser_session.py +26 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/tools/browser_use_tools.py +168 -1
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/tools/vibesurf_tools.py +425 -3
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/tools/views.py +75 -0
- vibesurf-0.1.27/vibe_surf/tools/website_api/__init__.py +0 -0
- vibesurf-0.1.27/vibe_surf/tools/website_api/douyin/__init__.py +0 -0
- vibesurf-0.1.27/vibe_surf/tools/website_api/douyin/client.py +845 -0
- vibesurf-0.1.27/vibe_surf/tools/website_api/douyin/douyin.js +435 -0
- vibesurf-0.1.27/vibe_surf/tools/website_api/douyin/helpers.py +239 -0
- vibesurf-0.1.27/vibe_surf/tools/website_api/weibo/__init__.py +0 -0
- vibesurf-0.1.27/vibe_surf/tools/website_api/weibo/client.py +846 -0
- vibesurf-0.1.27/vibe_surf/tools/website_api/weibo/helpers.py +997 -0
- vibesurf-0.1.27/vibe_surf/tools/website_api/xhs/__init__.py +0 -0
- vibesurf-0.1.27/vibe_surf/tools/website_api/xhs/client.py +807 -0
- vibesurf-0.1.27/vibe_surf/tools/website_api/xhs/helpers.py +301 -0
- vibesurf-0.1.27/vibe_surf/tools/website_api/youtube/__init__.py +32 -0
- vibesurf-0.1.27/vibe_surf/tools/website_api/youtube/client.py +1179 -0
- vibesurf-0.1.27/vibe_surf/tools/website_api/youtube/helpers.py +420 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27/vibesurf.egg-info}/PKG-INFO +26 -5
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibesurf.egg-info/SOURCES.txt +15 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibesurf.egg-info/requires.txt +1 -0
- vibesurf-0.1.26/LICENSE +0 -201
- {vibesurf-0.1.26 → vibesurf-0.1.27}/.env.example +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/.github/workflows/publish.yml +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/.gitignore +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/.python-version +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/MANIFEST.in +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/docs/EXECUTABLE_BUILD.md +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/docs/PYPI_SETUP.md +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/scripts/build-local.bat +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/scripts/build-local.sh +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/setup.cfg +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/tests/test_agents.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/tests/test_backend_api.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/tests/test_tools.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/tests/test_voice_api.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/__init__.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/agents/__init__.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/agents/browser_use_agent.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/agents/prompts/__init__.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/agents/prompts/report_writer_prompt.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/agents/prompts/vibe_surf_prompt.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/agents/report_writer_agent.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/agents/views.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/__init__.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/api/__init__.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/api/activity.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/api/agent.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/api/browser.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/api/config.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/api/files.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/api/models.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/api/task.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/api/voices.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/database/__init__.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/database/manager.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/database/migrations/v001_initial_schema.sql +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/database/migrations/v002_add_agent_mode.sql +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/database/migrations/v003_fix_task_status_case.sql +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/database/migrations/v004_add_voice_profiles.sql +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/database/models.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/database/queries.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/database/schemas.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/llm_config.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/main.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/shared_state.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/utils/__init__.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/utils/encryption.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/utils/llm_factory.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/backend/voice_model_config.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/browser/__init__.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/browser/agen_browser_profile.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/browser/browser_manager.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/browser/utils.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/browser/watchdogs/__init__.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/browser/watchdogs/action_watchdog.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/browser/watchdogs/dom_watchdog.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/background.js +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/config.js +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/content.js +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/dev-reload.js +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/icons/logo.icns +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/icons/logo.png +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/manifest.json +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/permission-iframe.html +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/permission-request.html +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/popup.html +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/scripts/api-client.js +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/scripts/file-manager.js +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/scripts/history-manager.js +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/scripts/main.js +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/scripts/markdown-it.min.js +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/scripts/modal-manager.js +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/scripts/permission-iframe-request.js +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/scripts/permission-request.js +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/scripts/session-manager.js +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/scripts/settings-manager.js +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/scripts/ui-manager.js +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/scripts/user-settings-storage.js +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/scripts/voice-recorder.js +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/sidepanel.html +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/styles/activity.css +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/styles/animations.css +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/styles/base.css +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/styles/components.css +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/styles/history-modal.css +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/styles/input.css +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/styles/layout.css +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/styles/responsive.css +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/styles/settings-environment.css +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/styles/settings-forms.css +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/styles/settings-modal.css +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/styles/settings-profiles.css +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/styles/settings-responsive.css +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/styles/settings-utilities.css +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/chrome_extension/styles/variables.css +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/cli.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/common.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/llm/__init__.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/llm/openai_compatible.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/logger.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/tools/__init__.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/tools/file_system.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/tools/finance_tools.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/tools/mcp_client.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/tools/report_writer_tools.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/tools/vibesurf_registry.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibe_surf/tools/voice_asr.py +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibesurf.egg-info/dependency_links.txt +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibesurf.egg-info/entry_points.txt +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibesurf.egg-info/top_level.txt +0 -0
- {vibesurf-0.1.26 → vibesurf-0.1.27}/vibesurf.spec +0 -0
vibesurf-0.1.27/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Open Source License
|
|
2
|
+
|
|
3
|
+
VibeSurf is licensed under a modified version of the Apache License 2.0, with the following additional conditions:
|
|
4
|
+
|
|
5
|
+
1. VibeSurf may be utilized commercially, including as a backend service for other applications or as an application development platform for enterprises. Should the conditions below be met, a commercial license must be obtained from the producer:
|
|
6
|
+
|
|
7
|
+
a. Multi-tenant service: Unless explicitly authorized by VibeSurf in writing, you may not use the VibeSurf source code to operate a multi-tenant environment.
|
|
8
|
+
- Tenant Definition: Within the context of VibeSurf, one tenant corresponds to one workspace. The workspace provides a separated area for each tenant's data and configurations.
|
|
9
|
+
|
|
10
|
+
b. LOGO and copyright information: In the process of using VibeSurf's frontend, you may not remove or modify the LOGO or copyright information in the VibeSurf console or applications. This restriction is inapplicable to uses of VibeSurf that do not involve its frontend.
|
|
11
|
+
- Frontend Definition: For the purposes of this license, the "frontend" of VibeSurf includes all components located in the `vibe_surf/chrome_extension/` directory when running VibeSurf from the raw source code, or the corresponding frontend container/image when running VibeSurf with Docker or other containerization.
|
|
12
|
+
|
|
13
|
+
2. As a contributor, you should agree that:
|
|
14
|
+
|
|
15
|
+
a. The producer can adjust the open-source agreement to be more strict or relaxed as deemed necessary.
|
|
16
|
+
b. Your contributed code may be used for commercial purposes, including but not limited to its cloud business operations.
|
|
17
|
+
|
|
18
|
+
Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache License 2.0. Detailed information about the Apache License 2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0.
|
|
19
|
+
|
|
20
|
+
The interactive design of this product is protected by appearance patent.
|
|
21
|
+
|
|
22
|
+
© 2025 VibeSurf Authors. This repository is licensed under the VibeSurf Open Source License, based on Apache 2.0 with additional conditions.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vibesurf
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.27
|
|
4
4
|
Summary: VibeSurf: A powerful browser assistant for vibe surfing
|
|
5
5
|
Author: Shao Warm
|
|
6
6
|
License: Apache-2.0
|
|
@@ -44,16 +44,19 @@ Requires-Dist: markdownify>=1.2.0
|
|
|
44
44
|
Requires-Dist: pathvalidate>=3.3.1
|
|
45
45
|
Requires-Dist: dashscope>=1.24.5
|
|
46
46
|
Requires-Dist: yfinance>=0.2.66
|
|
47
|
+
Requires-Dist: pyexecjs>=1.5.1
|
|
47
48
|
Dynamic: license-file
|
|
48
49
|
|
|
49
50
|
# VibeSurf: A powerful browser assistant for vibe surfing
|
|
50
|
-
[](https://discord.gg/
|
|
51
|
+
[](https://discord.gg/EZ2YnUXP)
|
|
51
52
|
[](https://x.com/warmshao)
|
|
52
53
|
|
|
53
54
|
VibeSurf is an open-source AI agentic browser that revolutionizes browser automation and research.
|
|
54
55
|
|
|
55
56
|
If you're as excited about open-source AI browsing as I am, give it a star! ⭐
|
|
56
57
|
|
|
58
|
+
[中文](README_zh.md) | [English](README.md)
|
|
59
|
+
|
|
57
60
|
## ✨ Key Features
|
|
58
61
|
|
|
59
62
|
- 🧠 **Advanced AI Automation**: Beyond browser automation, VibeSurf performs deep research, intelligent crawling, content summarization, and more to exploration.
|
|
@@ -90,11 +93,29 @@ uv pip install vibesurf -U
|
|
|
90
93
|
uv run vibesurf
|
|
91
94
|
```
|
|
92
95
|
|
|
93
|
-
##
|
|
96
|
+
## 👩💻 For Contributors
|
|
97
|
+
|
|
98
|
+
Want to contribute to VibeSurf? Here are two ways to set up your development environment:
|
|
99
|
+
|
|
100
|
+
### Method 1: Direct Server Run
|
|
101
|
+
Run the backend server directly using uvicorn:
|
|
102
|
+
```bash
|
|
103
|
+
uvicorn vibe_surf.backend.main:app --host 127.0.0.1 --port 9335
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Method 2: Editable Installation
|
|
107
|
+
Install the package in editable mode and run using the CLI:
|
|
108
|
+
```bash
|
|
109
|
+
uv pip install -e .
|
|
110
|
+
uv run vibesurf
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Choose the method that works best for your development workflow!
|
|
114
|
+
## �️ Roadmap
|
|
94
115
|
|
|
95
116
|
We're building VibeSurf to be your ultimate AI browser companion. Here's what's coming next:
|
|
96
117
|
|
|
97
|
-
- [
|
|
118
|
+
- [x] **Smart Skills System**: Add `/search` for quick information search and `/crawl` for automatic website data extraction
|
|
98
119
|
- [ ] **Powerful Coding Agent**: Build a comprehensive coding assistant for data processing and analysis directly in your browser
|
|
99
120
|
- [ ] **Third-Party Integrations**: Connect with n8n workflows and other tools to combine browsing with automation
|
|
100
121
|
- [ ] **Custom Workflow Templates**: Create reusable templates for auto-login, data collection, and complex browser automation
|
|
@@ -113,7 +134,7 @@ We're building VibeSurf to be your ultimate AI browser companion. Here's what's
|
|
|
113
134
|
|
|
114
135
|
## 📝 License
|
|
115
136
|
|
|
116
|
-
|
|
137
|
+
This repository is licensed under the [VibeSurf Open Source License](./LICENSE), based on Apache 2.0 with additional conditions.
|
|
117
138
|
|
|
118
139
|
## 👏 Acknowledgments
|
|
119
140
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# VibeSurf: A powerful browser assistant for vibe surfing
|
|
2
|
-
[](https://discord.gg/
|
|
2
|
+
[](https://discord.gg/EZ2YnUXP)
|
|
3
3
|
[](https://x.com/warmshao)
|
|
4
4
|
|
|
5
5
|
VibeSurf is an open-source AI agentic browser that revolutionizes browser automation and research.
|
|
6
6
|
|
|
7
7
|
If you're as excited about open-source AI browsing as I am, give it a star! ⭐
|
|
8
8
|
|
|
9
|
+
[中文](README_zh.md) | [English](README.md)
|
|
10
|
+
|
|
9
11
|
## ✨ Key Features
|
|
10
12
|
|
|
11
13
|
- 🧠 **Advanced AI Automation**: Beyond browser automation, VibeSurf performs deep research, intelligent crawling, content summarization, and more to exploration.
|
|
@@ -42,11 +44,29 @@ uv pip install vibesurf -U
|
|
|
42
44
|
uv run vibesurf
|
|
43
45
|
```
|
|
44
46
|
|
|
45
|
-
##
|
|
47
|
+
## 👩💻 For Contributors
|
|
48
|
+
|
|
49
|
+
Want to contribute to VibeSurf? Here are two ways to set up your development environment:
|
|
50
|
+
|
|
51
|
+
### Method 1: Direct Server Run
|
|
52
|
+
Run the backend server directly using uvicorn:
|
|
53
|
+
```bash
|
|
54
|
+
uvicorn vibe_surf.backend.main:app --host 127.0.0.1 --port 9335
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Method 2: Editable Installation
|
|
58
|
+
Install the package in editable mode and run using the CLI:
|
|
59
|
+
```bash
|
|
60
|
+
uv pip install -e .
|
|
61
|
+
uv run vibesurf
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Choose the method that works best for your development workflow!
|
|
65
|
+
## �️ Roadmap
|
|
46
66
|
|
|
47
67
|
We're building VibeSurf to be your ultimate AI browser companion. Here's what's coming next:
|
|
48
68
|
|
|
49
|
-
- [
|
|
69
|
+
- [x] **Smart Skills System**: Add `/search` for quick information search and `/crawl` for automatic website data extraction
|
|
50
70
|
- [ ] **Powerful Coding Agent**: Build a comprehensive coding assistant for data processing and analysis directly in your browser
|
|
51
71
|
- [ ] **Third-Party Integrations**: Connect with n8n workflows and other tools to combine browsing with automation
|
|
52
72
|
- [ ] **Custom Workflow Templates**: Create reusable templates for auto-login, data collection, and complex browser automation
|
|
@@ -65,7 +85,7 @@ We're building VibeSurf to be your ultimate AI browser companion. Here's what's
|
|
|
65
85
|
|
|
66
86
|
## 📝 License
|
|
67
87
|
|
|
68
|
-
|
|
88
|
+
This repository is licensed under the [VibeSurf Open Source License](./LICENSE), based on Apache 2.0 with additional conditions.
|
|
69
89
|
|
|
70
90
|
## 👏 Acknowledgments
|
|
71
91
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# VibeSurf:强大的浏览器助手,用于氛围冲浪
|
|
2
|
+
|
|
3
|
+
[](https://discord.gg/EZ2YnUXP)
|
|
4
|
+
[](https://x.com/warmshao)
|
|
5
|
+
|
|
6
|
+
VibeSurf 是一个开源的 AI 代理浏览器,它革新了浏览器自动化和研究。
|
|
7
|
+
|
|
8
|
+
如果你和我一样对开源 AI 浏览感到兴奋,请给它一个 star!⭐
|
|
9
|
+
|
|
10
|
+
[中文](README_zh.md) | [English](README.md)
|
|
11
|
+
|
|
12
|
+
## ✨ 主要特性
|
|
13
|
+
|
|
14
|
+
- 🧠 **高级 AI 自动化**:超越浏览器自动化,VibeSurf 执行深度研究、智能爬取、内容摘要等,以进行探索。
|
|
15
|
+
|
|
16
|
+
- 🚀 **多代理并行处理**:在不同的浏览器标签页中同时运行多个 AI 代理,实现深度研究和广泛研究,大幅提升效率。
|
|
17
|
+
|
|
18
|
+
- 🥷 **隐身优先架构**:使用 Chrome DevTools 协议(CDP)而不是 Playwright,提供卓越的隐身能力,防止机器人检测。
|
|
19
|
+
|
|
20
|
+
- 🎨 **无缝的 Chrome 扩展 UI**:原生浏览器集成,无需切换应用程序,提供直观的界面,感觉就像浏览器的一部分。
|
|
21
|
+
|
|
22
|
+
- 🔒 **隐私优先的 LLM 支持**:支持本地 LLM(Ollama 等)和自定义 LLM API,确保在氛围冲浪期间您的浏览数据保持私密和安全。
|
|
23
|
+
|
|
24
|
+
## 🛠️ 安装
|
|
25
|
+
|
|
26
|
+
### 第一步:安装 uv
|
|
27
|
+
从 [https://docs.astral.sh/uv/getting-started/installation/](https://docs.astral.sh/uv/getting-started/installation/) 安装 uv:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# 在 macOS 和 Linux 上
|
|
31
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
32
|
+
|
|
33
|
+
# 在 Windows 上
|
|
34
|
+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 第二步:设置和安装
|
|
38
|
+
```bash
|
|
39
|
+
uv venv --python 3.12
|
|
40
|
+
uv pip install vibesurf -U
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 第三步:启动
|
|
44
|
+
```bash
|
|
45
|
+
uv run vibesurf
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 👩💻 对贡献者
|
|
49
|
+
|
|
50
|
+
想为 VibeSurf 做贡献?以下是设置开发环境的两种方法:
|
|
51
|
+
|
|
52
|
+
### 方法 1:直接运行服务器
|
|
53
|
+
使用 uvicorn 直接运行后端服务器:
|
|
54
|
+
```bash
|
|
55
|
+
uvicorn vibe_surf.backend.main:app --host 127.0.0.1 --port 9335
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 方法 2:可编辑安装
|
|
59
|
+
以可编辑模式安装包并使用 CLI 运行:
|
|
60
|
+
```bash
|
|
61
|
+
uv pip install -e .
|
|
62
|
+
uv run vibesurf
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
选择最适合您开发工作流程的方法!
|
|
66
|
+
|
|
67
|
+
## 🗺️ 路线图
|
|
68
|
+
|
|
69
|
+
我们正在构建 VibeSurf,使其成为您终极的 AI 浏览器伴侣。以下是接下来的计划:
|
|
70
|
+
|
|
71
|
+
- [x] **智能技能系统**:添加 `/search` 用于快速信息搜索,`/crawl` 用于自动网站数据提取
|
|
72
|
+
- [ ] **强大的编码代理**:构建一个全面的编码助手,用于在浏览器中直接进行数据处理和分析
|
|
73
|
+
- [ ] **第三方集成**:与 n8n 工作流和其他工具连接,将浏览与自动化结合
|
|
74
|
+
- [ ] **自定义工作流模板**:创建可重用的模板,用于自动登录、数据收集和复杂的浏览器自动化
|
|
75
|
+
- [ ] **智能交互功能**:文本选择用于翻译/问答、截图分析和语音阅读功能
|
|
76
|
+
- [ ] **实时对话和记忆**:添加持久聊天功能和全局记忆,使 VibeSurf 真正理解您
|
|
77
|
+
|
|
78
|
+
## 🎬 演示
|
|
79
|
+
|
|
80
|
+
### 如何使用?
|
|
81
|
+
<video src="https://github.com/user-attachments/assets/0a4650c0-c4ed-423e-9e16-7889e9f9816d" controls="controls">您的浏览器不支持播放此视频!</video>
|
|
82
|
+
|
|
83
|
+
### 在浏览器中运行数十个代理
|
|
84
|
+
<video src="https://github.com/user-attachments/assets/9c461a6e-5d97-4335-ba09-59e8ec4ad47b" controls="controls">您的浏览器不支持播放此视频!</video>
|
|
85
|
+
|
|
86
|
+
## 📝 许可证
|
|
87
|
+
|
|
88
|
+
本仓库采用 [VibeSurf 开源许可证](./LICENSE),基于 Apache 2.0 并附加额外条款。
|
|
89
|
+
|
|
90
|
+
## 👏 致谢
|
|
91
|
+
|
|
92
|
+
VibeSurf 建立在其他优秀的开源项目之上:
|
|
93
|
+
|
|
94
|
+
- [Browser Use](https://github.com/browser-use/browser-use)
|
|
95
|
+
- [LangGraph](https://github.com/langchain-ai/langgraph)
|
|
96
|
+
|
|
97
|
+
非常感谢他们的创作者和贡献者!
|
|
@@ -262,6 +262,75 @@ async def get_all_css_selector(browser_session: AgentBrowserSession):
|
|
|
262
262
|
pdb.set_trace()
|
|
263
263
|
|
|
264
264
|
|
|
265
|
+
async def test_website_api(main_browser_session: AgentBrowserSession):
|
|
266
|
+
# from vibe_surf.tools.website_api.xhs.client import XiaoHongShuApiClient, SearchType
|
|
267
|
+
# xhs_client = XiaoHongShuApiClient(browser_session=main_browser_session)
|
|
268
|
+
# await xhs_client.setup()
|
|
269
|
+
# user_info = await xhs_client.get_me()
|
|
270
|
+
# user_id = user_info['user_id']
|
|
271
|
+
# ret = await xhs_client.search_content_by_keyword("browser-use", sort_type=SearchType.POPULAR)
|
|
272
|
+
# pdb.set_trace()
|
|
273
|
+
# ret = await xhs_client.get_home_recommendations()
|
|
274
|
+
# pdb.set_trace()
|
|
275
|
+
# ret = await xhs_client.get_user_profile(user_id=user_id)
|
|
276
|
+
# ret = await xhs_client.fetch_all_user_content(user_id=user_id)
|
|
277
|
+
# note_id = ret[1]['note_id']
|
|
278
|
+
# xsec_token = ret[1]['xsec_token']
|
|
279
|
+
# ret1 = await xhs_client.fetch_content_details(content_id=note_id, xsec_token=xsec_token)
|
|
280
|
+
# pdb.set_trace()
|
|
281
|
+
# ret2 = await xhs_client.fetch_all_content_comments(content_id=note_id, xsec_token=xsec_token)
|
|
282
|
+
# pdb.set_trace()
|
|
283
|
+
|
|
284
|
+
from vibe_surf.tools.website_api.weibo.client import WeiboApiClient
|
|
285
|
+
from vibe_surf.tools.website_api.weibo.helpers import SearchType
|
|
286
|
+
wb_client = WeiboApiClient(browser_session=main_browser_session)
|
|
287
|
+
await wb_client.setup()
|
|
288
|
+
# ret = await wb_client.search_posts_by_keyword("邓紫棋", page=4, search_type=SearchType.POPULAR)
|
|
289
|
+
# pdb.set_trace()
|
|
290
|
+
# mid = ret[0]['note_id']
|
|
291
|
+
# user_id = ret[0]['user_id']
|
|
292
|
+
# ret = await wb_client.get_post_detail(mid=mid)
|
|
293
|
+
# pdb.set_trace()
|
|
294
|
+
# ret = await wb_client.get_all_post_comments(mid=mid)
|
|
295
|
+
# pdb.set_trace()
|
|
296
|
+
# ret1 = await wb_client.get_user_info(user_id=user_id)
|
|
297
|
+
# ret3 = await wb_client.get_all_user_posts(user_id=user_id)
|
|
298
|
+
# pdb.set_trace()
|
|
299
|
+
ret = await wb_client.get_hot_posts()
|
|
300
|
+
pdb.set_trace()
|
|
301
|
+
ret = await wb_client.get_trending_posts()
|
|
302
|
+
pdb.set_trace()
|
|
303
|
+
|
|
304
|
+
# from vibe_surf.tools.website_api.douyin.client import DouyinApiClient
|
|
305
|
+
#
|
|
306
|
+
# dy_client = DouyinApiClient(main_browser_session)
|
|
307
|
+
# await dy_client.setup()
|
|
308
|
+
# ret = await dy_client.search_content_by_keyword("Sora2")
|
|
309
|
+
# aweme_id = ret[0]['aweme_id']
|
|
310
|
+
# user_id = ret[0]['user_id']
|
|
311
|
+
# ret1 = await dy_client.fetch_video_details(aweme_id=aweme_id)
|
|
312
|
+
# ret2 = await dy_client.fetch_video_comments(aweme_id=aweme_id)
|
|
313
|
+
# ret3 = await dy_client.fetch_user_info(sec_user_id=user_id)
|
|
314
|
+
# ret3 = await dy_client.fetch_user_videos(sec_user_id=user_id)
|
|
315
|
+
# pdb.set_trace()
|
|
316
|
+
|
|
317
|
+
# from vibe_surf.tools.website_api.youtube.client import YouTubeApiClient
|
|
318
|
+
# yt_client = YouTubeApiClient(browser_session=main_browser_session)
|
|
319
|
+
# await yt_client.setup()
|
|
320
|
+
# ret = await yt_client.get_trending_videos()
|
|
321
|
+
# pdb.set_trace()
|
|
322
|
+
# ret = await yt_client.search_videos(query="何同学", max_results=30)
|
|
323
|
+
# pdb.set_trace()
|
|
324
|
+
# ret = await yt_client.get_video_details(ret[0]['video_id'])
|
|
325
|
+
# pdb.set_trace()
|
|
326
|
+
# ret = await yt_client.get_video_comments(ret[0]['video_id'])
|
|
327
|
+
# pdb.set_trace()
|
|
328
|
+
# ret = await yt_client.get_channel_info(ret[0]['channel_id'])
|
|
329
|
+
# pdb.set_trace()
|
|
330
|
+
# ret = await yt_client.get_channel_videos(ret[0]['channel_id'], max_videos=50)
|
|
331
|
+
# pdb.set_trace()
|
|
332
|
+
|
|
333
|
+
|
|
265
334
|
async def main():
|
|
266
335
|
"""
|
|
267
336
|
Main function to run all browser session tests.
|
|
@@ -296,7 +365,8 @@ async def main():
|
|
|
296
365
|
# await test_agent_cleanup(manager)
|
|
297
366
|
# await test_agent_tab_isolation(manager)
|
|
298
367
|
# await test_browser_state_capture(manager)
|
|
299
|
-
await get_all_css_selector(main_browser_session)
|
|
368
|
+
# await get_all_css_selector(main_browser_session)
|
|
369
|
+
await test_website_api(main_browser_session)
|
|
300
370
|
|
|
301
371
|
except Exception as e:
|
|
302
372
|
logging.error(f"An error occurred during tests: {e}", exc_info=True)
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.1.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
31
|
+
__version__ = version = '0.1.27'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 27)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'gb87f28077'
|
|
@@ -479,17 +479,16 @@ async def _vibesurf_agent_node_impl(state: VibeSurfState) -> VibeSurfState:
|
|
|
479
479
|
llm=vibesurf_agent.llm,
|
|
480
480
|
file_system=vibesurf_agent.file_system,
|
|
481
481
|
)
|
|
482
|
-
if action_name
|
|
482
|
+
if action_name.startswith("skill_"):
|
|
483
483
|
state.current_step = "END"
|
|
484
484
|
# Format final response
|
|
485
|
-
final_response = f"{result.extracted_content}"
|
|
486
|
-
await log_agent_activity(state, agent_name, "result", final_response)
|
|
485
|
+
final_response = f"{result.extracted_content}" or f"{result.error}"
|
|
487
486
|
state.final_response = final_response
|
|
488
487
|
logger.debug(final_response)
|
|
489
488
|
state.is_complete = True
|
|
490
|
-
|
|
489
|
+
else:
|
|
490
|
+
state.current_step = "vibesurf_agent"
|
|
491
491
|
|
|
492
|
-
state.current_step = "vibesurf_agent"
|
|
493
492
|
if result.extracted_content:
|
|
494
493
|
vibesurf_agent.message_history.append(
|
|
495
494
|
UserMessage(content=f'Action result:\n{result.extracted_content}'))
|
|
@@ -384,6 +384,32 @@ class AgentBrowserSession(BrowserSession):
|
|
|
384
384
|
)
|
|
385
385
|
]
|
|
386
386
|
|
|
387
|
+
def model_post_init(self, __context) -> None:
|
|
388
|
+
"""Register event handlers after model initialization."""
|
|
389
|
+
# Check if handlers are already registered to prevent duplicates
|
|
390
|
+
|
|
391
|
+
from browser_use.browser.watchdog_base import BaseWatchdog
|
|
392
|
+
|
|
393
|
+
start_handlers = self.event_bus.handlers.get('BrowserStartEvent', [])
|
|
394
|
+
start_handler_names = [getattr(h, '__name__', str(h)) for h in start_handlers]
|
|
395
|
+
|
|
396
|
+
if any('on_BrowserStartEvent' in name for name in start_handler_names):
|
|
397
|
+
raise RuntimeError(
|
|
398
|
+
'[BrowserSession] Duplicate handler registration attempted! '
|
|
399
|
+
'on_BrowserStartEvent is already registered. '
|
|
400
|
+
'This likely means BrowserSession was initialized multiple times with the same EventBus.'
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
BaseWatchdog.attach_handler_to_session(self, BrowserStartEvent, self.on_BrowserStartEvent)
|
|
404
|
+
BaseWatchdog.attach_handler_to_session(self, BrowserStopEvent, self.on_BrowserStopEvent)
|
|
405
|
+
BaseWatchdog.attach_handler_to_session(self, NavigateToUrlEvent, self.on_NavigateToUrlEvent)
|
|
406
|
+
BaseWatchdog.attach_handler_to_session(self, SwitchTabEvent, self.on_SwitchTabEvent)
|
|
407
|
+
BaseWatchdog.attach_handler_to_session(self, TabCreatedEvent, self.on_TabCreatedEvent)
|
|
408
|
+
BaseWatchdog.attach_handler_to_session(self, TabClosedEvent, self.on_TabClosedEvent)
|
|
409
|
+
BaseWatchdog.attach_handler_to_session(self, AgentFocusChangedEvent, self.on_AgentFocusChangedEvent)
|
|
410
|
+
# BaseWatchdog.attach_handler_to_session(self, FileDownloadedEvent, self.on_FileDownloadedEvent)
|
|
411
|
+
BaseWatchdog.attach_handler_to_session(self, CloseTabEvent, self.on_CloseTabEvent)
|
|
412
|
+
|
|
387
413
|
async def attach_all_watchdogs(self) -> None:
|
|
388
414
|
"""Initialize and attach all watchdogs EXCEPT AboutBlankWatchdog to disable DVD animation."""
|
|
389
415
|
# Prevent duplicate watchdog attachment
|
|
@@ -6,6 +6,9 @@ import enum
|
|
|
6
6
|
import base64
|
|
7
7
|
import mimetypes
|
|
8
8
|
import datetime
|
|
9
|
+
import aiohttp
|
|
10
|
+
import re
|
|
11
|
+
import urllib.parse
|
|
9
12
|
from pathvalidate import sanitize_filename
|
|
10
13
|
from typing import Optional, Type, Callable, Dict, Any, Union, Awaitable, TypeVar
|
|
11
14
|
from pydantic import BaseModel
|
|
@@ -40,7 +43,7 @@ from browser_use.browser.views import BrowserError
|
|
|
40
43
|
from browser_use.mcp.client import MCPClient
|
|
41
44
|
|
|
42
45
|
from vibe_surf.browser.agent_browser_session import AgentBrowserSession
|
|
43
|
-
from vibe_surf.tools.views import HoverAction, ExtractionAction, FileExtractionAction
|
|
46
|
+
from vibe_surf.tools.views import HoverAction, ExtractionAction, FileExtractionAction, DownloadMediaAction
|
|
44
47
|
from vibe_surf.tools.mcp_client import CustomMCPClient
|
|
45
48
|
from vibe_surf.tools.file_system import CustomFileSystem
|
|
46
49
|
from vibe_surf.logger import get_logger
|
|
@@ -501,3 +504,167 @@ class BrowserUseTools(Tools, VibeSurfTools):
|
|
|
501
504
|
error_msg = f'❌ Failed to take screenshot: {str(e)}'
|
|
502
505
|
logger.error(error_msg)
|
|
503
506
|
return ActionResult(error=error_msg)
|
|
507
|
+
|
|
508
|
+
@self.registry.action(
|
|
509
|
+
'Download media from URL and save to filesystem downloads folder',
|
|
510
|
+
param_model=DownloadMediaAction
|
|
511
|
+
)
|
|
512
|
+
async def download_media(params: DownloadMediaAction, file_system: FileSystem):
|
|
513
|
+
"""Download media from URL with automatic file format detection"""
|
|
514
|
+
try:
|
|
515
|
+
# Get file system directory path (Path type)
|
|
516
|
+
fs_dir = file_system.get_dir()
|
|
517
|
+
|
|
518
|
+
# Create downloads directory if it doesn't exist
|
|
519
|
+
downloads_dir = fs_dir / "downloads"
|
|
520
|
+
downloads_dir.mkdir(exist_ok=True)
|
|
521
|
+
|
|
522
|
+
# Download the file and detect format
|
|
523
|
+
async with aiohttp.ClientSession() as session:
|
|
524
|
+
async with session.get(params.url) as response:
|
|
525
|
+
if response.status != 200:
|
|
526
|
+
raise Exception(f"HTTP {response.status}: Failed to download from {params.url}")
|
|
527
|
+
|
|
528
|
+
# Get content
|
|
529
|
+
content = await response.read()
|
|
530
|
+
|
|
531
|
+
# Detect file format and extension
|
|
532
|
+
file_extension = await self._detect_file_format(params.url, response.headers, content)
|
|
533
|
+
|
|
534
|
+
# Generate filename
|
|
535
|
+
if params.filename:
|
|
536
|
+
# Use provided filename, add extension if missing
|
|
537
|
+
filename = params.filename
|
|
538
|
+
if not filename.endswith(file_extension):
|
|
539
|
+
filename = f"{filename}{file_extension}"
|
|
540
|
+
else:
|
|
541
|
+
# Generate filename from URL or timestamp
|
|
542
|
+
url_path = urllib.parse.urlparse(params.url).path
|
|
543
|
+
url_filename = os.path.basename(url_path)
|
|
544
|
+
|
|
545
|
+
if url_filename and not url_filename.startswith('.'):
|
|
546
|
+
# Use URL filename, ensure correct extension
|
|
547
|
+
filename = url_filename
|
|
548
|
+
if not filename.endswith(file_extension):
|
|
549
|
+
base_name = os.path.splitext(filename)[0]
|
|
550
|
+
filename = f"{base_name}{file_extension}"
|
|
551
|
+
else:
|
|
552
|
+
# Generate timestamp-based filename
|
|
553
|
+
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
554
|
+
filename = f"media_{timestamp}{file_extension}"
|
|
555
|
+
|
|
556
|
+
# Sanitize filename
|
|
557
|
+
filename = sanitize_filename(filename)
|
|
558
|
+
filepath = downloads_dir / filename
|
|
559
|
+
|
|
560
|
+
# Save file
|
|
561
|
+
with open(filepath, "wb") as f:
|
|
562
|
+
f.write(content)
|
|
563
|
+
|
|
564
|
+
# Calculate file size for display
|
|
565
|
+
file_size = len(content)
|
|
566
|
+
size_str = self._format_file_size(file_size)
|
|
567
|
+
|
|
568
|
+
msg = f'📥 Downloaded media to: {str(filepath.relative_to(fs_dir))} ({size_str})'
|
|
569
|
+
logger.info(msg)
|
|
570
|
+
return ActionResult(
|
|
571
|
+
extracted_content=msg,
|
|
572
|
+
include_in_memory=True,
|
|
573
|
+
long_term_memory=f'Downloaded media from {params.url} to {str(filepath.relative_to(fs_dir))}',
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
except Exception as e:
|
|
577
|
+
error_msg = f'❌ Failed to download media: {str(e)}'
|
|
578
|
+
logger.error(error_msg)
|
|
579
|
+
return ActionResult(error=error_msg)
|
|
580
|
+
|
|
581
|
+
async def _detect_file_format(self, url: str, headers: dict, content: bytes) -> str:
|
|
582
|
+
"""Detect file format from URL, headers, and content"""
|
|
583
|
+
|
|
584
|
+
# Try Content-Type header first
|
|
585
|
+
content_type = headers.get('content-type', '').lower()
|
|
586
|
+
if content_type:
|
|
587
|
+
# Common image formats
|
|
588
|
+
if 'image/jpeg' in content_type or 'image/jpg' in content_type:
|
|
589
|
+
return '.jpg'
|
|
590
|
+
elif 'image/png' in content_type:
|
|
591
|
+
return '.png'
|
|
592
|
+
elif 'image/gif' in content_type:
|
|
593
|
+
return '.gif'
|
|
594
|
+
elif 'image/webp' in content_type:
|
|
595
|
+
return '.webp'
|
|
596
|
+
elif 'image/svg' in content_type:
|
|
597
|
+
return '.svg'
|
|
598
|
+
elif 'image/bmp' in content_type:
|
|
599
|
+
return '.bmp'
|
|
600
|
+
elif 'image/tiff' in content_type:
|
|
601
|
+
return '.tiff'
|
|
602
|
+
# Video formats
|
|
603
|
+
elif 'video/mp4' in content_type:
|
|
604
|
+
return '.mp4'
|
|
605
|
+
elif 'video/webm' in content_type:
|
|
606
|
+
return '.webm'
|
|
607
|
+
elif 'video/avi' in content_type:
|
|
608
|
+
return '.avi'
|
|
609
|
+
elif 'video/mov' in content_type or 'video/quicktime' in content_type:
|
|
610
|
+
return '.mov'
|
|
611
|
+
# Audio formats
|
|
612
|
+
elif 'audio/mpeg' in content_type or 'audio/mp3' in content_type:
|
|
613
|
+
return '.mp3'
|
|
614
|
+
elif 'audio/wav' in content_type:
|
|
615
|
+
return '.wav'
|
|
616
|
+
elif 'audio/ogg' in content_type:
|
|
617
|
+
return '.ogg'
|
|
618
|
+
elif 'audio/webm' in content_type:
|
|
619
|
+
return '.webm'
|
|
620
|
+
|
|
621
|
+
# Try magic number detection
|
|
622
|
+
if len(content) >= 8:
|
|
623
|
+
# JPEG
|
|
624
|
+
if content.startswith(b'\xff\xd8\xff'):
|
|
625
|
+
return '.jpg'
|
|
626
|
+
# PNG
|
|
627
|
+
elif content.startswith(b'\x89PNG\r\n\x1a\n'):
|
|
628
|
+
return '.png'
|
|
629
|
+
# GIF
|
|
630
|
+
elif content.startswith(b'GIF87a') or content.startswith(b'GIF89a'):
|
|
631
|
+
return '.gif'
|
|
632
|
+
# WebP
|
|
633
|
+
elif content[8:12] == b'WEBP':
|
|
634
|
+
return '.webp'
|
|
635
|
+
# BMP
|
|
636
|
+
elif content.startswith(b'BM'):
|
|
637
|
+
return '.bmp'
|
|
638
|
+
# TIFF
|
|
639
|
+
elif content.startswith(b'II*\x00') or content.startswith(b'MM\x00*'):
|
|
640
|
+
return '.tiff'
|
|
641
|
+
# MP4
|
|
642
|
+
elif b'ftyp' in content[4:12]:
|
|
643
|
+
return '.mp4'
|
|
644
|
+
# PDF
|
|
645
|
+
elif content.startswith(b'%PDF'):
|
|
646
|
+
return '.pdf'
|
|
647
|
+
|
|
648
|
+
# Try URL path extension
|
|
649
|
+
url_path = urllib.parse.urlparse(url).path
|
|
650
|
+
if url_path:
|
|
651
|
+
ext = os.path.splitext(url_path)[1].lower()
|
|
652
|
+
if ext in ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg', '.bmp', '.tiff',
|
|
653
|
+
'.mp4', '.webm', '.avi', '.mov', '.wmv', '.flv',
|
|
654
|
+
'.mp3', '.wav', '.ogg', '.aac', '.flac',
|
|
655
|
+
'.pdf', '.doc', '.docx', '.txt']:
|
|
656
|
+
return ext
|
|
657
|
+
|
|
658
|
+
# Default fallback
|
|
659
|
+
return '.bin'
|
|
660
|
+
|
|
661
|
+
def _format_file_size(self, size_bytes: int) -> str:
|
|
662
|
+
"""Format file size in human readable format"""
|
|
663
|
+
if size_bytes == 0:
|
|
664
|
+
return "0 B"
|
|
665
|
+
size_names = ["B", "KB", "MB", "GB", "TB"]
|
|
666
|
+
i = 0
|
|
667
|
+
while size_bytes >= 1024.0 and i < len(size_names) - 1:
|
|
668
|
+
size_bytes /= 1024.0
|
|
669
|
+
i += 1
|
|
670
|
+
return f"{size_bytes:.1f} {size_names[i]}"
|