tokenade 3.5.0__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.
- tokenade/__init__.py +84 -0
- tokenade/cli/__init__.py +306 -0
- tokenade/cli/__main__.py +5 -0
- tokenade/cli/advanced.py +458 -0
- tokenade/cli/completions.py +73 -0
- tokenade/cli/config.py +47 -0
- tokenade/cli/management.py +264 -0
- tokenade/cli/output.py +31 -0
- tokenade/cli/proxy.py +118 -0
- tokenade/cli/security.py +156 -0
- tokenade/cli/session.py +471 -0
- tokenade/core/__init__.py +0 -0
- tokenade/core/antidetection/__init__.py +4 -0
- tokenade/core/antidetection/behavioral.py +140 -0
- tokenade/core/antidetection/cdp_cleaner.py +60 -0
- tokenade/core/api/__init__.py +3 -0
- tokenade/core/api/server.py +251 -0
- tokenade/core/batch/__init__.py +23 -0
- tokenade/core/batch/operations.py +399 -0
- tokenade/core/browser/__init__.py +0 -0
- tokenade/core/browser/manager.py +311 -0
- tokenade/core/crypto/__init__.py +19 -0
- tokenade/core/crypto/cookie_crypto.py +506 -0
- tokenade/core/crypto/encryptor.py +299 -0
- tokenade/core/extractor/__init__.py +0 -0
- tokenade/core/fingerprint/__init__.py +0 -0
- tokenade/core/fingerprint/collectors/__init__.py +30 -0
- tokenade/core/fingerprint/collectors/audio.py +124 -0
- tokenade/core/fingerprint/collectors/base.py +69 -0
- tokenade/core/fingerprint/collectors/battery.py +59 -0
- tokenade/core/fingerprint/collectors/canvas.py +81 -0
- tokenade/core/fingerprint/collectors/fonts.py +52 -0
- tokenade/core/fingerprint/collectors/navigator.py +99 -0
- tokenade/core/fingerprint/collectors/plugins.py +92 -0
- tokenade/core/fingerprint/collectors/screen.py +81 -0
- tokenade/core/fingerprint/collectors/webgl.py +104 -0
- tokenade/core/fingerprint/collectors/webrtc.py +91 -0
- tokenade/core/fingerprint/injector.py +94 -0
- tokenade/core/fingerprint/manager.py +278 -0
- tokenade/core/fingerprint/stealth.py +357 -0
- tokenade/core/importer/__init__.py +23 -0
- tokenade/core/importer/adb_extractor.py +261 -0
- tokenade/core/importer/advanced_validator.py +666 -0
- tokenade/core/importer/browser_discovery.py +314 -0
- tokenade/core/importer/chromium_forks.py +175 -0
- tokenade/core/importer/cookie_extractor.py +517 -0
- tokenade/core/importer/db_utils.py +66 -0
- tokenade/core/importer/format_exporter.py +213 -0
- tokenade/core/importer/format_importer.py +278 -0
- tokenade/core/importer/local_storage_extractor.py +272 -0
- tokenade/core/importer/mobile_extractor.py +264 -0
- tokenade/core/importer/safari_extractor.py +318 -0
- tokenade/core/importer/session_comparator.py +124 -0
- tokenade/core/importer/session_loader.py +470 -0
- tokenade/core/importer/session_manager.py +295 -0
- tokenade/core/importer/session_packager.py +354 -0
- tokenade/core/importer/session_refresher.py +354 -0
- tokenade/core/importer/session_rotation.py +150 -0
- tokenade/core/importer/session_sharer.py +692 -0
- tokenade/core/importer/session_vault.py +282 -0
- tokenade/core/importer/tor_extractor.py +138 -0
- tokenade/core/importer/validator.py +498 -0
- tokenade/core/injector/__init__.py +15 -0
- tokenade/core/injector/profile_manager.py +375 -0
- tokenade/core/integration/__init__.py +8 -0
- tokenade/core/integration/docker_manager.py +349 -0
- tokenade/core/integration/kubernetes.py +395 -0
- tokenade/core/integration/plugin_registry.py +250 -0
- tokenade/core/integration/webhooks.py +219 -0
- tokenade/core/proxy/__init__.py +11 -0
- tokenade/core/proxy/cdp_proxy.py +1301 -0
- tokenade/core/proxy/extension_bridge.py +132 -0
- tokenade/core/proxy/forward_proxy.py +151 -0
- tokenade/core/proxy/multi_site_proxy.py +257 -0
- tokenade/core/proxy/server.py +1295 -0
- tokenade/core/refresh/__init__.py +19 -0
- tokenade/core/refresh/health_checker.py +356 -0
- tokenade/core/refresh/health_scorer.py +292 -0
- tokenade/core/runtime/__init__.py +30 -0
- tokenade/core/runtime/engine.py +786 -0
- tokenade/core/runtime/tls_matcher.py +251 -0
- tokenade/core/security/__init__.py +27 -0
- tokenade/core/security/audit.py +577 -0
- tokenade/core/security/credentials.py +418 -0
- tokenade/core/utils/__init__.py +0 -0
- tokenade/core/utils/performance.py +444 -0
- tokenade/handlers/__init__.py +0 -0
- tokenade/handlers/base.py +294 -0
- tokenade/handlers/generic_oauth.py +594 -0
- tokenade/handlers/github.py +349 -0
- tokenade/handlers/google.py +302 -0
- tokenade/sdk/__init__.py +257 -0
- tokenade/tests/__init__.py +0 -0
- tokenade/tests/portability.py +309 -0
- tokenade/tests/test_advanced_validator.py +237 -0
- tokenade/tests/test_antidetection.py +147 -0
- tokenade/tests/test_api_sdk.py +264 -0
- tokenade/tests/test_benchmarks.py +157 -0
- tokenade/tests/test_browser.py +351 -0
- tokenade/tests/test_browser_discovery.py +59 -0
- tokenade/tests/test_browser_improvements.py +276 -0
- tokenade/tests/test_browser_support.py +732 -0
- tokenade/tests/test_cli.py +204 -0
- tokenade/tests/test_cli_helpers.py +102 -0
- tokenade/tests/test_cli_refactor.py +458 -0
- tokenade/tests/test_collectors.py +286 -0
- tokenade/tests/test_comprehensive.py +314 -0
- tokenade/tests/test_cookie_extractor.py +156 -0
- tokenade/tests/test_crypto.py +189 -0
- tokenade/tests/test_db_utils.py +93 -0
- tokenade/tests/test_encryptor.py +126 -0
- tokenade/tests/test_encryptor_edge.py +66 -0
- tokenade/tests/test_enterprise.py +819 -0
- tokenade/tests/test_fingerprint.py +67 -0
- tokenade/tests/test_format_export.py +415 -0
- tokenade/tests/test_forward_proxy.py +52 -0
- tokenade/tests/test_handlers.py +523 -0
- tokenade/tests/test_health_scorer.py +538 -0
- tokenade/tests/test_importer_integration.py +273 -0
- tokenade/tests/test_integration.py +660 -0
- tokenade/tests/test_local_storage.py +57 -0
- tokenade/tests/test_local_storage_extractor.py +175 -0
- tokenade/tests/test_mac_crypto.py +74 -0
- tokenade/tests/test_multi_site_proxy.py +51 -0
- tokenade/tests/test_performance.py +302 -0
- tokenade/tests/test_property_based.py +215 -0
- tokenade/tests/test_proxy.py +645 -0
- tokenade/tests/test_proxy_config.py +86 -0
- tokenade/tests/test_proxy_gui.py +81 -0
- tokenade/tests/test_runtime.py +473 -0
- tokenade/tests/test_security.py +349 -0
- tokenade/tests/test_session_comparator.py +110 -0
- tokenade/tests/test_session_loader.py +53 -0
- tokenade/tests/test_session_manager.py +135 -0
- tokenade/tests/test_session_packager.py +104 -0
- tokenade/tests/test_session_refresher.py +558 -0
- tokenade/tests/test_session_rotation.py +408 -0
- tokenade/tests/test_session_sharer.py +588 -0
- tokenade/tests/test_session_vault.py +418 -0
- tokenade/tests/test_site_filter.py +66 -0
- tokenade/tests/test_ssrf.py +55 -0
- tokenade/tests/test_stealth.py +148 -0
- tokenade/tests/test_tls.py +40 -0
- tokenade/tests/test_tls_matcher.py +131 -0
- tokenade/utils/__init__.py +0 -0
- tokenade-3.5.0.dist-info/METADATA +567 -0
- tokenade-3.5.0.dist-info/RECORD +150 -0
- tokenade-3.5.0.dist-info/WHEEL +5 -0
- tokenade-3.5.0.dist-info/entry_points.txt +2 -0
- tokenade-3.5.0.dist-info/top_level.txt +1 -0
tokenade/cli/advanced.py
ADDED
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
"""Advanced CLI commands."""
|
|
2
|
+
import asyncio
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger("tokenade")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def cmd_batch_export(args):
|
|
11
|
+
"""Batch export multiple sites."""
|
|
12
|
+
from tokenade.core.batch.operations import BatchExporter, load_batch_config, generate_batch_report
|
|
13
|
+
|
|
14
|
+
print("\n" + "=" * 80)
|
|
15
|
+
print("TOKENADE - Batch Export")
|
|
16
|
+
print("=" * 80)
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
sites = load_batch_config(args.site_config)
|
|
20
|
+
print(f"\n📋 Loaded {len(sites)} site(s) from: {args.site_config}")
|
|
21
|
+
except Exception as e:
|
|
22
|
+
print(f"❌ Failed to load site config: {e}")
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
output_dir = args.output or "sessions_batch"
|
|
26
|
+
|
|
27
|
+
print(f"\n🌐 Browser: {args.browser}")
|
|
28
|
+
print(f"📁 Output: {output_dir}")
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
exporter = BatchExporter()
|
|
32
|
+
result = exporter.export_batch(
|
|
33
|
+
browser=args.browser,
|
|
34
|
+
sites=sites,
|
|
35
|
+
output_dir=output_dir,
|
|
36
|
+
browser_path=args.browser_path,
|
|
37
|
+
profile=args.profile,
|
|
38
|
+
extract_local_storage=args.extract_local_storage
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
print("\n" + generate_batch_report(result))
|
|
42
|
+
|
|
43
|
+
if result.success:
|
|
44
|
+
print(f"\n✅ Batch export completed successfully")
|
|
45
|
+
else:
|
|
46
|
+
print(f"\n⚠️ Batch export completed with errors")
|
|
47
|
+
|
|
48
|
+
except Exception as e:
|
|
49
|
+
logger.error(f"Batch export failed: {e}")
|
|
50
|
+
print(f"❌ Failed: {e}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def cmd_batch_load(args):
|
|
54
|
+
"""Batch load multiple sessions."""
|
|
55
|
+
from tokenade.core.batch.operations import BatchLoader, load_batch_config, generate_batch_report
|
|
56
|
+
|
|
57
|
+
print("\n" + "=" * 80)
|
|
58
|
+
print("TOKENADE - Batch Load")
|
|
59
|
+
print("=" * 80)
|
|
60
|
+
|
|
61
|
+
sites = None
|
|
62
|
+
if args.site_config:
|
|
63
|
+
try:
|
|
64
|
+
sites = load_batch_config(args.site_config)
|
|
65
|
+
print(f"\n📋 Loaded {len(sites)} site(s) from: {args.site_config}")
|
|
66
|
+
except Exception as e:
|
|
67
|
+
print(f"⚠️ Failed to load site config: {e}")
|
|
68
|
+
|
|
69
|
+
print(f"\n📂 Sessions: {args.sessions_dir}")
|
|
70
|
+
print(f"🌐 Target: {args.target_browser}")
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
loader = BatchLoader()
|
|
74
|
+
result = loader.load_batch(
|
|
75
|
+
sessions_dir=args.sessions_dir,
|
|
76
|
+
target_browser=args.target_browser,
|
|
77
|
+
site_configs=sites,
|
|
78
|
+
profile_dir=args.profile_dir,
|
|
79
|
+
validate=args.validate,
|
|
80
|
+
visible=args.visible
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
print("\n" + generate_batch_report(result))
|
|
84
|
+
|
|
85
|
+
if result.success:
|
|
86
|
+
print(f"\n✅ Batch load completed successfully")
|
|
87
|
+
else:
|
|
88
|
+
print(f"\n⚠️ Batch load completed with errors")
|
|
89
|
+
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.error(f"Batch load failed: {e}")
|
|
92
|
+
print(f"❌ Failed: {e}")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def cmd_validate(args):
|
|
96
|
+
"""Validate stored sessions."""
|
|
97
|
+
print("\n" + "=" * 80)
|
|
98
|
+
print("TOKENADE - Session Validation")
|
|
99
|
+
print("=" * 80)
|
|
100
|
+
|
|
101
|
+
sessions_dir = Path(args.sessions_dir)
|
|
102
|
+
if not sessions_dir.exists():
|
|
103
|
+
print(f"❌ Directory not found: {args.sessions_dir}")
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
valid = 0
|
|
107
|
+
invalid = 0
|
|
108
|
+
|
|
109
|
+
for session_file in sessions_dir.glob("*.json"):
|
|
110
|
+
print(f"\n📁 Checking: {session_file.name}")
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
with open(session_file) as f:
|
|
114
|
+
data = json.load(f)
|
|
115
|
+
|
|
116
|
+
required = ["site_name", "auth_status", "cookies"]
|
|
117
|
+
missing = [f for f in required if f not in data]
|
|
118
|
+
|
|
119
|
+
if missing:
|
|
120
|
+
print(f" ❌ Missing fields: {missing}")
|
|
121
|
+
invalid += 1
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
cookies = data.get("cookies", [])
|
|
125
|
+
if not cookies:
|
|
126
|
+
print(f" ⚠️ No cookies")
|
|
127
|
+
else:
|
|
128
|
+
print(f" ✅ {len(cookies)} cookies")
|
|
129
|
+
|
|
130
|
+
tokens = data.get("tokens", [])
|
|
131
|
+
if tokens:
|
|
132
|
+
print(f" ✅ {len(tokens)} tokens")
|
|
133
|
+
|
|
134
|
+
status = data.get("auth_status", "unknown")
|
|
135
|
+
if status == "logged_in":
|
|
136
|
+
print(f" ✅ Status: logged_in")
|
|
137
|
+
valid += 1
|
|
138
|
+
else:
|
|
139
|
+
print(f" ⚠️ Status: {status}")
|
|
140
|
+
invalid += 1
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
print(f" ❌ Error: {e}")
|
|
144
|
+
invalid += 1
|
|
145
|
+
|
|
146
|
+
print(f"\n📊 Summary: {valid} valid, {invalid} invalid")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def cmd_validate_rules(args):
|
|
150
|
+
"""Validate session with custom rules."""
|
|
151
|
+
from tokenade.core.importer.advanced_validator import AdvancedValidator, load_validation_rules
|
|
152
|
+
from tokenade.core.importer.session_packager import SessionPackager
|
|
153
|
+
|
|
154
|
+
session_file = Path(args.session)
|
|
155
|
+
if not session_file.exists():
|
|
156
|
+
print(f"❌ Session file not found: {args.session}")
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
rules_file = Path(args.rules)
|
|
160
|
+
if not rules_file.exists():
|
|
161
|
+
print(f"❌ Rules file not found: {args.rules}")
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
packager = SessionPackager()
|
|
165
|
+
session = packager.load(str(session_file))
|
|
166
|
+
|
|
167
|
+
rules = load_validation_rules(str(rules_file))
|
|
168
|
+
|
|
169
|
+
print("\n" + "=" * 60)
|
|
170
|
+
print("TOKENADE - Advanced Validation")
|
|
171
|
+
print("=" * 60)
|
|
172
|
+
print(f"\n📂 Session: {args.session}")
|
|
173
|
+
print(f"📋 Rules: {len(rules)}")
|
|
174
|
+
|
|
175
|
+
validator = AdvancedValidator()
|
|
176
|
+
|
|
177
|
+
results = asyncio.run(validator.validate_rules(
|
|
178
|
+
session,
|
|
179
|
+
rules,
|
|
180
|
+
site_url=args.url,
|
|
181
|
+
))
|
|
182
|
+
|
|
183
|
+
passed = sum(1 for r in results if r.passed)
|
|
184
|
+
failed = sum(1 for r in results if not r.passed)
|
|
185
|
+
|
|
186
|
+
print(f"\n{'='*60}")
|
|
187
|
+
for result in results:
|
|
188
|
+
status = "✅" if result.passed else "❌"
|
|
189
|
+
duration = f" ({result.duration_ms:.0f}ms)" if result.duration_ms else ""
|
|
190
|
+
print(f"{status} {result.rule_name}: {result.message}{duration}")
|
|
191
|
+
if result.details and not result.passed:
|
|
192
|
+
for k, v in result.details.items():
|
|
193
|
+
print(f" {k}: {v}")
|
|
194
|
+
|
|
195
|
+
print(f"\n{'='*60}")
|
|
196
|
+
print(f"Results: {passed} passed, {failed} failed")
|
|
197
|
+
print(f"{'='*60}\n")
|
|
198
|
+
|
|
199
|
+
if failed:
|
|
200
|
+
import sys
|
|
201
|
+
sys.exit(1)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def cmd_diff(args):
|
|
205
|
+
"""Compare two session files."""
|
|
206
|
+
from tokenade.core.importer.session_comparator import SessionComparator
|
|
207
|
+
|
|
208
|
+
for path in (args.session_a, args.session_b):
|
|
209
|
+
if not Path(path).exists():
|
|
210
|
+
print(f"❌ File not found: {path}")
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
comparator = SessionComparator()
|
|
214
|
+
result = comparator.compare_files(args.session_a, args.session_b)
|
|
215
|
+
|
|
216
|
+
print("\n" + "=" * 70)
|
|
217
|
+
print("SESSION COMPARISON")
|
|
218
|
+
print("=" * 70)
|
|
219
|
+
print(f"\n A: {args.session_a}")
|
|
220
|
+
print(f" B: {args.session_b}")
|
|
221
|
+
|
|
222
|
+
if not result.has_changes:
|
|
223
|
+
print("\n ✅ Sessions are identical")
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
print(f"\n {'─' * 50}")
|
|
227
|
+
print(result.summary())
|
|
228
|
+
|
|
229
|
+
if args.verbose:
|
|
230
|
+
if result.cookies_only_in_a:
|
|
231
|
+
print(f"\n Cookies only in A:")
|
|
232
|
+
for c in result.cookies_only_in_a:
|
|
233
|
+
print(f" - {c.get('name')} ({c.get('domain')})")
|
|
234
|
+
if result.cookies_only_in_b:
|
|
235
|
+
print(f"\n Cookies only in B:")
|
|
236
|
+
for c in result.cookies_only_in_b:
|
|
237
|
+
print(f" - {c.get('name')} ({c.get('domain')})")
|
|
238
|
+
if result.cookies_modified:
|
|
239
|
+
print(f"\n Cookies modified:")
|
|
240
|
+
for m in result.cookies_modified:
|
|
241
|
+
print(f" - {m['key']}")
|
|
242
|
+
print(f" A: {m['a'].get('value', '')[:50]}...")
|
|
243
|
+
print(f" B: {m['b'].get('value', '')[:50]}...")
|
|
244
|
+
if result.localStorage_only_in_a:
|
|
245
|
+
print(f"\n localStorage only in A:")
|
|
246
|
+
for k in result.localStorage_only_in_a:
|
|
247
|
+
print(f" - {k}")
|
|
248
|
+
if result.localStorage_only_in_b:
|
|
249
|
+
print(f"\n localStorage only in B:")
|
|
250
|
+
for k in result.localStorage_only_in_b:
|
|
251
|
+
print(f" - {k}")
|
|
252
|
+
if result.localStorage_modified:
|
|
253
|
+
print(f"\n localStorage modified:")
|
|
254
|
+
for k, v in result.localStorage_modified.items():
|
|
255
|
+
print(f" - {k}")
|
|
256
|
+
print(f" A: {v['a'][:50]}...")
|
|
257
|
+
print(f" B: {v['b'][:50]}...")
|
|
258
|
+
if result.metadata_diffs:
|
|
259
|
+
print(f"\n Metadata differences:")
|
|
260
|
+
for field, vals in result.metadata_diffs.items():
|
|
261
|
+
print(f" - {field}: {vals['a']} -> {vals['b']}")
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def cmd_fingerprint(args):
|
|
265
|
+
"""Manage browser fingerprints."""
|
|
266
|
+
from tokenade.core.browser.manager import BrowserFactory, BrowserConfig
|
|
267
|
+
from tokenade.core.fingerprint.manager import FingerprintManager, FingerprintCollector
|
|
268
|
+
|
|
269
|
+
fp_manager = FingerprintManager()
|
|
270
|
+
|
|
271
|
+
if args.action == "list":
|
|
272
|
+
print("\n📋 Stored fingerprints:")
|
|
273
|
+
for name in fp_manager.list():
|
|
274
|
+
print(f" • {name}")
|
|
275
|
+
|
|
276
|
+
elif args.action == "collect":
|
|
277
|
+
print("\n🚀 Launching browser to collect fingerprint...")
|
|
278
|
+
|
|
279
|
+
config = BrowserConfig(
|
|
280
|
+
headless=False,
|
|
281
|
+
user_data_dir=args.profile_dir,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
browser = BrowserFactory.create(**config.__dict__)
|
|
285
|
+
|
|
286
|
+
try:
|
|
287
|
+
browser.launch()
|
|
288
|
+
fp = FingerprintCollector.collect_from_browser(browser)
|
|
289
|
+
path = fp_manager.save(args.name, fp)
|
|
290
|
+
print(f"✅ Fingerprint saved: {path}")
|
|
291
|
+
print(f"\n User Agent: {fp.user_agent[:80]}...")
|
|
292
|
+
print(f" Screen: {fp.screen_width}x{fp.screen_height}")
|
|
293
|
+
print(f" Platform: {fp.platform}")
|
|
294
|
+
finally:
|
|
295
|
+
browser.close()
|
|
296
|
+
|
|
297
|
+
elif args.action == "show":
|
|
298
|
+
fp = fp_manager.load(args.name)
|
|
299
|
+
if fp:
|
|
300
|
+
print(f"\n🔍 Fingerprint: {args.name}")
|
|
301
|
+
print(f" User Agent: {fp.user_agent}")
|
|
302
|
+
print(f" Screen: {fp.screen_width}x{fp.screen_height}")
|
|
303
|
+
print(f" Viewport: {fp.viewport_width}x{fp.viewport_height}")
|
|
304
|
+
print(f" Platform: {fp.platform}")
|
|
305
|
+
print(f" Language: {fp.language}")
|
|
306
|
+
print(f" Timezone: {fp.timezone}")
|
|
307
|
+
print(f" Hardware: {fp.hardware_concurrency} cores, {fp.device_memory}GB RAM")
|
|
308
|
+
else:
|
|
309
|
+
print(f"❌ Fingerprint not found: {args.name}")
|
|
310
|
+
|
|
311
|
+
elif args.action == "delete":
|
|
312
|
+
if fp_manager.delete(args.name):
|
|
313
|
+
print(f"✅ Deleted: {args.name}")
|
|
314
|
+
else:
|
|
315
|
+
print(f"❌ Not found: {args.name}")
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def cmd_test(args):
|
|
319
|
+
"""Run portability tests with fingerprint spoofing."""
|
|
320
|
+
from tokenade.core.browser.manager import BrowserFactory, BrowserConfig
|
|
321
|
+
from tokenade.core.fingerprint.manager import FingerprintManager
|
|
322
|
+
from tokenade.core.fingerprint.injector import validate_injection
|
|
323
|
+
from tokenade.handlers.google import GoogleHandler
|
|
324
|
+
from tokenade.tests.portability import PortabilityTester
|
|
325
|
+
|
|
326
|
+
print("\n" + "=" * 80)
|
|
327
|
+
print("TOKENADE - Portability Test")
|
|
328
|
+
print("=" * 80)
|
|
329
|
+
|
|
330
|
+
session_file = Path(args.session)
|
|
331
|
+
if not session_file.exists():
|
|
332
|
+
print(f"❌ Session file not found: {args.session}")
|
|
333
|
+
return
|
|
334
|
+
|
|
335
|
+
with open(session_file) as f:
|
|
336
|
+
session_data = json.load(f)
|
|
337
|
+
|
|
338
|
+
fp_manager = FingerprintManager()
|
|
339
|
+
tester = PortabilityTester(BrowserFactory, fp_manager)
|
|
340
|
+
|
|
341
|
+
print(f"\n🛡️ Stealth level: {args.stealth_level}")
|
|
342
|
+
|
|
343
|
+
if args.variations:
|
|
344
|
+
print("\n🧪 Testing fingerprint variations...")
|
|
345
|
+
results = tester.test_fingerprint_variations(
|
|
346
|
+
session_data=session_data,
|
|
347
|
+
base_fp_name=args.source_fp or "default",
|
|
348
|
+
handler_class=GoogleHandler,
|
|
349
|
+
)
|
|
350
|
+
else:
|
|
351
|
+
print(f"\n🧪 Testing transfer to: {args.target_fp}")
|
|
352
|
+
|
|
353
|
+
fp = fp_manager.load(args.target_fp)
|
|
354
|
+
if fp:
|
|
355
|
+
print(f" User Agent: {fp.user_agent[:60]}...")
|
|
356
|
+
print(f" Screen: {fp.screen_width}x{fp.screen_height}")
|
|
357
|
+
|
|
358
|
+
result = tester.test_session_transfer(
|
|
359
|
+
session_data=session_data,
|
|
360
|
+
source_fp_name=args.source_fp or "default",
|
|
361
|
+
target_fp_name=args.target_fp,
|
|
362
|
+
handler_class=GoogleHandler,
|
|
363
|
+
test_api=args.test_api,
|
|
364
|
+
)
|
|
365
|
+
results = [result]
|
|
366
|
+
|
|
367
|
+
if args.validate_stealth and fp:
|
|
368
|
+
print("\n🔍 Validating stealth injection...")
|
|
369
|
+
config = BrowserConfig(
|
|
370
|
+
headless=True,
|
|
371
|
+
fingerprint=fp.to_dict(),
|
|
372
|
+
stealth_level=args.stealth_level,
|
|
373
|
+
)
|
|
374
|
+
browser = BrowserFactory.create(**config.__dict__)
|
|
375
|
+
try:
|
|
376
|
+
browser.launch()
|
|
377
|
+
result = validate_injection(browser)
|
|
378
|
+
if result["valid"]:
|
|
379
|
+
print(" ✅ Stealth injection verified")
|
|
380
|
+
else:
|
|
381
|
+
print(" ⚠️ Stealth injection may not be fully active")
|
|
382
|
+
finally:
|
|
383
|
+
browser.close()
|
|
384
|
+
|
|
385
|
+
report = tester.generate_report(args.output)
|
|
386
|
+
print(report)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def cmd_setup(args):
|
|
390
|
+
"""Setup accounts with initial login."""
|
|
391
|
+
from datetime import datetime
|
|
392
|
+
import getpass
|
|
393
|
+
from tokenade.core.browser.manager import BrowserFactory, BrowserConfig
|
|
394
|
+
from tokenade.core.security.credentials import CredentialManager, AccountCredentials
|
|
395
|
+
from tokenade.handlers.google import GoogleHandler
|
|
396
|
+
|
|
397
|
+
print("\n" + "=" * 80)
|
|
398
|
+
print("TOKENADE - Account Setup")
|
|
399
|
+
print("=" * 80)
|
|
400
|
+
|
|
401
|
+
manager = CredentialManager()
|
|
402
|
+
accounts = manager.load_accounts()
|
|
403
|
+
|
|
404
|
+
if accounts:
|
|
405
|
+
print(f"\n📋 Found {len(accounts)} existing account(s)")
|
|
406
|
+
|
|
407
|
+
while True:
|
|
408
|
+
choice = input("\nAdd account? (yes/no): ").strip().lower()
|
|
409
|
+
if choice not in ("yes", "y"):
|
|
410
|
+
break
|
|
411
|
+
|
|
412
|
+
email = input("📧 Email: ").strip()
|
|
413
|
+
password = getpass.getpass("🔒 Password: ")
|
|
414
|
+
|
|
415
|
+
if not email or not password:
|
|
416
|
+
print("❌ Email and password required")
|
|
417
|
+
continue
|
|
418
|
+
|
|
419
|
+
account_num = len(accounts) + 1
|
|
420
|
+
profile_dir = f"browser_data/{account_num}"
|
|
421
|
+
|
|
422
|
+
print(f"\n🚀 Setting up account #{account_num}...")
|
|
423
|
+
|
|
424
|
+
config = BrowserConfig(
|
|
425
|
+
headless=False,
|
|
426
|
+
user_data_dir=profile_dir,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
browser = BrowserFactory.create(**config.__dict__)
|
|
430
|
+
page = browser.launch()
|
|
431
|
+
|
|
432
|
+
try:
|
|
433
|
+
handler = GoogleHandler(browser)
|
|
434
|
+
status = handler.login(email, password, headless=False)
|
|
435
|
+
|
|
436
|
+
if status.value == "logged_in":
|
|
437
|
+
account = AccountCredentials(
|
|
438
|
+
number=account_num,
|
|
439
|
+
email=email,
|
|
440
|
+
password=password,
|
|
441
|
+
profile_dir=profile_dir,
|
|
442
|
+
site="google",
|
|
443
|
+
metadata={"created_at": datetime.now().isoformat()},
|
|
444
|
+
)
|
|
445
|
+
accounts.append(account)
|
|
446
|
+
manager.save_accounts(accounts, use_keyring=True, encrypt_file=False)
|
|
447
|
+
|
|
448
|
+
print(f"✅ Account #{account_num} setup complete")
|
|
449
|
+
print(f" Password stored in system keyring" if manager._keyring_available
|
|
450
|
+
else f" ⚠️ Keyring unavailable — run 'tokenade setup --encrypt' for secure storage")
|
|
451
|
+
else:
|
|
452
|
+
print(f"❌ Login failed for account #{account_num}")
|
|
453
|
+
|
|
454
|
+
finally:
|
|
455
|
+
browser.close()
|
|
456
|
+
|
|
457
|
+
print(f"\n📊 Total accounts: {len(accounts)}")
|
|
458
|
+
print("\nNext: run 'tokenade extract' to collect tokens")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Shell completion scripts for bash, zsh, and fish."""
|
|
2
|
+
from textwrap import dedent
|
|
3
|
+
|
|
4
|
+
BASH_COMPLETION = dedent("""\
|
|
5
|
+
# Tokenade bash completion
|
|
6
|
+
_tokenade_completions() {
|
|
7
|
+
local cur prev commands
|
|
8
|
+
COMPREPLY=()
|
|
9
|
+
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
10
|
+
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
|
11
|
+
commands="setup extract transfer test fingerprint export load inject-profile encrypt decrypt rekey batch-export batch-load health refresh proxy sessions share unshare validate-rules diff"
|
|
12
|
+
|
|
13
|
+
if [[ ${cur} == -* ]]; then
|
|
14
|
+
COMPREPLY=( $(compgen -W "--help --version --browser-name --domains --output --port --host --visible --session --format --password --expiry --profile --site-config --list-profiles" -- ${cur}) )
|
|
15
|
+
else
|
|
16
|
+
COMPREPLY=( $(compgen -W "${commands}" -- ${cur}) )
|
|
17
|
+
fi
|
|
18
|
+
return 0
|
|
19
|
+
}
|
|
20
|
+
complete -F _tokenade_completions tokenade
|
|
21
|
+
""")
|
|
22
|
+
|
|
23
|
+
ZSH_COMPLETION = dedent("""\
|
|
24
|
+
#compdef tokenade
|
|
25
|
+
# Tokenade zsh completion
|
|
26
|
+
|
|
27
|
+
_tokenade() {
|
|
28
|
+
_arguments \
|
|
29
|
+
'1:command:(setup extract transfer test fingerprint export load inject-profile encrypt decrypt rekey batch-export batch-load health refresh proxy sessions share unshare validate-rules diff)' \
|
|
30
|
+
'*::arg:->args'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_tokenade "$@"
|
|
34
|
+
""")
|
|
35
|
+
|
|
36
|
+
FISH_COMPLETION = dedent("""\
|
|
37
|
+
# Tokenade fish completion
|
|
38
|
+
complete -c tokenade -f
|
|
39
|
+
complete -c tokenade -n '__fish_use_subcommand' -a setup -d 'Initial setup'
|
|
40
|
+
complete -c tokenade -n '__fish_use_subcommand' -a extract -d 'Extract cookies'
|
|
41
|
+
complete -c tokenade -n '__fish_use_subcommand' -a export -d 'Export session'
|
|
42
|
+
complete -c tokenade -n '__fish_use_subcommand' -a proxy -d 'Start proxy'
|
|
43
|
+
complete -c tokenade -n '__fish_use_subcommand' -a share -d 'Share session'
|
|
44
|
+
complete -c tokenade -n '__fish_use_subcommand' -a sessions -d 'Manage sessions'
|
|
45
|
+
complete -c tokenade -n '__fish_use_subcommand' -a health -d 'Check session health'
|
|
46
|
+
complete -c tokenade -n '__fish_use_subcommand' -a encrypt -d 'Encrypt session'
|
|
47
|
+
complete -c tokenade -n '__fish_use_subcommand' -a decrypt -d 'Decrypt session'
|
|
48
|
+
""")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def install_completion(shell: str = "bash"):
|
|
52
|
+
"""Install shell completion."""
|
|
53
|
+
from pathlib import Path
|
|
54
|
+
|
|
55
|
+
if shell == "bash":
|
|
56
|
+
comp_dir = Path.home() / ".bash_completion.d"
|
|
57
|
+
comp_dir.mkdir(exist_ok=True)
|
|
58
|
+
comp_file = comp_dir / "tokenade"
|
|
59
|
+
comp_file.write_text(BASH_COMPLETION)
|
|
60
|
+
print(f"Installed bash completion to {comp_file}")
|
|
61
|
+
print("Restart your shell or run: source ~/.bash_completion.d/tokenade")
|
|
62
|
+
elif shell == "zsh":
|
|
63
|
+
comp_dir = Path.home() / ".zsh" / "completions"
|
|
64
|
+
comp_dir.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
comp_file = comp_dir / "_tokenade"
|
|
66
|
+
comp_file.write_text(ZSH_COMPLETION)
|
|
67
|
+
print(f"Installed zsh completion to {comp_file}")
|
|
68
|
+
elif shell == "fish":
|
|
69
|
+
comp_dir = Path.home() / ".config" / "fish" / "completions"
|
|
70
|
+
comp_dir.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
comp_file = comp_dir / "tokenade.fish"
|
|
72
|
+
comp_file.write_text(FISH_COMPLETION)
|
|
73
|
+
print(f"Installed fish completion to {comp_file}")
|
tokenade/cli/config.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Tokenade configuration file support."""
|
|
2
|
+
import json
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
DEFAULT_CONFIG_DIR = Path.home() / ".tokenade"
|
|
7
|
+
DEFAULT_CONFIG_FILE = DEFAULT_CONFIG_DIR / "config.json"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TokenadeConfig:
|
|
11
|
+
def __init__(self, config_file: Path = DEFAULT_CONFIG_FILE):
|
|
12
|
+
self.config_file = config_file
|
|
13
|
+
self._defaults = {
|
|
14
|
+
"browser": "chrome",
|
|
15
|
+
"output_format": "tokenade",
|
|
16
|
+
"proxy_port": 9222,
|
|
17
|
+
"proxy_host": "127.0.0.1",
|
|
18
|
+
"sessions_dir": "~/.tokenade/sessions",
|
|
19
|
+
"auto_refresh": False,
|
|
20
|
+
"source_browser": None,
|
|
21
|
+
}
|
|
22
|
+
self._config = self._load()
|
|
23
|
+
|
|
24
|
+
def _load(self) -> dict:
|
|
25
|
+
if self.config_file.exists():
|
|
26
|
+
with open(self.config_file) as f:
|
|
27
|
+
return {**self._defaults, **json.load(f)}
|
|
28
|
+
return self._defaults.copy()
|
|
29
|
+
|
|
30
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
31
|
+
return self._config.get(key, default)
|
|
32
|
+
|
|
33
|
+
def set(self, key: str, value: Any):
|
|
34
|
+
self._config[key] = value
|
|
35
|
+
self._save()
|
|
36
|
+
|
|
37
|
+
def _save(self):
|
|
38
|
+
self.config_file.parent.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
with open(self.config_file, "w") as f:
|
|
40
|
+
json.dump(self._config, f, indent=2)
|
|
41
|
+
|
|
42
|
+
def delete(self, key: str):
|
|
43
|
+
self._config.pop(key, None)
|
|
44
|
+
self._save()
|
|
45
|
+
|
|
46
|
+
def list_all(self) -> dict:
|
|
47
|
+
return self._config.copy()
|