walla-cli 0.1.2__tar.gz → 0.1.3__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.
- {walla_cli-0.1.2 → walla_cli-0.1.3}/PKG-INFO +1 -1
- {walla_cli-0.1.2 → walla_cli-0.1.3}/pyproject.toml +1 -1
- {walla_cli-0.1.2 → walla_cli-0.1.3}/tests/test_session_store.py +20 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/__init__.py +1 -1
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/account/cookies.py +39 -7
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/account/login.py +19 -4
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/account/prompt.py +2 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/.cursor/rules/quality.mdc +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/.github/workflows/ci.yml +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/.gitignore +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/AGENTS.md +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/LICENSE +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/README.md +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/docs/WIRE.md +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/docs/assets/walla-github-banner.jpg +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/docs/code_quality.md +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/docs/data_engineering_standards.md +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/docs/mcp.md +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/docs/objective_function.md +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/docs/rate_limit_analysis.md +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/fixtures/categories.json +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/fixtures/item_detail.json +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/fixtures/search_bicicleta.json +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/scripts/check_code_quality.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/scripts/probe_rate.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/skills/walla/SKILL.md +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/tests/test_account.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/tests/test_account_more.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/tests/test_catch.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/tests/test_cli_json.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/tests/test_core.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/tests/test_edges.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/tests/test_http_more.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/tests/test_live.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/tests/test_parsers.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/tests/test_polite.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/tests/test_search_api.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/tests/test_verdict.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/account/__init__.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/account/actions.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/account/inbox.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/account/session_store.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/cli/__init__.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/cli/banner.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/cli/catch.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/cli/cmd_account.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/cli/cmd_look.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/cli/cmd_ops.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/cli/console.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/cli/main.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/core/__init__.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/core/envelope.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/core/errors.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/core/exceptions.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/core/human_fix.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/core/instruct.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/core/path.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/http/__init__.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/http/client.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/http/headers.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/http/polite.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/hunter/__init__.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/hunter/profile_store.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/hunter/verdict.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/hunter/watches.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/mcp/__init__.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/mcp/_entry.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/mcp/server.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/models/__init__.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/models/account.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/models/listing.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/models/profile.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/search/__init__.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/search/api.py +0 -0
- {walla_cli-0.1.2 → walla_cli-0.1.3}/walla/search/parse.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: walla-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Wallapop.es CLI - look · offer · talk for humans and agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/berendgort/wallapop-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/berendgort/wallapop-cli
|
|
@@ -53,3 +53,23 @@ def test_parse_cookie_missing(tmp_path: Path) -> None:
|
|
|
53
53
|
f.write_text("foo=bar\n", encoding="utf-8")
|
|
54
54
|
with pytest.raises(WallaAuthError):
|
|
55
55
|
parse_cookie_export(f)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_parse_cookie_editor_name_colon_quotes() -> None:
|
|
59
|
+
from walla.account.cookies import parse_cookie_text
|
|
60
|
+
|
|
61
|
+
raw = '__Secure-next-auth.session-token:"eyJhbGci.aaa.bbb"'
|
|
62
|
+
cookie, _ = parse_cookie_text(raw)
|
|
63
|
+
assert cookie == "eyJhbGci.aaa.bbb"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_login_cookie_text_does_not_treat_blob_as_path() -> None:
|
|
67
|
+
from unittest.mock import patch
|
|
68
|
+
|
|
69
|
+
from walla.account.login import login_cookie_text
|
|
70
|
+
|
|
71
|
+
blob = '__Secure-next-auth.session-token:"' + ("x." * 400) + 'yyy"'
|
|
72
|
+
with patch("walla.account.login._session_from_cookie") as mock_sess:
|
|
73
|
+
mock_sess.return_value = SessionData(access_token="t", session_cookie="c")
|
|
74
|
+
login_cookie_text(blob)
|
|
75
|
+
assert mock_sess.call_args[0][0].startswith("x.")
|
|
@@ -64,25 +64,57 @@ def _from_text(text: str) -> tuple[str, str | None]:
|
|
|
64
64
|
if len(fields) >= 7:
|
|
65
65
|
name, value = fields[5], fields[6]
|
|
66
66
|
if name == SESSION_COOKIE:
|
|
67
|
-
session = value
|
|
67
|
+
session = _strip_quotes(value)
|
|
68
68
|
elif name == "device_id":
|
|
69
|
-
device = value
|
|
69
|
+
device = _strip_quotes(value)
|
|
70
70
|
continue
|
|
71
71
|
line = line.removeprefix("Cookie:").strip()
|
|
72
|
+
# Cookie-Editor sometimes copies: name:"value" or name=value
|
|
73
|
+
if SESSION_COOKIE in line and (":" in line or "=" in line):
|
|
74
|
+
extracted = _extract_named(line, SESSION_COOKIE)
|
|
75
|
+
if extracted:
|
|
76
|
+
session = extracted
|
|
77
|
+
continue
|
|
72
78
|
for pair in line.split(";"):
|
|
73
79
|
pair = pair.strip()
|
|
74
|
-
if "=" not in pair:
|
|
80
|
+
if "=" not in pair and ":" not in pair:
|
|
75
81
|
if pair.count(".") >= 2 and len(pair) > 100:
|
|
76
|
-
session = pair
|
|
82
|
+
session = _strip_quotes(pair)
|
|
77
83
|
continue
|
|
78
|
-
name,
|
|
79
|
-
name, value = name.strip(), value.strip()
|
|
84
|
+
name, value = _split_pair(pair)
|
|
80
85
|
if name == SESSION_COOKIE:
|
|
81
86
|
session = value
|
|
82
87
|
elif name == "device_id":
|
|
83
88
|
device = value
|
|
84
89
|
if not session and text.count(".") >= 2 and len(text.strip()) > 100:
|
|
85
|
-
session = text.strip()
|
|
90
|
+
session = _strip_quotes(text.strip())
|
|
86
91
|
if not session:
|
|
87
92
|
raise WallaAuthError(f"no {SESSION_COOKIE} in cookie paste")
|
|
88
93
|
return session, device
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _strip_quotes(value: str) -> str:
|
|
97
|
+
v = value.strip()
|
|
98
|
+
if len(v) >= 2 and v[0] == v[-1] and v[0] in "'\"":
|
|
99
|
+
return v[1:-1]
|
|
100
|
+
return v
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _split_pair(pair: str) -> tuple[str, str]:
|
|
104
|
+
if "=" in pair:
|
|
105
|
+
name, _, value = pair.partition("=")
|
|
106
|
+
else:
|
|
107
|
+
name, _, value = pair.partition(":")
|
|
108
|
+
return name.strip(), _strip_quotes(value.strip())
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _extract_named(line: str, cookie_name: str) -> str | None:
|
|
112
|
+
for sep in (":", "="):
|
|
113
|
+
prefix = f"{cookie_name}{sep}"
|
|
114
|
+
if line.startswith(prefix):
|
|
115
|
+
return _strip_quotes(line[len(prefix) :].strip())
|
|
116
|
+
# also tolerate leading junk
|
|
117
|
+
idx = line.find(prefix)
|
|
118
|
+
if idx >= 0:
|
|
119
|
+
return _strip_quotes(line[idx + len(prefix) :].strip())
|
|
120
|
+
return None
|
|
@@ -28,13 +28,28 @@ __all__ = (
|
|
|
28
28
|
|
|
29
29
|
def login_cookie_text(text: str) -> SessionData:
|
|
30
30
|
"""Primary path: pasted cookie value, Cookie header, or export body."""
|
|
31
|
-
|
|
32
|
-
if
|
|
33
|
-
return login_cookies(
|
|
34
|
-
cookie, device = parse_cookie_text(
|
|
31
|
+
stripped = text.strip()
|
|
32
|
+
if _looks_like_cookie_file_path(stripped):
|
|
33
|
+
return login_cookies(Path(stripped).expanduser())
|
|
34
|
+
cookie, device = parse_cookie_text(stripped)
|
|
35
35
|
return _session_from_cookie(cookie, device)
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
def _looks_like_cookie_file_path(text: str) -> bool:
|
|
39
|
+
"""True only for short path-like pastes; never treat JWT blobs as paths."""
|
|
40
|
+
if "\n" in text or len(text) > 512:
|
|
41
|
+
return False
|
|
42
|
+
if text.count(".") >= 2 and len(text) > 80 and "/" not in text and "\\" not in text:
|
|
43
|
+
return False
|
|
44
|
+
if SESSION_COOKIE in text or text.startswith("eyJ") or ':"' in text:
|
|
45
|
+
return False
|
|
46
|
+
path = Path(text).expanduser()
|
|
47
|
+
try:
|
|
48
|
+
return path.is_file()
|
|
49
|
+
except OSError:
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
38
53
|
def login_cookies(path: Path) -> SessionData:
|
|
39
54
|
cookie, device = parse_cookie_export(path)
|
|
40
55
|
return _session_from_cookie(cookie, device)
|
|
@@ -37,6 +37,8 @@ How to get your Wallapop session cookie
|
|
|
37
37
|
- Double-click the Value cell -> Ctrl+C / Cmd+C
|
|
38
38
|
|
|
39
39
|
4. Paste that long string below (one line), then press Enter.
|
|
40
|
+
Paste ONLY the Value (starts with eyJ...), or Cookie-Editor's
|
|
41
|
+
name:"value" form is also OK.
|
|
40
42
|
Do NOT paste your password. Do NOT share this cookie in chat.
|
|
41
43
|
|
|
42
44
|
Optional: save an export as cookies.txt and paste the file path instead.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|