perplexity-webui-scraper 0.3.5__py3-none-any.whl → 0.3.6__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.
- perplexity_webui_scraper/__init__.py +3 -1
- perplexity_webui_scraper/cli/get_perplexity_session_token.py +24 -8
- perplexity_webui_scraper/config.py +6 -2
- perplexity_webui_scraper/constants.py +30 -10
- perplexity_webui_scraper/core.py +58 -13
- perplexity_webui_scraper/enums.py +63 -21
- perplexity_webui_scraper/exceptions.py +3 -1
- perplexity_webui_scraper/http.py +8 -3
- perplexity_webui_scraper/limits.py +12 -4
- perplexity_webui_scraper/logging.py +36 -14
- perplexity_webui_scraper/mcp/__init__.py +3 -1
- perplexity_webui_scraper/mcp/__main__.py +3 -1
- perplexity_webui_scraper/mcp/server.py +15 -30
- perplexity_webui_scraper/models.py +55 -19
- perplexity_webui_scraper/resilience.py +3 -1
- perplexity_webui_scraper/types.py +15 -5
- {perplexity_webui_scraper-0.3.5.dist-info → perplexity_webui_scraper-0.3.6.dist-info}/METADATA +3 -3
- perplexity_webui_scraper-0.3.6.dist-info/RECORD +21 -0
- {perplexity_webui_scraper-0.3.5.dist-info → perplexity_webui_scraper-0.3.6.dist-info}/WHEEL +1 -1
- perplexity_webui_scraper-0.3.5.dist-info/RECORD +0 -21
- {perplexity_webui_scraper-0.3.5.dist-info → perplexity_webui_scraper-0.3.6.dist-info}/entry_points.txt +0 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
CLI utility for secure Perplexity authentication and session extraction.
|
|
3
|
+
"""
|
|
2
4
|
|
|
3
5
|
from __future__ import annotations
|
|
4
6
|
|
|
@@ -57,7 +59,9 @@ def update_env(token: str) -> bool:
|
|
|
57
59
|
|
|
58
60
|
|
|
59
61
|
def _initialize_session() -> tuple[Session, str]:
|
|
60
|
-
"""
|
|
62
|
+
"""
|
|
63
|
+
Initialize session and obtain CSRF token.
|
|
64
|
+
"""
|
|
61
65
|
|
|
62
66
|
session = Session(impersonate="chrome", headers={"Referer": BASE_URL, "Origin": BASE_URL})
|
|
63
67
|
|
|
@@ -73,7 +77,9 @@ def _initialize_session() -> tuple[Session, str]:
|
|
|
73
77
|
|
|
74
78
|
|
|
75
79
|
def _request_verification_code(session: Session, csrf: str, email: str) -> None:
|
|
76
|
-
"""
|
|
80
|
+
"""
|
|
81
|
+
Send verification code to user's email.
|
|
82
|
+
"""
|
|
77
83
|
|
|
78
84
|
with console.status("[bold green]Sending verification code...", spinner="dots"):
|
|
79
85
|
r = session.post(
|
|
@@ -92,7 +98,9 @@ def _request_verification_code(session: Session, csrf: str, email: str) -> None:
|
|
|
92
98
|
|
|
93
99
|
|
|
94
100
|
def _validate_and_get_redirect_url(session: Session, email: str, user_input: str) -> str:
|
|
95
|
-
"""
|
|
101
|
+
"""
|
|
102
|
+
Validate user input (OTP or magic link) and return redirect URL.
|
|
103
|
+
"""
|
|
96
104
|
|
|
97
105
|
with console.status("[bold green]Validating...", spinner="dots"):
|
|
98
106
|
if user_input.startswith("http"):
|
|
@@ -120,7 +128,9 @@ def _validate_and_get_redirect_url(session: Session, email: str, user_input: str
|
|
|
120
128
|
|
|
121
129
|
|
|
122
130
|
def _extract_session_token(session: Session, redirect_url: str) -> str:
|
|
123
|
-
"""
|
|
131
|
+
"""
|
|
132
|
+
Extract session token from cookies after authentication.
|
|
133
|
+
"""
|
|
124
134
|
|
|
125
135
|
session.get(redirect_url)
|
|
126
136
|
token = session.cookies.get("__Secure-next-auth.session-token")
|
|
@@ -132,7 +142,9 @@ def _extract_session_token(session: Session, redirect_url: str) -> str:
|
|
|
132
142
|
|
|
133
143
|
|
|
134
144
|
def _display_and_save_token(token: str) -> None:
|
|
135
|
-
"""
|
|
145
|
+
"""
|
|
146
|
+
Display token and optionally save to .env file.
|
|
147
|
+
"""
|
|
136
148
|
|
|
137
149
|
console.print("\n[bold green]✅ Token generated successfully![/bold green]")
|
|
138
150
|
console.print(f"\n[bold white]Your session token:[/bold white]\n[green]{token}[/green]\n")
|
|
@@ -147,7 +159,9 @@ def _display_and_save_token(token: str) -> None:
|
|
|
147
159
|
|
|
148
160
|
|
|
149
161
|
def _show_header() -> None:
|
|
150
|
-
"""
|
|
162
|
+
"""
|
|
163
|
+
Display welcome header.
|
|
164
|
+
"""
|
|
151
165
|
|
|
152
166
|
console.print(
|
|
153
167
|
Panel(
|
|
@@ -161,7 +175,9 @@ def _show_header() -> None:
|
|
|
161
175
|
|
|
162
176
|
|
|
163
177
|
def _show_exit_message() -> None:
|
|
164
|
-
"""
|
|
178
|
+
"""
|
|
179
|
+
Display security note and wait for user to exit.
|
|
180
|
+
"""
|
|
165
181
|
|
|
166
182
|
console.print("\n[bold yellow]⚠️ Security Note:[/bold yellow]")
|
|
167
183
|
console.print("Press [bold white]ENTER[/bold white] to clear screen and exit.")
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
Configuration classes.
|
|
3
|
+
"""
|
|
2
4
|
|
|
3
5
|
from __future__ import annotations
|
|
4
6
|
|
|
@@ -17,7 +19,9 @@ if TYPE_CHECKING:
|
|
|
17
19
|
|
|
18
20
|
@dataclass(slots=True)
|
|
19
21
|
class ConversationConfig:
|
|
20
|
-
"""
|
|
22
|
+
"""
|
|
23
|
+
Default settings for a conversation. Can be overridden per message.
|
|
24
|
+
"""
|
|
21
25
|
|
|
22
26
|
model: Model | None = None
|
|
23
27
|
citation_mode: CitationMode = CitationMode.CLEAN
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
Constants and values for the Perplexity internal API and HTTP interactions.
|
|
3
|
+
"""
|
|
2
4
|
|
|
3
5
|
from __future__ import annotations
|
|
4
6
|
|
|
@@ -8,20 +10,30 @@ from typing import Final
|
|
|
8
10
|
|
|
9
11
|
# API Configuration
|
|
10
12
|
API_VERSION: Final[str] = "2.18"
|
|
11
|
-
"""
|
|
13
|
+
"""
|
|
14
|
+
Current API version used by Perplexity WebUI.
|
|
15
|
+
"""
|
|
12
16
|
|
|
13
17
|
API_BASE_URL: Final[str] = "https://www.perplexity.ai"
|
|
14
|
-
"""
|
|
18
|
+
"""
|
|
19
|
+
Base URL for all API requests.
|
|
20
|
+
"""
|
|
15
21
|
|
|
16
22
|
# API Endpoints
|
|
17
23
|
ENDPOINT_ASK: Final[str] = "/rest/sse/perplexity_ask"
|
|
18
|
-
"""
|
|
24
|
+
"""
|
|
25
|
+
SSE endpoint for sending prompts.
|
|
26
|
+
"""
|
|
19
27
|
|
|
20
28
|
ENDPOINT_SEARCH_INIT: Final[str] = "/search/new"
|
|
21
|
-
"""
|
|
29
|
+
"""
|
|
30
|
+
Endpoint to initialize a search session.
|
|
31
|
+
"""
|
|
22
32
|
|
|
23
33
|
ENDPOINT_UPLOAD: Final[str] = "/rest/uploads/batch_create_upload_urls"
|
|
24
|
-
"""
|
|
34
|
+
"""
|
|
35
|
+
Endpoint for file upload URL generation.
|
|
36
|
+
"""
|
|
25
37
|
|
|
26
38
|
# API Fixed Parameters
|
|
27
39
|
SEND_BACK_TEXT: Final[bool] = True
|
|
@@ -33,10 +45,14 @@ False = API sends delta chunks only (accumulate mode).
|
|
|
33
45
|
"""
|
|
34
46
|
|
|
35
47
|
USE_SCHEMATIZED_API: Final[bool] = False
|
|
36
|
-
"""
|
|
48
|
+
"""
|
|
49
|
+
Whether to use the schematized API format.
|
|
50
|
+
"""
|
|
37
51
|
|
|
38
52
|
PROMPT_SOURCE: Final[str] = "user"
|
|
39
|
-
"""
|
|
53
|
+
"""
|
|
54
|
+
Source identifier for prompts.
|
|
55
|
+
"""
|
|
40
56
|
|
|
41
57
|
# Regex Patterns (Pre-compiled for performance in streaming parsing)
|
|
42
58
|
CITATION_PATTERN: Final[Pattern[str]] = compile(r"\[(\d{1,2})\]")
|
|
@@ -47,7 +63,9 @@ Uses word boundary to avoid matching things like [123].
|
|
|
47
63
|
"""
|
|
48
64
|
|
|
49
65
|
JSON_OBJECT_PATTERN: Final[Pattern[str]] = compile(r"^\{.*\}$")
|
|
50
|
-
"""
|
|
66
|
+
"""
|
|
67
|
+
Pattern to detect JSON object strings.
|
|
68
|
+
"""
|
|
51
69
|
|
|
52
70
|
# HTTP Headers
|
|
53
71
|
DEFAULT_HEADERS: Final[dict[str, str]] = {
|
|
@@ -61,4 +79,6 @@ Referer and Origin are added dynamically based on BASE_URL.
|
|
|
61
79
|
"""
|
|
62
80
|
|
|
63
81
|
SESSION_COOKIE_NAME: Final[str] = "__Secure-next-auth.session-token"
|
|
64
|
-
"""
|
|
82
|
+
"""
|
|
83
|
+
Name of the session cookie used for authentication.
|
|
84
|
+
"""
|
perplexity_webui_scraper/core.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
Core client implementation.
|
|
3
|
+
"""
|
|
2
4
|
|
|
3
5
|
from __future__ import annotations
|
|
4
6
|
|
|
@@ -8,6 +10,8 @@ from pathlib import Path
|
|
|
8
10
|
from typing import TYPE_CHECKING, Any
|
|
9
11
|
from uuid import uuid4
|
|
10
12
|
|
|
13
|
+
from curl_cffi import CurlMime
|
|
14
|
+
from curl_cffi.requests import Session
|
|
11
15
|
from orjson import JSONDecodeError, loads
|
|
12
16
|
|
|
13
17
|
|
|
@@ -376,16 +380,55 @@ class Conversation:
|
|
|
376
380
|
try:
|
|
377
381
|
response = self._http.post(ENDPOINT_UPLOAD, json=json_data)
|
|
378
382
|
response_data = response.json()
|
|
379
|
-
|
|
383
|
+
result = response_data.get("results", {}).get(file_uuid, {})
|
|
380
384
|
|
|
381
|
-
|
|
385
|
+
s3_bucket_url = result.get("s3_bucket_url")
|
|
386
|
+
s3_object_url = result.get("s3_object_url")
|
|
387
|
+
fields = result.get("fields", {})
|
|
388
|
+
|
|
389
|
+
if not s3_object_url:
|
|
382
390
|
raise FileUploadError(file_info.path, "No upload URL returned")
|
|
383
391
|
|
|
384
|
-
|
|
392
|
+
if not s3_bucket_url or not fields:
|
|
393
|
+
raise FileUploadError(file_info.path, "Missing S3 upload credentials")
|
|
394
|
+
|
|
395
|
+
# Upload the file to S3 using presigned POST
|
|
396
|
+
file_path = Path(file_info.path)
|
|
397
|
+
|
|
398
|
+
with file_path.open("rb") as f:
|
|
399
|
+
file_content = f.read()
|
|
400
|
+
|
|
401
|
+
# Build multipart form data using CurlMime
|
|
402
|
+
# For S3 presigned POST, form fields must come before the file
|
|
403
|
+
mime = CurlMime()
|
|
404
|
+
|
|
405
|
+
for field_name, field_value in fields.items():
|
|
406
|
+
mime.addpart(name=field_name, data=field_value)
|
|
407
|
+
|
|
408
|
+
mime.addpart(
|
|
409
|
+
name="file",
|
|
410
|
+
content_type=file_info.mimetype,
|
|
411
|
+
filename=file_path.name,
|
|
412
|
+
data=file_content,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# S3 requires a clean session
|
|
416
|
+
with Session() as s3_session:
|
|
417
|
+
upload_response = s3_session.post(s3_bucket_url, multipart=mime)
|
|
418
|
+
|
|
419
|
+
mime.close()
|
|
420
|
+
|
|
421
|
+
if upload_response.status_code not in (200, 201, 204):
|
|
422
|
+
raise FileUploadError(
|
|
423
|
+
file_info.path,
|
|
424
|
+
f"S3 upload failed with status {upload_response.status_code}: {upload_response.text}",
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
return s3_object_url
|
|
385
428
|
except FileUploadError as error:
|
|
386
429
|
raise error
|
|
387
|
-
except Exception as
|
|
388
|
-
raise FileUploadError(file_info.path, str(
|
|
430
|
+
except Exception as error:
|
|
431
|
+
raise FileUploadError(file_info.path, str(error)) from error
|
|
389
432
|
|
|
390
433
|
def _build_payload(
|
|
391
434
|
self,
|
|
@@ -460,9 +503,10 @@ class Conversation:
|
|
|
460
503
|
return CITATION_PATTERN.sub(replacer, text)
|
|
461
504
|
|
|
462
505
|
def _parse_line(self, line: str | bytes) -> dict[str, Any] | None:
|
|
463
|
-
|
|
506
|
+
if isinstance(line, bytes) and line.startswith(b"data: "):
|
|
507
|
+
return loads(line[6:])
|
|
464
508
|
|
|
465
|
-
if
|
|
509
|
+
if isinstance(line, str) and line.startswith("data: "):
|
|
466
510
|
return loads(line[6:])
|
|
467
511
|
|
|
468
512
|
return None
|
|
@@ -493,10 +537,10 @@ class Conversation:
|
|
|
493
537
|
|
|
494
538
|
try:
|
|
495
539
|
json_data = loads(data["text"])
|
|
496
|
-
except KeyError as
|
|
497
|
-
raise ValueError("Missing 'text' field in data") from
|
|
498
|
-
except JSONDecodeError as
|
|
499
|
-
raise ValueError("Invalid JSON in 'text' field") from
|
|
540
|
+
except KeyError as error:
|
|
541
|
+
raise ValueError("Missing 'text' field in data") from error
|
|
542
|
+
except JSONDecodeError as error:
|
|
543
|
+
raise ValueError("Invalid JSON in 'text' field") from error
|
|
500
544
|
|
|
501
545
|
answer_data: dict[str, Any] = {}
|
|
502
546
|
|
|
@@ -583,7 +627,8 @@ class Conversation:
|
|
|
583
627
|
chunks = answer_data.get("chunks", [])
|
|
584
628
|
|
|
585
629
|
if chunks:
|
|
586
|
-
|
|
630
|
+
formatted = [self._format_citations(chunk) for chunk in chunks if chunk is not None]
|
|
631
|
+
self._chunks = [c for c in formatted if c is not None]
|
|
587
632
|
|
|
588
633
|
self._raw_data = answer_data
|
|
589
634
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
Enums for Perplexity WebUI Scraper configuration options.
|
|
3
|
+
"""
|
|
2
4
|
|
|
3
5
|
from __future__ import annotations
|
|
4
6
|
|
|
@@ -13,13 +15,19 @@ class CitationMode(str, Enum):
|
|
|
13
15
|
"""
|
|
14
16
|
|
|
15
17
|
DEFAULT = "default"
|
|
16
|
-
"""
|
|
18
|
+
"""
|
|
19
|
+
Keep original Perplexity citation format (e.g., 'This is a citation[1]').
|
|
20
|
+
"""
|
|
17
21
|
|
|
18
22
|
MARKDOWN = "markdown"
|
|
19
|
-
"""
|
|
23
|
+
"""
|
|
24
|
+
Convert citations to markdown links (e.g., 'This is a citation[1](https://example.com)').
|
|
25
|
+
"""
|
|
20
26
|
|
|
21
27
|
CLEAN = "clean"
|
|
22
|
-
"""
|
|
28
|
+
"""
|
|
29
|
+
Remove all citation markers (e.g., 'This is a citation').
|
|
30
|
+
"""
|
|
23
31
|
|
|
24
32
|
|
|
25
33
|
class SearchFocus(str, Enum):
|
|
@@ -30,10 +38,14 @@ class SearchFocus(str, Enum):
|
|
|
30
38
|
"""
|
|
31
39
|
|
|
32
40
|
WEB = "internet"
|
|
33
|
-
"""
|
|
41
|
+
"""
|
|
42
|
+
Search the web for information. Best for factual queries and research.
|
|
43
|
+
"""
|
|
34
44
|
|
|
35
45
|
WRITING = "writing"
|
|
36
|
-
"""
|
|
46
|
+
"""
|
|
47
|
+
Focus on writing tasks. Best for creative writing, editing, and text generation.
|
|
48
|
+
"""
|
|
37
49
|
|
|
38
50
|
|
|
39
51
|
class SourceFocus(str, Enum):
|
|
@@ -44,16 +56,24 @@ class SourceFocus(str, Enum):
|
|
|
44
56
|
"""
|
|
45
57
|
|
|
46
58
|
WEB = "web"
|
|
47
|
-
"""
|
|
59
|
+
"""
|
|
60
|
+
Search across the entire internet. General web search.
|
|
61
|
+
"""
|
|
48
62
|
|
|
49
63
|
ACADEMIC = "scholar"
|
|
50
|
-
"""
|
|
64
|
+
"""
|
|
65
|
+
Search academic papers and scholarly articles (Google Scholar, etc.).
|
|
66
|
+
"""
|
|
51
67
|
|
|
52
68
|
SOCIAL = "social"
|
|
53
|
-
"""
|
|
69
|
+
"""
|
|
70
|
+
Search social media for discussions and opinions (Reddit, Twitter, etc.).
|
|
71
|
+
"""
|
|
54
72
|
|
|
55
73
|
FINANCE = "edgar"
|
|
56
|
-
"""
|
|
74
|
+
"""
|
|
75
|
+
Search SEC EDGAR filings for financial and corporate documents.
|
|
76
|
+
"""
|
|
57
77
|
|
|
58
78
|
|
|
59
79
|
class TimeRange(str, Enum):
|
|
@@ -64,19 +84,29 @@ class TimeRange(str, Enum):
|
|
|
64
84
|
"""
|
|
65
85
|
|
|
66
86
|
ALL = ""
|
|
67
|
-
"""
|
|
87
|
+
"""
|
|
88
|
+
Include sources from all time. No time restriction.
|
|
89
|
+
"""
|
|
68
90
|
|
|
69
91
|
TODAY = "DAY"
|
|
70
|
-
"""
|
|
92
|
+
"""
|
|
93
|
+
Include only sources from today (last 24 hours).
|
|
94
|
+
"""
|
|
71
95
|
|
|
72
96
|
LAST_WEEK = "WEEK"
|
|
73
|
-
"""
|
|
97
|
+
"""
|
|
98
|
+
Include sources from the last 7 days.
|
|
99
|
+
"""
|
|
74
100
|
|
|
75
101
|
LAST_MONTH = "MONTH"
|
|
76
|
-
"""
|
|
102
|
+
"""
|
|
103
|
+
Include sources from the last 30 days.
|
|
104
|
+
"""
|
|
77
105
|
|
|
78
106
|
LAST_YEAR = "YEAR"
|
|
79
|
-
"""
|
|
107
|
+
"""
|
|
108
|
+
Include sources from the last 365 days.
|
|
109
|
+
"""
|
|
80
110
|
|
|
81
111
|
|
|
82
112
|
class LogLevel(str, Enum):
|
|
@@ -87,19 +117,31 @@ class LogLevel(str, Enum):
|
|
|
87
117
|
"""
|
|
88
118
|
|
|
89
119
|
DISABLED = "DISABLED"
|
|
90
|
-
"""
|
|
120
|
+
"""
|
|
121
|
+
Completely disable all logging output. This is the default.
|
|
122
|
+
"""
|
|
91
123
|
|
|
92
124
|
DEBUG = "DEBUG"
|
|
93
|
-
"""
|
|
125
|
+
"""
|
|
126
|
+
Show all messages including internal debug information.
|
|
127
|
+
"""
|
|
94
128
|
|
|
95
129
|
INFO = "INFO"
|
|
96
|
-
"""
|
|
130
|
+
"""
|
|
131
|
+
Show informational messages, warnings, and errors.
|
|
132
|
+
"""
|
|
97
133
|
|
|
98
134
|
WARNING = "WARNING"
|
|
99
|
-
"""
|
|
135
|
+
"""
|
|
136
|
+
Show only warnings and errors.
|
|
137
|
+
"""
|
|
100
138
|
|
|
101
139
|
ERROR = "ERROR"
|
|
102
|
-
"""
|
|
140
|
+
"""
|
|
141
|
+
Show only error messages.
|
|
142
|
+
"""
|
|
103
143
|
|
|
104
144
|
CRITICAL = "CRITICAL"
|
|
105
|
-
"""
|
|
145
|
+
"""
|
|
146
|
+
Show only critical/fatal errors.
|
|
147
|
+
"""
|
perplexity_webui_scraper/http.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
HTTP client wrapper for Perplexity API requests.
|
|
3
|
+
"""
|
|
2
4
|
|
|
3
5
|
from __future__ import annotations
|
|
4
6
|
|
|
@@ -177,7 +179,9 @@ class HTTPClient:
|
|
|
177
179
|
logger.debug(f"Browser fingerprint rotated successfully | new_profile={new_profile}")
|
|
178
180
|
|
|
179
181
|
def _on_retry(self, retry_state: RetryCallState) -> None:
|
|
180
|
-
"""
|
|
182
|
+
"""
|
|
183
|
+
Callback executed before each retry attempt.
|
|
184
|
+
"""
|
|
181
185
|
|
|
182
186
|
attempt = retry_state.attempt_number
|
|
183
187
|
exception = retry_state.outcome.exception() if retry_state.outcome else None
|
|
@@ -257,7 +261,7 @@ class HTTPClient:
|
|
|
257
261
|
logger.debug(f"Error has response | status_code={status_code}")
|
|
258
262
|
|
|
259
263
|
# Check for Cloudflare before handling as regular 403
|
|
260
|
-
if is_cloudflare_status(status_code):
|
|
264
|
+
if status_code is not None and is_cloudflare_status(status_code):
|
|
261
265
|
logger.debug(f"Checking if error is Cloudflare challenge | status_code={status_code}")
|
|
262
266
|
|
|
263
267
|
try:
|
|
@@ -431,6 +435,7 @@ class HTTPClient:
|
|
|
431
435
|
response.raise_for_status()
|
|
432
436
|
|
|
433
437
|
logger.debug(f"POST request successful | endpoint={endpoint}")
|
|
438
|
+
|
|
434
439
|
return response
|
|
435
440
|
except Exception as error:
|
|
436
441
|
elapsed_ms = (monotonic() - request_start) * 1000
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
Upload and request limits for Perplexity WebUI Scraper.
|
|
3
|
+
"""
|
|
2
4
|
|
|
3
5
|
from __future__ import annotations
|
|
4
6
|
|
|
@@ -7,11 +9,17 @@ from typing import Final
|
|
|
7
9
|
|
|
8
10
|
# File Upload Limits
|
|
9
11
|
MAX_FILES: Final[int] = 30
|
|
10
|
-
"""
|
|
12
|
+
"""
|
|
13
|
+
Maximum number of files that can be attached to a single prompt.
|
|
14
|
+
"""
|
|
11
15
|
|
|
12
16
|
MAX_FILE_SIZE: Final[int] = 50 * 1024 * 1024 # 50 MB in bytes
|
|
13
|
-
"""
|
|
17
|
+
"""
|
|
18
|
+
Maximum file size in bytes.
|
|
19
|
+
"""
|
|
14
20
|
|
|
15
21
|
# Request Limits
|
|
16
22
|
DEFAULT_TIMEOUT: Final[int] = 30 * 60 # 30 minutes in seconds
|
|
17
|
-
"""
|
|
23
|
+
"""
|
|
24
|
+
Default request timeout in seconds.
|
|
25
|
+
"""
|
|
@@ -120,7 +120,9 @@ def log_request(
|
|
|
120
120
|
headers: dict[str, str] | None = None,
|
|
121
121
|
body_size: int | None = None,
|
|
122
122
|
) -> None:
|
|
123
|
-
"""
|
|
123
|
+
"""
|
|
124
|
+
Log an outgoing HTTP request with full details.
|
|
125
|
+
"""
|
|
124
126
|
|
|
125
127
|
logger.debug(
|
|
126
128
|
"HTTP request initiated | method={method} url={url} params={params} "
|
|
@@ -142,7 +144,9 @@ def log_response(
|
|
|
142
144
|
content_length: int | None = None,
|
|
143
145
|
headers: dict[str, str] | None = None,
|
|
144
146
|
) -> None:
|
|
145
|
-
"""
|
|
147
|
+
"""
|
|
148
|
+
Log an HTTP response with full details.
|
|
149
|
+
"""
|
|
146
150
|
|
|
147
151
|
level = "DEBUG" if status_code < 400 else "WARNING"
|
|
148
152
|
logger.log(
|
|
@@ -160,24 +164,28 @@ def log_response(
|
|
|
160
164
|
def log_retry(
|
|
161
165
|
attempt: int,
|
|
162
166
|
max_attempts: int,
|
|
163
|
-
exception:
|
|
167
|
+
exception: BaseException | None,
|
|
164
168
|
wait_seconds: float,
|
|
165
169
|
) -> None:
|
|
166
|
-
"""
|
|
170
|
+
"""
|
|
171
|
+
Log a retry attempt.
|
|
172
|
+
"""
|
|
167
173
|
|
|
168
174
|
logger.warning(
|
|
169
175
|
"Retry attempt | attempt={attempt}/{max_attempts} exception={exception_type}: {exception_msg} "
|
|
170
176
|
"wait_seconds={wait_seconds:.2f}",
|
|
171
177
|
attempt=attempt,
|
|
172
178
|
max_attempts=max_attempts,
|
|
173
|
-
exception_type=type(exception).__name__,
|
|
174
|
-
exception_msg=str(exception),
|
|
179
|
+
exception_type=type(exception).__name__ if exception else "None",
|
|
180
|
+
exception_msg=str(exception) if exception else "None",
|
|
175
181
|
wait_seconds=wait_seconds,
|
|
176
182
|
)
|
|
177
183
|
|
|
178
184
|
|
|
179
185
|
def log_cloudflare_detected(status_code: int, markers_found: list[str]) -> None:
|
|
180
|
-
"""
|
|
186
|
+
"""
|
|
187
|
+
Log Cloudflare challenge detection.
|
|
188
|
+
"""
|
|
181
189
|
|
|
182
190
|
logger.warning(
|
|
183
191
|
"Cloudflare challenge detected | status_code={status_code} markers={markers}",
|
|
@@ -187,7 +195,9 @@ def log_cloudflare_detected(status_code: int, markers_found: list[str]) -> None:
|
|
|
187
195
|
|
|
188
196
|
|
|
189
197
|
def log_fingerprint_rotation(old_profile: str, new_profile: str) -> None:
|
|
190
|
-
"""
|
|
198
|
+
"""
|
|
199
|
+
Log browser fingerprint rotation.
|
|
200
|
+
"""
|
|
191
201
|
|
|
192
202
|
logger.info(
|
|
193
203
|
"Browser fingerprint rotated | old_profile={old} new_profile={new}",
|
|
@@ -197,7 +207,9 @@ def log_fingerprint_rotation(old_profile: str, new_profile: str) -> None:
|
|
|
197
207
|
|
|
198
208
|
|
|
199
209
|
def log_rate_limit(wait_seconds: float) -> None:
|
|
200
|
-
"""
|
|
210
|
+
"""
|
|
211
|
+
Log rate limiting wait.
|
|
212
|
+
"""
|
|
201
213
|
|
|
202
214
|
logger.debug(
|
|
203
215
|
"Rate limiter throttling | wait_seconds={wait_seconds:.3f}",
|
|
@@ -206,7 +218,9 @@ def log_rate_limit(wait_seconds: float) -> None:
|
|
|
206
218
|
|
|
207
219
|
|
|
208
220
|
def log_session_created(impersonate: str, timeout: int) -> None:
|
|
209
|
-
"""
|
|
221
|
+
"""
|
|
222
|
+
Log HTTP session creation.
|
|
223
|
+
"""
|
|
210
224
|
|
|
211
225
|
logger.info(
|
|
212
226
|
"HTTP session created | browser_profile={profile} timeout={timeout}s",
|
|
@@ -216,7 +230,9 @@ def log_session_created(impersonate: str, timeout: int) -> None:
|
|
|
216
230
|
|
|
217
231
|
|
|
218
232
|
def log_conversation_created(config_summary: str) -> None:
|
|
219
|
-
"""
|
|
233
|
+
"""
|
|
234
|
+
Log conversation creation.
|
|
235
|
+
"""
|
|
220
236
|
|
|
221
237
|
logger.info(
|
|
222
238
|
"Conversation created | config={config}",
|
|
@@ -225,7 +241,9 @@ def log_conversation_created(config_summary: str) -> None:
|
|
|
225
241
|
|
|
226
242
|
|
|
227
243
|
def log_query_sent(query: str, model: str, has_files: bool) -> None:
|
|
228
|
-
"""
|
|
244
|
+
"""
|
|
245
|
+
Log a query being sent.
|
|
246
|
+
"""
|
|
229
247
|
|
|
230
248
|
logger.info(
|
|
231
249
|
"Query sent | model={model} has_files={has_files} query_preview={query_preview}",
|
|
@@ -236,7 +254,9 @@ def log_query_sent(query: str, model: str, has_files: bool) -> None:
|
|
|
236
254
|
|
|
237
255
|
|
|
238
256
|
def log_stream_chunk(chunk_size: int, is_final: bool) -> None:
|
|
239
|
-
"""
|
|
257
|
+
"""
|
|
258
|
+
Log a streaming chunk received.
|
|
259
|
+
"""
|
|
240
260
|
|
|
241
261
|
logger.debug(
|
|
242
262
|
"Stream chunk received | size={size} is_final={is_final}",
|
|
@@ -246,7 +266,9 @@ def log_stream_chunk(chunk_size: int, is_final: bool) -> None:
|
|
|
246
266
|
|
|
247
267
|
|
|
248
268
|
def log_error(error: Exception, context: str = "") -> None:
|
|
249
|
-
"""
|
|
269
|
+
"""
|
|
270
|
+
Log an error with full traceback.
|
|
271
|
+
"""
|
|
250
272
|
|
|
251
273
|
logger.exception(
|
|
252
274
|
"Error occurred | context={context} error_type={error_type} message={message}",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
MCP server implementation using FastMCP.
|
|
3
|
+
"""
|
|
2
4
|
|
|
3
5
|
from __future__ import annotations
|
|
4
6
|
|
|
@@ -45,7 +47,6 @@ MODEL_MAP = {
|
|
|
45
47
|
"kimi_thinking": Models.KIMI_K2_THINKING,
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
# Available model names for type hints
|
|
49
50
|
ModelName = Literal[
|
|
50
51
|
"best",
|
|
51
52
|
"research",
|
|
@@ -81,7 +82,9 @@ _client: Perplexity | None = None
|
|
|
81
82
|
|
|
82
83
|
|
|
83
84
|
def _get_client() -> Perplexity:
|
|
84
|
-
"""
|
|
85
|
+
"""
|
|
86
|
+
Get or create Perplexity client.
|
|
87
|
+
"""
|
|
85
88
|
|
|
86
89
|
global _client # noqa: PLW0603
|
|
87
90
|
if _client is None:
|
|
@@ -108,35 +111,15 @@ def perplexity_ask(
|
|
|
108
111
|
|
|
109
112
|
Returns up-to-date information from web sources. Use for factual queries, research,
|
|
110
113
|
current events, news, library versions, documentation, or any question requiring
|
|
114
|
+
the latest information.
|
|
111
115
|
|
|
112
116
|
Args:
|
|
113
|
-
query: The
|
|
114
|
-
model: AI model to use.
|
|
115
|
-
|
|
116
|
-
- "research": Fast and thorough for routine research
|
|
117
|
-
- "labs": Multi-step tasks with advanced troubleshooting
|
|
118
|
-
- "sonar": Perplexity's fast built-in model
|
|
119
|
-
- "gpt52": OpenAI's GPT-5.2
|
|
120
|
-
- "gpt52_thinking": GPT-5.2 with reasoning
|
|
121
|
-
- "claude_opus": Anthropic's Claude Opus 4.5
|
|
122
|
-
- "claude_opus_thinking": Claude Opus with reasoning
|
|
123
|
-
- "claude_sonnet": Anthropic's Claude Sonnet 4.5
|
|
124
|
-
- "claude_sonnet_thinking": Claude Sonnet with reasoning
|
|
125
|
-
- "gemini_pro": Google's Gemini 3 Pro
|
|
126
|
-
- "gemini_flash": Google's Gemini 3 Flash
|
|
127
|
-
- "gemini_flash_thinking": Gemini Flash with reasoning
|
|
128
|
-
- "grok": xAI's Grok 4.1
|
|
129
|
-
- "grok_thinking": Grok with reasoning
|
|
130
|
-
- "kimi_thinking": Moonshot's Kimi K2 with reasoning
|
|
131
|
-
source_focus: Type of sources to prioritize:
|
|
132
|
-
- "web": General web search (default)
|
|
133
|
-
- "academic": Scholarly articles and papers
|
|
134
|
-
- "social": Social media (Reddit, Twitter)
|
|
135
|
-
- "finance": SEC EDGAR financial filings
|
|
136
|
-
- "all": Combine web, academic, and social sources
|
|
117
|
+
query: The question to ask.
|
|
118
|
+
model: AI model to use.
|
|
119
|
+
source_focus: Type of sources to prioritize (web, academic, social, finance, all).
|
|
137
120
|
|
|
138
121
|
Returns:
|
|
139
|
-
AI-generated answer with inline citations
|
|
122
|
+
AI-generated answer with inline citations and a Citations section.
|
|
140
123
|
"""
|
|
141
124
|
|
|
142
125
|
client = _get_client()
|
|
@@ -168,11 +151,13 @@ def perplexity_ask(
|
|
|
168
151
|
|
|
169
152
|
return "".join(response_parts)
|
|
170
153
|
except Exception as error:
|
|
171
|
-
return f"Error
|
|
154
|
+
return f"Error: {error!s}"
|
|
172
155
|
|
|
173
156
|
|
|
174
157
|
def main() -> None:
|
|
175
|
-
"""
|
|
158
|
+
"""
|
|
159
|
+
Run the MCP server.
|
|
160
|
+
"""
|
|
176
161
|
|
|
177
162
|
mcp.run()
|
|
178
163
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
AI model definitions for Perplexity WebUI Scraper.
|
|
3
|
+
"""
|
|
2
4
|
|
|
3
5
|
from __future__ import annotations
|
|
4
6
|
|
|
@@ -7,7 +9,8 @@ from dataclasses import dataclass
|
|
|
7
9
|
|
|
8
10
|
@dataclass(frozen=True, slots=True)
|
|
9
11
|
class Model:
|
|
10
|
-
"""
|
|
12
|
+
"""
|
|
13
|
+
AI model configuration.
|
|
11
14
|
|
|
12
15
|
Attributes:
|
|
13
16
|
identifier: Model identifier used by the API.
|
|
@@ -19,55 +22,88 @@ class Model:
|
|
|
19
22
|
|
|
20
23
|
|
|
21
24
|
class Models:
|
|
22
|
-
"""
|
|
25
|
+
"""
|
|
26
|
+
Available AI models with their configurations.
|
|
23
27
|
|
|
24
28
|
All models use the "copilot" mode which enables web search.
|
|
25
29
|
"""
|
|
26
30
|
|
|
27
31
|
RESEARCH = Model(identifier="pplx_alpha")
|
|
28
|
-
"""
|
|
32
|
+
"""
|
|
33
|
+
Research - Fast and thorough for routine research.
|
|
34
|
+
"""
|
|
29
35
|
|
|
30
36
|
LABS = Model(identifier="pplx_beta")
|
|
31
|
-
"""
|
|
37
|
+
"""
|
|
38
|
+
Labs - Multi-step tasks with advanced troubleshooting.
|
|
39
|
+
"""
|
|
32
40
|
|
|
33
41
|
BEST = Model(identifier="pplx_pro_upgraded")
|
|
34
|
-
"""
|
|
42
|
+
"""
|
|
43
|
+
Best - Automatically selects the most responsive model based on the query.
|
|
44
|
+
"""
|
|
35
45
|
|
|
36
46
|
SONAR = Model(identifier="experimental")
|
|
37
|
-
"""
|
|
47
|
+
"""
|
|
48
|
+
Sonar - Perplexity's fast model.
|
|
49
|
+
"""
|
|
38
50
|
|
|
39
51
|
GPT_52 = Model(identifier="gpt52")
|
|
40
|
-
"""
|
|
52
|
+
"""
|
|
53
|
+
GPT-5.2 - OpenAI's latest model.
|
|
54
|
+
"""
|
|
41
55
|
|
|
42
56
|
GPT_52_THINKING = Model(identifier="gpt52_thinking")
|
|
43
|
-
"""
|
|
57
|
+
"""
|
|
58
|
+
GPT-5.2 Thinking - OpenAI's latest model with thinking.
|
|
59
|
+
"""
|
|
44
60
|
|
|
45
61
|
CLAUDE_45_OPUS = Model(identifier="claude45opus")
|
|
46
|
-
"""
|
|
62
|
+
"""
|
|
63
|
+
Claude Opus 4.5 - Anthropic's Opus reasoning model.
|
|
64
|
+
"""
|
|
47
65
|
|
|
48
66
|
CLAUDE_45_OPUS_THINKING = Model(identifier="claude45opusthinking")
|
|
49
|
-
"""
|
|
67
|
+
"""
|
|
68
|
+
Claude Opus 4.5 Thinking - Anthropic's Opus reasoning model with thinking.
|
|
69
|
+
"""
|
|
50
70
|
|
|
51
71
|
GEMINI_3_PRO = Model(identifier="gemini30pro")
|
|
52
|
-
"""
|
|
72
|
+
"""
|
|
73
|
+
Gemini 3 Pro - Google's newest reasoning model.
|
|
74
|
+
"""
|
|
53
75
|
|
|
54
76
|
GEMINI_3_FLASH = Model(identifier="gemini30flash")
|
|
55
|
-
"""
|
|
77
|
+
"""
|
|
78
|
+
Gemini 3 Flash - Google's fast reasoning model.
|
|
79
|
+
"""
|
|
56
80
|
|
|
57
81
|
GEMINI_3_FLASH_THINKING = Model(identifier="gemini30flash_high")
|
|
58
|
-
"""
|
|
82
|
+
"""
|
|
83
|
+
Gemini 3 Flash Thinking - Google's fast reasoning model with enhanced thinking.
|
|
84
|
+
"""
|
|
59
85
|
|
|
60
86
|
GROK_41 = Model(identifier="grok41nonreasoning")
|
|
61
|
-
"""
|
|
87
|
+
"""
|
|
88
|
+
Grok 4.1 - xAI's latest advanced model.
|
|
89
|
+
"""
|
|
62
90
|
|
|
63
91
|
GROK_41_THINKING = Model(identifier="grok41reasoning")
|
|
64
|
-
"""
|
|
92
|
+
"""
|
|
93
|
+
Grok 4.1 Thinking - xAI's latest reasoning model.
|
|
94
|
+
"""
|
|
65
95
|
|
|
66
96
|
KIMI_K2_THINKING = Model(identifier="kimik2thinking")
|
|
67
|
-
"""
|
|
97
|
+
"""
|
|
98
|
+
Kimi K2 Thinking - Moonshot AI's latest reasoning model.
|
|
99
|
+
"""
|
|
68
100
|
|
|
69
101
|
CLAUDE_45_SONNET = Model(identifier="claude45sonnet")
|
|
70
|
-
"""
|
|
102
|
+
"""
|
|
103
|
+
Claude Sonnet 4.5 - Anthropic's newest advanced model.
|
|
104
|
+
"""
|
|
71
105
|
|
|
72
106
|
CLAUDE_45_SONNET_THINKING = Model(identifier="claude45sonnetthinking")
|
|
73
|
-
"""
|
|
107
|
+
"""
|
|
108
|
+
Claude Sonnet 4.5 Thinking - Anthropic's newest reasoning model.
|
|
109
|
+
"""
|
|
@@ -84,7 +84,9 @@ class RateLimiter:
|
|
|
84
84
|
_lock: Lock = field(default_factory=Lock, init=False)
|
|
85
85
|
|
|
86
86
|
def acquire(self) -> None:
|
|
87
|
-
"""
|
|
87
|
+
"""
|
|
88
|
+
Wait until a request can be made within rate limits.
|
|
89
|
+
"""
|
|
88
90
|
|
|
89
91
|
with self._lock:
|
|
90
92
|
now = time.monotonic()
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
Response types and data models.
|
|
3
|
+
"""
|
|
2
4
|
|
|
3
5
|
from __future__ import annotations
|
|
4
6
|
|
|
@@ -8,7 +10,9 @@ from typing import Any
|
|
|
8
10
|
|
|
9
11
|
@dataclass(frozen=True, slots=True)
|
|
10
12
|
class Coordinates:
|
|
11
|
-
"""
|
|
13
|
+
"""
|
|
14
|
+
Geographic coordinates (lat/lng).
|
|
15
|
+
"""
|
|
12
16
|
|
|
13
17
|
latitude: float
|
|
14
18
|
longitude: float
|
|
@@ -16,7 +20,9 @@ class Coordinates:
|
|
|
16
20
|
|
|
17
21
|
@dataclass(frozen=True, slots=True)
|
|
18
22
|
class SearchResultItem:
|
|
19
|
-
"""
|
|
23
|
+
"""
|
|
24
|
+
A single search result.
|
|
25
|
+
"""
|
|
20
26
|
|
|
21
27
|
title: str | None = None
|
|
22
28
|
snippet: str | None = None
|
|
@@ -25,7 +31,9 @@ class SearchResultItem:
|
|
|
25
31
|
|
|
26
32
|
@dataclass(slots=True)
|
|
27
33
|
class Response:
|
|
28
|
-
"""
|
|
34
|
+
"""
|
|
35
|
+
Response from Perplexity AI.
|
|
36
|
+
"""
|
|
29
37
|
|
|
30
38
|
title: str | None = None
|
|
31
39
|
answer: str | None = None
|
|
@@ -38,7 +46,9 @@ class Response:
|
|
|
38
46
|
|
|
39
47
|
@dataclass(frozen=True, slots=True)
|
|
40
48
|
class _FileInfo:
|
|
41
|
-
"""
|
|
49
|
+
"""
|
|
50
|
+
Internal file info for uploads.
|
|
51
|
+
"""
|
|
42
52
|
|
|
43
53
|
path: str
|
|
44
54
|
size: int
|
{perplexity_webui_scraper-0.3.5.dist-info → perplexity_webui_scraper-0.3.6.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: perplexity-webui-scraper
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Summary: Python scraper to extract AI responses from Perplexity's web interface.
|
|
5
5
|
Keywords: perplexity,ai,scraper,webui,api,client
|
|
6
6
|
Author: henrique-coder
|
|
@@ -24,8 +24,8 @@ Requires-Dist: loguru>=0.7.3
|
|
|
24
24
|
Requires-Dist: orjson>=3.11.5
|
|
25
25
|
Requires-Dist: pydantic>=2.12.5
|
|
26
26
|
Requires-Dist: tenacity>=9.1.2
|
|
27
|
-
Requires-Dist: fastmcp>=2.14.
|
|
28
|
-
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: fastmcp>=2.14.2 ; extra == 'mcp'
|
|
28
|
+
Requires-Python: >=3.10
|
|
29
29
|
Project-URL: Changelog, https://github.com/henrique-coder/perplexity-webui-scraper/releases
|
|
30
30
|
Project-URL: Documentation, https://github.com/henrique-coder/perplexity-webui-scraper#readme
|
|
31
31
|
Project-URL: Homepage, https://github.com/henrique-coder/perplexity-webui-scraper
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
perplexity_webui_scraper/__init__.py,sha256=DQdyF667plbSLdULrEll1ovrOPB7HwVNrD7fttLNkuQ,715
|
|
2
|
+
perplexity_webui_scraper/cli/get_perplexity_session_token.py,sha256=PxHZbrTbekUQX2MIMDZC0iXBTmnTSBFZMb85mLgezR8,7009
|
|
3
|
+
perplexity_webui_scraper/config.py,sha256=nQYNsyCvMQW1V51R-6UV5hA0DI02qBLJ-ufX6r3NZyU,2067
|
|
4
|
+
perplexity_webui_scraper/constants.py,sha256=j32_i67mZGJ4B3KPV1ICE-6NR5g3KxDtDY5OxQXIthw,1887
|
|
5
|
+
perplexity_webui_scraper/core.py,sha256=X8aQgIBCFzz000Cy58ofWCByT94HYuqyuQQmlgEQBVk,22460
|
|
6
|
+
perplexity_webui_scraper/enums.py,sha256=QHrDfdawedGuuThcAR0XC0xPnc7VwSaK4BFQ9jqzQag,2953
|
|
7
|
+
perplexity_webui_scraper/exceptions.py,sha256=vdr5fm6OSgruII8zLwcYT3NsrU58GAXFX-WyS6nuW0M,3987
|
|
8
|
+
perplexity_webui_scraper/http.py,sha256=blRITnFrJsqcsZXUDub8_ZFjSNtLz7mK2ZBRV6tRc1I,19757
|
|
9
|
+
perplexity_webui_scraper/limits.py,sha256=4-eO9xbfKtObN4kMDIVglXmfC4dZvYtvjyUKUP_LKSU,474
|
|
10
|
+
perplexity_webui_scraper/logging.py,sha256=jxgho9jjrZvr3tZ5m0z3caQQsqdtFT9sM-HDVFWL67M,7300
|
|
11
|
+
perplexity_webui_scraper/mcp/__init__.py,sha256=Ur7fmsPDrxewBzq9UdvMoFHp85a8pGsW32WBHG5pfrc,376
|
|
12
|
+
perplexity_webui_scraper/mcp/__main__.py,sha256=cmGev_HXYQK2hoNSQAziEo7bVqHdc0lXaTquzMItSiU,148
|
|
13
|
+
perplexity_webui_scraper/mcp/server.py,sha256=3RJeOHOjv048ZleUURG3sFdMaDHsSPmdn7GY47yAm2s,4758
|
|
14
|
+
perplexity_webui_scraper/models.py,sha256=I5PhVD7B5XeeWfjWvXBtnl3CHMv4P59DO_hnYk9O0b0,2636
|
|
15
|
+
perplexity_webui_scraper/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
perplexity_webui_scraper/resilience.py,sha256=sVsLW1kU6dMit4L6-dyPhVoT00EXGfokbEUnzvBNb0k,4680
|
|
17
|
+
perplexity_webui_scraper/types.py,sha256=qWsIABBxn1vcQQ2KUbC_hyiQHeaLsVb63epmRcpcmLk,1070
|
|
18
|
+
perplexity_webui_scraper-0.3.6.dist-info/WHEEL,sha256=RRVLqVugUmFOqBedBFAmA4bsgFcROUBiSUKlERi0Hcg,79
|
|
19
|
+
perplexity_webui_scraper-0.3.6.dist-info/entry_points.txt,sha256=ODpXpDTkmoQ_o3Y3lsy22PLs-8ndapvMKYwxcz6A9gs,189
|
|
20
|
+
perplexity_webui_scraper-0.3.6.dist-info/METADATA,sha256=OkwWS6PQwj1-hTkMPXvqA8peuBL-ei5AUp_kgX9KpAo,12168
|
|
21
|
+
perplexity_webui_scraper-0.3.6.dist-info/RECORD,,
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
perplexity_webui_scraper/__init__.py,sha256=7hLTMTHMC-aCvbDRMXCUBfJ3vztsW9WtEZ08LdIYs9I,713
|
|
2
|
-
perplexity_webui_scraper/cli/get_perplexity_session_token.py,sha256=67Ck4S2MJ0701LnRHq73qY5oRLCsFOyu_8SMgsbTNFc,6937
|
|
3
|
-
perplexity_webui_scraper/config.py,sha256=05lkW9PlMmbj-oh-4xc3-iFXXGvKfKCy8yK-O1GWJdw,2055
|
|
4
|
-
perplexity_webui_scraper/constants.py,sha256=Kq-4i6yyTZ5VhUvbiZmbUmHrjMQm-p7H82Emm7b10-c,1867
|
|
5
|
-
perplexity_webui_scraper/core.py,sha256=dpcx8tTxESWz_iz7nSKN1XWRvBOV_cD9gK-E2gJYJ8w,20894
|
|
6
|
-
perplexity_webui_scraper/enums.py,sha256=I-jMiQMzBW72PGJFBNZIc874wijBMynDF-pYQmS1OZc,2751
|
|
7
|
-
perplexity_webui_scraper/exceptions.py,sha256=Q2dx7j1OrM9CB7ty8fRhheAt4-QhN7szUDXzoT6rx1E,3985
|
|
8
|
-
perplexity_webui_scraper/http.py,sha256=1qxZ3cvkL-TXST2H1V7AUoA5_poZ9sDBm8OV3FEtWZU,19708
|
|
9
|
-
perplexity_webui_scraper/limits.py,sha256=GwcwC8CnSNhlcLWGLpuDYA37gn8OXSfsXLIOc-QbxNs,465
|
|
10
|
-
perplexity_webui_scraper/logging.py,sha256=5IyUiKjN88WCItKl6Yrbfn6rF6jz68rWjFTWQGdvTRo,7129
|
|
11
|
-
perplexity_webui_scraper/mcp/__init__.py,sha256=Ke166qPFVZORf39lc6cHjKoBbbuJztAfU29vYpCwOrA,366
|
|
12
|
-
perplexity_webui_scraper/mcp/__main__.py,sha256=N_cSeNjAzSJ861jspq60W0ZVjxWNXhM5O-FQ0aD1oPs,146
|
|
13
|
-
perplexity_webui_scraper/mcp/server.py,sha256=dadzelHJO3Fkw85hdUiTO10jFaZB26D5e5jWuL3yVoA,5982
|
|
14
|
-
perplexity_webui_scraper/models.py,sha256=QVeZI-WQzpyi9JnE15QIMJ7nsG0YjIjOsZEA6YfX0tw,2448
|
|
15
|
-
perplexity_webui_scraper/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
perplexity_webui_scraper/resilience.py,sha256=-_XYYCazsx5jxrc2HbuJBP16TLh02EEhy8WOukLmbFE,4662
|
|
17
|
-
perplexity_webui_scraper/types.py,sha256=VlnzvNilIHrDXM2YOGjJa1y2VY0tfR-F0zaPjQHoPKs,1028
|
|
18
|
-
perplexity_webui_scraper-0.3.5.dist-info/WHEEL,sha256=ZyFSCYkV2BrxH6-HRVRg3R9Fo7MALzer9KiPYqNxSbo,79
|
|
19
|
-
perplexity_webui_scraper-0.3.5.dist-info/entry_points.txt,sha256=ODpXpDTkmoQ_o3Y3lsy22PLs-8ndapvMKYwxcz6A9gs,189
|
|
20
|
-
perplexity_webui_scraper-0.3.5.dist-info/METADATA,sha256=NEkzx5B0HIj9gA9p72e6v0GOTG6cHoEWeSatgaoghhw,12175
|
|
21
|
-
perplexity_webui_scraper-0.3.5.dist-info/RECORD,,
|
|
File without changes
|