vibesurf 0.1.27__py3-none-any.whl → 0.1.29__py3-none-any.whl
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.
- vibe_surf/_version.py +2 -2
- vibe_surf/backend/shared_state.py +2 -2
- vibe_surf/chrome_extension/config.js +8 -0
- vibe_surf/chrome_extension/scripts/ui-manager.js +146 -0
- vibe_surf/chrome_extension/sidepanel.html +2 -16
- vibe_surf/tools/website_api/__init__.py +0 -0
- vibe_surf/tools/website_api/douyin/__init__.py +0 -0
- vibe_surf/tools/website_api/douyin/client.py +845 -0
- vibe_surf/tools/website_api/douyin/helpers.py +239 -0
- vibe_surf/tools/website_api/weibo/__init__.py +0 -0
- vibe_surf/tools/website_api/weibo/client.py +846 -0
- vibe_surf/tools/website_api/weibo/helpers.py +997 -0
- vibe_surf/tools/website_api/xhs/__init__.py +0 -0
- vibe_surf/tools/website_api/xhs/client.py +807 -0
- vibe_surf/tools/website_api/xhs/helpers.py +301 -0
- vibe_surf/tools/website_api/youtube/__init__.py +32 -0
- vibe_surf/tools/website_api/youtube/client.py +1179 -0
- vibe_surf/tools/website_api/youtube/helpers.py +420 -0
- {vibesurf-0.1.27.dist-info → vibesurf-0.1.29.dist-info}/METADATA +55 -23
- {vibesurf-0.1.27.dist-info → vibesurf-0.1.29.dist-info}/RECORD +24 -11
- {vibesurf-0.1.27.dist-info → vibesurf-0.1.29.dist-info}/WHEEL +0 -0
- {vibesurf-0.1.27.dist-info → vibesurf-0.1.29.dist-info}/entry_points.txt +0 -0
- {vibesurf-0.1.27.dist-info → vibesurf-0.1.29.dist-info}/licenses/LICENSE +0 -0
- {vibesurf-0.1.27.dist-info → vibesurf-0.1.29.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import pdb
|
|
2
|
+
import random
|
|
3
|
+
import time
|
|
4
|
+
import json
|
|
5
|
+
import urllib.parse
|
|
6
|
+
from typing import Dict, List, Tuple, Optional
|
|
7
|
+
from enum import Enum
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SearchChannelType(Enum):
|
|
11
|
+
"""Search channel type constants"""
|
|
12
|
+
GENERAL = "aweme_general" # General content
|
|
13
|
+
VIDEO = "aweme_video_web" # Video content
|
|
14
|
+
USER = "aweme_user_web" # User content
|
|
15
|
+
LIVE = "aweme_live" # Live content
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SearchSortType(Enum):
|
|
19
|
+
"""Search sort type constants"""
|
|
20
|
+
GENERAL = 0 # General sorting
|
|
21
|
+
MOST_LIKED = 1 # Most liked
|
|
22
|
+
LATEST = 2 # Latest published
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PublishTimeType(Enum):
|
|
26
|
+
"""Publish time type constants"""
|
|
27
|
+
UNLIMITED = 0 # Unlimited
|
|
28
|
+
ONE_DAY = 1 # Within one day
|
|
29
|
+
ONE_WEEK = 7 # Within one week
|
|
30
|
+
SIX_MONTHS = 180 # Within six months
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def generate_web_id() -> str:
|
|
34
|
+
"""
|
|
35
|
+
Generate random webid for Douyin requests
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Random webid string
|
|
39
|
+
"""
|
|
40
|
+
def generate_part(t):
|
|
41
|
+
if t is not None:
|
|
42
|
+
return str(t ^ (int(16 * random.random()) >> (t // 4)))
|
|
43
|
+
else:
|
|
44
|
+
return ''.join([
|
|
45
|
+
str(int(1e7)), '-', str(int(1e3)), '-',
|
|
46
|
+
str(int(4e3)), '-', str(int(8e3)), '-', str(int(1e11))
|
|
47
|
+
])
|
|
48
|
+
|
|
49
|
+
web_id = ''.join(
|
|
50
|
+
generate_part(int(x)) if x in '018' else x for x in generate_part(None)
|
|
51
|
+
)
|
|
52
|
+
return web_id.replace('-', '')[:19]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def generate_trace_id() -> str:
|
|
56
|
+
"""Generate a random trace ID for requests"""
|
|
57
|
+
chars = "abcdef0123456789"
|
|
58
|
+
return ''.join(random.choices(chars, k=16))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def create_session_id() -> str:
|
|
62
|
+
"""Create a unique session identifier"""
|
|
63
|
+
timestamp = int(time.time() * 1000) << 64
|
|
64
|
+
rand_num = random.randint(0, 2147483646)
|
|
65
|
+
return encode_base36(timestamp + rand_num)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def encode_base36(number: int, alphabet: str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ') -> str:
|
|
69
|
+
"""Convert integer to base36 string"""
|
|
70
|
+
if not isinstance(number, int):
|
|
71
|
+
raise TypeError('Input must be an integer')
|
|
72
|
+
|
|
73
|
+
if number == 0:
|
|
74
|
+
return alphabet[0]
|
|
75
|
+
|
|
76
|
+
result = ''
|
|
77
|
+
sign = ''
|
|
78
|
+
|
|
79
|
+
if number < 0:
|
|
80
|
+
sign = '-'
|
|
81
|
+
number = -number
|
|
82
|
+
|
|
83
|
+
while number:
|
|
84
|
+
number, remainder = divmod(number, len(alphabet))
|
|
85
|
+
result = alphabet[remainder] + result
|
|
86
|
+
|
|
87
|
+
return sign + result
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def create_common_params() -> Dict[str, any]:
|
|
91
|
+
"""
|
|
92
|
+
Create common parameters for Douyin API requests
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Dictionary of common parameters
|
|
96
|
+
"""
|
|
97
|
+
return {
|
|
98
|
+
"device_platform": "webapp",
|
|
99
|
+
"aid": "6383",
|
|
100
|
+
"channel": "channel_pc_web",
|
|
101
|
+
"version_code": "190600",
|
|
102
|
+
"version_name": "19.6.0",
|
|
103
|
+
"update_version_code": "170400",
|
|
104
|
+
"pc_client_type": "1",
|
|
105
|
+
"cookie_enabled": "true",
|
|
106
|
+
"browser_language": "en-US",
|
|
107
|
+
"browser_platform": "MacIntel",
|
|
108
|
+
"browser_name": "Chrome",
|
|
109
|
+
"browser_version": "125.0.0.0",
|
|
110
|
+
"browser_online": "true",
|
|
111
|
+
"engine_name": "Blink",
|
|
112
|
+
"os_name": "Mac OS",
|
|
113
|
+
"os_version": "10.15.7",
|
|
114
|
+
"cpu_core_num": "8",
|
|
115
|
+
"device_memory": "8",
|
|
116
|
+
"engine_version": "109.0",
|
|
117
|
+
"platform": "PC",
|
|
118
|
+
"screen_width": "1920",
|
|
119
|
+
"screen_height": "1080",
|
|
120
|
+
"effective_type": "4g",
|
|
121
|
+
"round_trip_time": "50",
|
|
122
|
+
"webid": generate_web_id(),
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def extract_cookies_from_browser(web_cookies: List[Dict]) -> Tuple[str, Dict[str, str]]:
|
|
127
|
+
"""Extract and format cookies from browser, filtering only Douyin related cookies"""
|
|
128
|
+
cookie_dict = {}
|
|
129
|
+
cookie_parts = []
|
|
130
|
+
|
|
131
|
+
# Douyin domain patterns to filter
|
|
132
|
+
douyin_domains = [
|
|
133
|
+
'.douyin.com',
|
|
134
|
+
# 'www.douyin.com',
|
|
135
|
+
# 'sso.douyin.com'
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
for cookie in web_cookies:
|
|
139
|
+
if 'name' in cookie and 'value' in cookie and 'domain' in cookie:
|
|
140
|
+
domain = cookie['domain']
|
|
141
|
+
|
|
142
|
+
# Filter only Douyin related cookies
|
|
143
|
+
if any(douyin_domain in domain for douyin_domain in douyin_domains):
|
|
144
|
+
name = cookie['name']
|
|
145
|
+
value = cookie['value']
|
|
146
|
+
cookie_dict[name] = value
|
|
147
|
+
cookie_parts.append(f"{name}={value}")
|
|
148
|
+
cookie_string = "; ".join(cookie_parts)
|
|
149
|
+
return cookie_string, cookie_dict
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def create_referer_url(keyword: str = "", aweme_id: str = "") -> str:
|
|
153
|
+
"""
|
|
154
|
+
Create appropriate referer URL for Douyin requests
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
keyword: Search keyword if applicable
|
|
158
|
+
aweme_id: Aweme ID if applicable
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Referer URL string
|
|
162
|
+
"""
|
|
163
|
+
if keyword:
|
|
164
|
+
return f"https://www.douyin.com/search/{urllib.parse.quote(keyword)}"
|
|
165
|
+
elif aweme_id:
|
|
166
|
+
return f"https://www.douyin.com/video/{aweme_id}"
|
|
167
|
+
else:
|
|
168
|
+
return "https://www.douyin.com/"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def extract_aweme_media_urls(aweme_data: Dict) -> Dict[str, List[str]]:
|
|
172
|
+
"""
|
|
173
|
+
Extract media URLs from aweme data
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
aweme_data: Aweme item data
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
Dictionary containing image and video URLs
|
|
180
|
+
"""
|
|
181
|
+
result = {
|
|
182
|
+
"images": [],
|
|
183
|
+
"videos": [],
|
|
184
|
+
"cover": ""
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
# Extract images if available
|
|
189
|
+
if "images" in aweme_data:
|
|
190
|
+
for img in aweme_data["images"]:
|
|
191
|
+
if "url_list" in img and img["url_list"]:
|
|
192
|
+
result["images"].append(img["url_list"][0])
|
|
193
|
+
|
|
194
|
+
# Extract video URL
|
|
195
|
+
if "video" in aweme_data and "play_addr" in aweme_data["video"]:
|
|
196
|
+
play_addr = aweme_data["video"]["play_addr"]
|
|
197
|
+
if "url_list" in play_addr and play_addr["url_list"]:
|
|
198
|
+
result["videos"].append(play_addr["url_list"][0])
|
|
199
|
+
|
|
200
|
+
# Extract cover image
|
|
201
|
+
if "video" in aweme_data and "cover" in aweme_data["video"]:
|
|
202
|
+
cover_data = aweme_data["video"]["cover"]
|
|
203
|
+
if "url_list" in cover_data and cover_data["url_list"]:
|
|
204
|
+
result["cover"] = cover_data["url_list"][0]
|
|
205
|
+
|
|
206
|
+
except (KeyError, TypeError, IndexError) as e:
|
|
207
|
+
pass # Ignore extraction errors
|
|
208
|
+
|
|
209
|
+
return result
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class DouyinError(Exception):
|
|
213
|
+
"""Base exception for Douyin API errors"""
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class NetworkError(DouyinError):
|
|
218
|
+
"""Network connection error"""
|
|
219
|
+
pass
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class DataExtractionError(DouyinError):
|
|
223
|
+
"""Data extraction error"""
|
|
224
|
+
pass
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class AuthenticationError(DouyinError):
|
|
228
|
+
"""Authentication error"""
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class RateLimitError(DouyinError):
|
|
233
|
+
"""Rate limit exceeded error"""
|
|
234
|
+
pass
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class VerificationError(DouyinError):
|
|
238
|
+
"""Account verification required error"""
|
|
239
|
+
pass
|
|
File without changes
|