pylookyloo 1.23.1__py3-none-any.whl → 1.25.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.
- pylookyloo/api.py +227 -91
- {pylookyloo-1.23.1.dist-info → pylookyloo-1.25.0.dist-info}/METADATA +2 -2
- pylookyloo-1.25.0.dist-info/RECORD +8 -0
- {pylookyloo-1.23.1.dist-info → pylookyloo-1.25.0.dist-info}/WHEEL +1 -1
- pylookyloo-1.23.1.dist-info/RECORD +0 -8
- {pylookyloo-1.23.1.dist-info → pylookyloo-1.25.0.dist-info}/LICENSE +0 -0
- {pylookyloo-1.23.1.dist-info → pylookyloo-1.25.0.dist-info}/entry_points.txt +0 -0
pylookyloo/api.py
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
4
6
|
import base64
|
|
5
7
|
import warnings
|
|
6
8
|
|
|
9
|
+
from datetime import datetime
|
|
7
10
|
from importlib.metadata import version
|
|
8
11
|
from io import BytesIO, StringIO
|
|
9
|
-
from typing import
|
|
12
|
+
from typing import Any, TypedDict, overload, Literal
|
|
10
13
|
from urllib.parse import urljoin, urlparse
|
|
11
14
|
from pathlib import PurePosixPath, Path
|
|
12
15
|
|
|
@@ -24,39 +27,39 @@ class AuthError(PyLookylooError):
|
|
|
24
27
|
class CaptureSettings(TypedDict, total=False):
|
|
25
28
|
'''The capture settings that can be passed to Lookyloo.'''
|
|
26
29
|
|
|
27
|
-
url:
|
|
28
|
-
document_name:
|
|
29
|
-
document:
|
|
30
|
-
browser:
|
|
31
|
-
device_name:
|
|
32
|
-
user_agent:
|
|
33
|
-
proxy:
|
|
34
|
-
general_timeout_in_sec:
|
|
35
|
-
cookies:
|
|
36
|
-
headers:
|
|
37
|
-
http_credentials:
|
|
38
|
-
geolocation:
|
|
39
|
-
timezone_id:
|
|
40
|
-
locale:
|
|
41
|
-
color_scheme:
|
|
42
|
-
viewport:
|
|
43
|
-
referer:
|
|
44
|
-
|
|
45
|
-
listing:
|
|
46
|
-
auto_report:
|
|
30
|
+
url: str | None
|
|
31
|
+
document_name: str | None
|
|
32
|
+
document: str | None
|
|
33
|
+
browser: str | None
|
|
34
|
+
device_name: str | None
|
|
35
|
+
user_agent: str | None
|
|
36
|
+
proxy: str | dict[str, str] | None
|
|
37
|
+
general_timeout_in_sec: int | None
|
|
38
|
+
cookies: list[dict[str, Any]] | None
|
|
39
|
+
headers: str | dict[str, str] | None
|
|
40
|
+
http_credentials: dict[str, int] | None
|
|
41
|
+
geolocation: dict[str, float] | None
|
|
42
|
+
timezone_id: str | None
|
|
43
|
+
locale: str | None
|
|
44
|
+
color_scheme: str | None
|
|
45
|
+
viewport: dict[str, int] | None
|
|
46
|
+
referer: str | None
|
|
47
|
+
|
|
48
|
+
listing: bool | None
|
|
49
|
+
auto_report: bool | dict[str, str] | None
|
|
47
50
|
|
|
48
51
|
|
|
49
52
|
class CompareSettings(TypedDict, total=False):
|
|
50
53
|
'''The settings that can be passed to the compare method on lookyloo side to filter out some differences'''
|
|
51
54
|
|
|
52
|
-
ressources_ignore_domains:
|
|
53
|
-
ressources_ignore_regexes:
|
|
55
|
+
ressources_ignore_domains: list[str] | None
|
|
56
|
+
ressources_ignore_regexes: list[str] | None
|
|
54
57
|
|
|
55
58
|
|
|
56
59
|
class Lookyloo():
|
|
57
60
|
|
|
58
|
-
def __init__(self, root_url: str='https://lookyloo.circl.lu/', useragent:
|
|
59
|
-
*, proxies:
|
|
61
|
+
def __init__(self, root_url: str='https://lookyloo.circl.lu/', useragent: str | None=None,
|
|
62
|
+
*, proxies: dict[str, str] | None=None):
|
|
60
63
|
'''Query a specific lookyloo instance.
|
|
61
64
|
|
|
62
65
|
:param root_url: URL of the instance to query.
|
|
@@ -73,7 +76,7 @@ class Lookyloo():
|
|
|
73
76
|
self.session.headers['user-agent'] = useragent if useragent else f'PyLookyloo / {version("pylookyloo")}'
|
|
74
77
|
if proxies:
|
|
75
78
|
self.session.proxies.update(proxies)
|
|
76
|
-
self.apikey:
|
|
79
|
+
self.apikey: str | None = None
|
|
77
80
|
|
|
78
81
|
@property
|
|
79
82
|
def is_up(self) -> bool:
|
|
@@ -84,7 +87,7 @@ class Lookyloo():
|
|
|
84
87
|
return False
|
|
85
88
|
return r.status_code == 200
|
|
86
89
|
|
|
87
|
-
def get_status(self, tree_uuid: str) ->
|
|
90
|
+
def get_status(self, tree_uuid: str) -> dict[str, Any]:
|
|
88
91
|
'''Get the status of a capture:
|
|
89
92
|
* -1: Unknown capture.
|
|
90
93
|
* 0: The capture is queued up but not processed yet.
|
|
@@ -94,24 +97,24 @@ class Lookyloo():
|
|
|
94
97
|
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', tree_uuid, 'status'))))
|
|
95
98
|
return r.json()
|
|
96
99
|
|
|
97
|
-
def get_capture_stats(self, tree_uuid: str) ->
|
|
100
|
+
def get_capture_stats(self, tree_uuid: str) -> dict[str, Any]:
|
|
98
101
|
'''Get statistics of the capture'''
|
|
99
102
|
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', tree_uuid, 'stats'))))
|
|
100
103
|
return r.json()
|
|
101
104
|
|
|
102
|
-
def get_info(self, tree_uuid: str) ->
|
|
105
|
+
def get_info(self, tree_uuid: str) -> dict[str, Any]:
|
|
103
106
|
'''Get information about the capture (url, timestamp, user agent)'''
|
|
104
107
|
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', tree_uuid, 'info'))))
|
|
105
108
|
return r.json()
|
|
106
109
|
|
|
107
|
-
def get_comparables(self, tree_uuid: str) ->
|
|
110
|
+
def get_comparables(self, tree_uuid: str) -> dict[str, Any]:
|
|
108
111
|
'''Get comparable information from the capture'''
|
|
109
112
|
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', tree_uuid, 'comparables'))))
|
|
110
113
|
return r.json()
|
|
111
114
|
|
|
112
|
-
def enqueue(self, url:
|
|
113
|
-
document:
|
|
114
|
-
document_name:
|
|
115
|
+
def enqueue(self, url: str | None=None, quiet: bool=False, # type: ignore[no-untyped-def]
|
|
116
|
+
document: Path | BytesIO | None=None,
|
|
117
|
+
document_name: str | None=None, **kwargs) -> str:
|
|
115
118
|
'''Enqueue an URL.
|
|
116
119
|
|
|
117
120
|
:param url: URL to enqueue
|
|
@@ -126,50 +129,50 @@ class Lookyloo():
|
|
|
126
129
|
|
|
127
130
|
@overload
|
|
128
131
|
def submit(self, *, quiet: bool=False,
|
|
129
|
-
capture_settings:
|
|
132
|
+
capture_settings: CaptureSettings | None=None) -> str:
|
|
130
133
|
...
|
|
131
134
|
|
|
132
135
|
@overload
|
|
133
136
|
def submit(self, *, quiet: bool=False,
|
|
134
|
-
url:
|
|
135
|
-
document_name:
|
|
136
|
-
browser:
|
|
137
|
-
user_agent:
|
|
138
|
-
proxy:
|
|
139
|
-
general_timeout_in_sec:
|
|
140
|
-
cookies:
|
|
141
|
-
headers:
|
|
142
|
-
http_credentials:
|
|
143
|
-
geolocation:
|
|
144
|
-
timezone_id:
|
|
145
|
-
locale:
|
|
146
|
-
color_scheme:
|
|
147
|
-
viewport:
|
|
148
|
-
referer:
|
|
149
|
-
listing:
|
|
150
|
-
auto_report:
|
|
137
|
+
url: str | None=None,
|
|
138
|
+
document_name: str | None=None, document: Path | BytesIO | None=None,
|
|
139
|
+
browser: str | None=None, device_name: str | None=None,
|
|
140
|
+
user_agent: str | None=None,
|
|
141
|
+
proxy: str | dict[str, str] | None=None,
|
|
142
|
+
general_timeout_in_sec: int | None=None,
|
|
143
|
+
cookies: list[dict[str, Any]] | None=None,
|
|
144
|
+
headers: str | dict[str, str] | None=None,
|
|
145
|
+
http_credentials: dict[str, int] | None=None,
|
|
146
|
+
geolocation: dict[str, float] | None=None,
|
|
147
|
+
timezone_id: str | None=None,
|
|
148
|
+
locale: str | None=None,
|
|
149
|
+
color_scheme: str | None=None,
|
|
150
|
+
viewport: dict[str, int] | None=None,
|
|
151
|
+
referer: str | None=None,
|
|
152
|
+
listing: bool | None=None,
|
|
153
|
+
auto_report: bool | dict[str, str] | None=None
|
|
151
154
|
) -> str:
|
|
152
155
|
...
|
|
153
156
|
|
|
154
157
|
def submit(self, *, quiet: bool=False,
|
|
155
|
-
capture_settings:
|
|
156
|
-
url:
|
|
157
|
-
document_name:
|
|
158
|
-
browser:
|
|
159
|
-
user_agent:
|
|
160
|
-
proxy:
|
|
161
|
-
general_timeout_in_sec:
|
|
162
|
-
cookies:
|
|
163
|
-
headers:
|
|
164
|
-
http_credentials:
|
|
165
|
-
geolocation:
|
|
166
|
-
timezone_id:
|
|
167
|
-
locale:
|
|
168
|
-
color_scheme:
|
|
169
|
-
viewport:
|
|
170
|
-
referer:
|
|
171
|
-
listing:
|
|
172
|
-
auto_report:
|
|
158
|
+
capture_settings: CaptureSettings | None=None,
|
|
159
|
+
url: str | None=None,
|
|
160
|
+
document_name: str | None=None, document: Path | BytesIO | None=None,
|
|
161
|
+
browser: str | None=None, device_name: str | None=None,
|
|
162
|
+
user_agent: str | None=None,
|
|
163
|
+
proxy: str | dict[str, str] | None=None,
|
|
164
|
+
general_timeout_in_sec: int | None=None,
|
|
165
|
+
cookies: list[dict[str, Any]] | None=None,
|
|
166
|
+
headers: str | dict[str, str] | None=None,
|
|
167
|
+
http_credentials: dict[str, int] | None=None,
|
|
168
|
+
geolocation: dict[str, float] | None=None,
|
|
169
|
+
timezone_id: str | None=None,
|
|
170
|
+
locale: str | None=None,
|
|
171
|
+
color_scheme: str | None=None,
|
|
172
|
+
viewport: dict[str, int] | None=None,
|
|
173
|
+
referer: str | None=None,
|
|
174
|
+
listing: bool | None=None,
|
|
175
|
+
auto_report: bool | dict[str, str] | None=None
|
|
173
176
|
) -> str:
|
|
174
177
|
'''Submit a URL to a lookyloo instance.
|
|
175
178
|
|
|
@@ -261,13 +264,13 @@ class Lookyloo():
|
|
|
261
264
|
return uuid
|
|
262
265
|
return urljoin(self.root_url, f'tree/{uuid}')
|
|
263
266
|
|
|
264
|
-
def get_apikey(self, username: str, password: str) ->
|
|
267
|
+
def get_apikey(self, username: str, password: str) -> dict[str, str]:
|
|
265
268
|
'''Get the API key for the given user.'''
|
|
266
269
|
to_post = {'username': username, 'password': password}
|
|
267
270
|
r = self.session.post(urljoin(self.root_url, str(PurePosixPath('json', 'get_token'))), json=to_post)
|
|
268
271
|
return r.json()
|
|
269
272
|
|
|
270
|
-
def init_apikey(self, username:
|
|
273
|
+
def init_apikey(self, username: str | None=None, password: str | None=None, apikey: str | None=None) -> None:
|
|
271
274
|
'''Init the API key for the current session. All the requests against lookyloo after this call will be authenticated.'''
|
|
272
275
|
if apikey:
|
|
273
276
|
self.apikey = apikey
|
|
@@ -282,12 +285,20 @@ class Lookyloo():
|
|
|
282
285
|
else:
|
|
283
286
|
raise AuthError('Unable to initialize API key')
|
|
284
287
|
|
|
285
|
-
def
|
|
288
|
+
def get_user_config(self) -> dict[str, Any] | None:
|
|
289
|
+
'''Get the configuration enforced by the server for the current user (requires an authenticated user, use init_apikey first)
|
|
290
|
+
'''
|
|
291
|
+
if not self.apikey:
|
|
292
|
+
raise AuthError('You need to initialize the apikey to use this method (see init_apikey)')
|
|
293
|
+
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', 'get_user_config'))))
|
|
294
|
+
return r.json()
|
|
295
|
+
|
|
296
|
+
def misp_export(self, tree_uuid: str) -> dict[str, Any]:
|
|
286
297
|
'''Export the capture in MISP format'''
|
|
287
298
|
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', tree_uuid, 'misp_export'))))
|
|
288
299
|
return r.json()
|
|
289
300
|
|
|
290
|
-
def misp_push(self, tree_uuid: str) ->
|
|
301
|
+
def misp_push(self, tree_uuid: str) -> dict[str, Any] | list[dict[str, Any]]:
|
|
291
302
|
'''Push the capture to a pre-configured MISP instance (requires an authenticated user, use init_apikey first)
|
|
292
303
|
Note: if the response is a dict, it is an error mesage. If it is a list, it's a list of MISP event.
|
|
293
304
|
'''
|
|
@@ -296,7 +307,7 @@ class Lookyloo():
|
|
|
296
307
|
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', tree_uuid, 'misp_push'))))
|
|
297
308
|
return r.json()
|
|
298
309
|
|
|
299
|
-
def trigger_modules(self, tree_uuid: str, force: bool=False) ->
|
|
310
|
+
def trigger_modules(self, tree_uuid: str, force: bool=False) -> dict[str, Any]:
|
|
300
311
|
'''Trigger all the available 3rd party modules on the given capture.
|
|
301
312
|
:param force: Trigger the modules even if they were already triggered today.
|
|
302
313
|
'''
|
|
@@ -305,21 +316,21 @@ class Lookyloo():
|
|
|
305
316
|
json=to_send)
|
|
306
317
|
return r.json()
|
|
307
318
|
|
|
308
|
-
def rebuild_capture(self, tree_uuid: str) ->
|
|
319
|
+
def rebuild_capture(self, tree_uuid: str) -> dict[str, str]:
|
|
309
320
|
'''Force rebuild a capture (requires an authenticated user, use init_apikey first)'''
|
|
310
321
|
if not self.apikey:
|
|
311
322
|
raise AuthError('You need to initialize the apikey to use this method (see init_apikey)')
|
|
312
|
-
r = self.session.
|
|
323
|
+
r = self.session.post(urljoin(self.root_url, str(PurePosixPath('admin', tree_uuid, 'rebuild'))))
|
|
313
324
|
return r.json()
|
|
314
325
|
|
|
315
|
-
def hide_capture(self, tree_uuid: str) ->
|
|
326
|
+
def hide_capture(self, tree_uuid: str) -> dict[str, str]:
|
|
316
327
|
'''Hide a capture from the index page (requires an authenticated user, use init_apikey first)'''
|
|
317
328
|
if not self.apikey:
|
|
318
329
|
raise AuthError('You need to initialize the apikey to use this method (see init_apikey)')
|
|
319
|
-
r = self.session.
|
|
330
|
+
r = self.session.post(urljoin(self.root_url, str(PurePosixPath('admin', tree_uuid, 'hide'))))
|
|
320
331
|
return r.json()
|
|
321
332
|
|
|
322
|
-
def get_redirects(self, capture_uuid: str) ->
|
|
333
|
+
def get_redirects(self, capture_uuid: str) -> dict[str, Any]:
|
|
323
334
|
'''Returns the initial redirects.
|
|
324
335
|
|
|
325
336
|
:param capture_uuid: UUID of the capture
|
|
@@ -327,7 +338,7 @@ class Lookyloo():
|
|
|
327
338
|
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', capture_uuid, 'redirects'))))
|
|
328
339
|
return r.json()
|
|
329
340
|
|
|
330
|
-
def get_urls(self, capture_uuid: str) ->
|
|
341
|
+
def get_urls(self, capture_uuid: str) -> dict[str, Any]:
|
|
331
342
|
'''Returns all the URLs seen during the capture.
|
|
332
343
|
|
|
333
344
|
:param capture_uuid: UUID of the capture
|
|
@@ -335,7 +346,7 @@ class Lookyloo():
|
|
|
335
346
|
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', capture_uuid, 'urls'))))
|
|
336
347
|
return r.json()
|
|
337
348
|
|
|
338
|
-
def get_hostnames(self, capture_uuid: str) ->
|
|
349
|
+
def get_hostnames(self, capture_uuid: str) -> dict[str, Any]:
|
|
339
350
|
'''Returns all the hostnames seen during the capture.
|
|
340
351
|
|
|
341
352
|
:param capture_uuid: UUID of the capture
|
|
@@ -359,7 +370,7 @@ class Lookyloo():
|
|
|
359
370
|
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('bin', capture_uuid, 'data'))))
|
|
360
371
|
return BytesIO(r.content)
|
|
361
372
|
|
|
362
|
-
def get_cookies(self, capture_uuid: str) ->
|
|
373
|
+
def get_cookies(self, capture_uuid: str) -> list[dict[str, str]]:
|
|
363
374
|
'''Returns the complete cookies jar.
|
|
364
375
|
|
|
365
376
|
:param capture_uuid: UUID of the capture
|
|
@@ -382,7 +393,7 @@ class Lookyloo():
|
|
|
382
393
|
:param algorithm: The algorithm of the hashes
|
|
383
394
|
:param hashes_only: If False, will also return the URLs related to the hashes
|
|
384
395
|
'''
|
|
385
|
-
params:
|
|
396
|
+
params: dict[str, str | int] = {'algorithm': algorithm, 'hashes_only': int(hashes_only)}
|
|
386
397
|
|
|
387
398
|
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', capture_uuid, 'hashes'))), params=params)
|
|
388
399
|
return r.json()
|
|
@@ -395,7 +406,7 @@ class Lookyloo():
|
|
|
395
406
|
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('bin', capture_uuid, 'export'))))
|
|
396
407
|
return BytesIO(r.content)
|
|
397
408
|
|
|
398
|
-
def get_hash_occurrences(self, h: str) ->
|
|
409
|
+
def get_hash_occurrences(self, h: str) -> dict[str, Any]:
|
|
399
410
|
'''Returns the base 64 body related the the hash, and a list of all the captures containing that hash.
|
|
400
411
|
|
|
401
412
|
:param h: sha512 to search
|
|
@@ -403,7 +414,7 @@ class Lookyloo():
|
|
|
403
414
|
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', 'hash_info', h))))
|
|
404
415
|
return r.json()
|
|
405
416
|
|
|
406
|
-
def get_url_occurrences(self, url: str, limit: int=20, cached_captures_only: bool=True) ->
|
|
417
|
+
def get_url_occurrences(self, url: str, limit: int=20, cached_captures_only: bool=True) -> dict[str, Any]:
|
|
407
418
|
'''Returns all the captures contining the URL
|
|
408
419
|
|
|
409
420
|
:param url: URL to lookup
|
|
@@ -414,7 +425,7 @@ class Lookyloo():
|
|
|
414
425
|
json={'url': url, 'limit': limit})
|
|
415
426
|
return r.json()
|
|
416
427
|
|
|
417
|
-
def get_hostname_occurrences(self, hostname: str, with_urls_occurrences: bool=False, limit: int=20, cached_captures_only: bool=True) ->
|
|
428
|
+
def get_hostname_occurrences(self, hostname: str, with_urls_occurrences: bool=False, limit: int=20, cached_captures_only: bool=True) -> dict[str, Any]:
|
|
418
429
|
'''Returns all the captures contining the hostname. It will be pretty slow on very common domains.
|
|
419
430
|
|
|
420
431
|
:param hostname: Hostname to lookup
|
|
@@ -426,22 +437,31 @@ class Lookyloo():
|
|
|
426
437
|
json={'hostname': hostname, 'with_urls_occurrences': with_urls_occurrences, 'limit': limit})
|
|
427
438
|
return r.json()
|
|
428
439
|
|
|
429
|
-
def get_stats(self) ->
|
|
440
|
+
def get_stats(self) -> dict[str, Any]:
|
|
430
441
|
'''Returns all the captures contining the URL'''
|
|
431
442
|
|
|
432
443
|
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', 'stats'))))
|
|
433
444
|
return r.json()
|
|
434
445
|
|
|
435
|
-
|
|
446
|
+
@overload
|
|
447
|
+
def get_takedown_information(self, capture_uuid: str, filter_contacts: Literal[True]) -> list[str]:
|
|
448
|
+
...
|
|
449
|
+
|
|
450
|
+
@overload
|
|
451
|
+
def get_takedown_information(self, capture_uuid: str, filter_contacts: Literal[False]=False) -> list[dict[str, Any]]:
|
|
452
|
+
...
|
|
453
|
+
|
|
454
|
+
def get_takedown_information(self, capture_uuid: str, filter_contacts: bool=False) -> list[dict[str, Any]] | list[str]:
|
|
436
455
|
'''Returns information required to request a takedown for a capture
|
|
437
456
|
|
|
438
457
|
:param capture_uuid: UUID of the capture
|
|
458
|
+
:param filter_contacts: If True, will only return the contact emails and filter out the invalid ones.
|
|
439
459
|
'''
|
|
440
460
|
r = self.session.post(urljoin(self.root_url, str(PurePosixPath('json', 'takedown'))),
|
|
441
|
-
json={'capture_uuid': capture_uuid})
|
|
461
|
+
json={'capture_uuid': capture_uuid, 'filter': filter_contacts})
|
|
442
462
|
return r.json()
|
|
443
463
|
|
|
444
|
-
def compare_captures(self, capture_left: str, capture_right: str, /, *, compare_settings:
|
|
464
|
+
def compare_captures(self, capture_left: str, capture_right: str, /, *, compare_settings: CompareSettings | None=None) -> dict[str, Any]:
|
|
445
465
|
'''Compares two captures
|
|
446
466
|
|
|
447
467
|
:param capture_left: UUID of the capture to compare from
|
|
@@ -453,3 +473,119 @@ class Lookyloo():
|
|
|
453
473
|
'capture_right': capture_right,
|
|
454
474
|
'compare_settings': compare_settings})
|
|
455
475
|
return r.json()
|
|
476
|
+
|
|
477
|
+
def get_modules_responses(self, tree_uuid: str) -> dict[str, Any]:
|
|
478
|
+
'''Returns information from the 3rd party modules
|
|
479
|
+
|
|
480
|
+
:param capture_uuid: UUID of the capture
|
|
481
|
+
'''
|
|
482
|
+
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', tree_uuid, 'modules'))))
|
|
483
|
+
return r.json()
|
|
484
|
+
|
|
485
|
+
def send_mail(self, tree_uuid: str, email: str = '', comment: str | None = None) -> bool | dict[str, Any]:
|
|
486
|
+
'''Reports a capture by sending an email to the investigation team
|
|
487
|
+
|
|
488
|
+
:param tree_uuid: UUID of the capture
|
|
489
|
+
:param email: Email of the reporter, used by the analyst to get in touch
|
|
490
|
+
:param comment: Description of the URL, will be given to the analyst
|
|
491
|
+
'''
|
|
492
|
+
to_send = {'email': email}
|
|
493
|
+
if comment:
|
|
494
|
+
to_send['comment'] = comment
|
|
495
|
+
r = self.session.post(urljoin(self.root_url, str(PurePosixPath('json', tree_uuid, 'report'))), json=to_send)
|
|
496
|
+
return r.json()
|
|
497
|
+
|
|
498
|
+
def get_recent_captures(self, timestamp: str | datetime | float | None=None) -> list[str]:
|
|
499
|
+
'''Gets the uuids of the most recent captures
|
|
500
|
+
|
|
501
|
+
:param timestamp: Timestamp of the capture
|
|
502
|
+
'''
|
|
503
|
+
if not timestamp:
|
|
504
|
+
url = urljoin(self.root_url, str(PurePosixPath('json', 'recent_captures')))
|
|
505
|
+
else:
|
|
506
|
+
if isinstance(timestamp, datetime):
|
|
507
|
+
timestamp = timestamp.timestamp()
|
|
508
|
+
url = urljoin(self.root_url, str(PurePosixPath('json', 'recent_captures', str(timestamp))))
|
|
509
|
+
r = self.session.get(url)
|
|
510
|
+
return r.json()
|
|
511
|
+
|
|
512
|
+
@overload
|
|
513
|
+
def upload_capture(self, *, quiet: Literal[True],
|
|
514
|
+
listing: bool = False,
|
|
515
|
+
full_capture: Path | BytesIO | str | None = None,
|
|
516
|
+
har: Path | BytesIO | str | None = None,
|
|
517
|
+
html: Path | BytesIO | str | None = None,
|
|
518
|
+
last_redirected_url: str | None = None,
|
|
519
|
+
screenshot: Path | BytesIO | str | None = None) -> str:
|
|
520
|
+
...
|
|
521
|
+
|
|
522
|
+
@overload
|
|
523
|
+
def upload_capture(self, *, quiet: Literal[False]=False,
|
|
524
|
+
listing: bool = False,
|
|
525
|
+
full_capture: Path | BytesIO | str | None = None,
|
|
526
|
+
har: Path | BytesIO | str | None = None,
|
|
527
|
+
html: Path | BytesIO | str | None = None,
|
|
528
|
+
last_redirected_url: str | None = None,
|
|
529
|
+
screenshot: Path | BytesIO | str | None = None) -> tuple[str, dict[str, str]]:
|
|
530
|
+
...
|
|
531
|
+
|
|
532
|
+
def upload_capture(self, *, quiet: bool = False,
|
|
533
|
+
listing: bool = False,
|
|
534
|
+
full_capture: Path | BytesIO | str | None = None,
|
|
535
|
+
har: Path | BytesIO | str | None = None,
|
|
536
|
+
html: Path | BytesIO | str | None = None,
|
|
537
|
+
last_redirected_url: str | None = None,
|
|
538
|
+
screenshot: Path | BytesIO | str | None = None) -> str | tuple[str, dict[str, str]]:
|
|
539
|
+
'''Upload a capture via har-file and others
|
|
540
|
+
|
|
541
|
+
:param quiet: Returns the UUID only, instead of the the UUID and the potential error / warning messages
|
|
542
|
+
:param listing: if true the capture should be public, else private - overwritten if the full_capture is given and it contains no_index
|
|
543
|
+
:param full_capture: path to the capture made by another instance
|
|
544
|
+
:param har: Harfile of the capture
|
|
545
|
+
:param html: rendered HTML of the capture
|
|
546
|
+
:param last_redirected_url: The landing page of the capture
|
|
547
|
+
:param screenshot: Screenshot of the capture
|
|
548
|
+
'''
|
|
549
|
+
def encode_document(document: Path | BytesIO | str) -> str:
|
|
550
|
+
if isinstance(document, str):
|
|
551
|
+
if not os.path.exists(document):
|
|
552
|
+
raise FileNotFoundError(f'{document} does not exist')
|
|
553
|
+
document = Path(document)
|
|
554
|
+
if isinstance(document, Path):
|
|
555
|
+
with document.open('rb') as f:
|
|
556
|
+
document = BytesIO(f.read())
|
|
557
|
+
return base64.b64encode(document.getvalue()).decode()
|
|
558
|
+
|
|
559
|
+
to_send: dict[str, Any] = {'listing': listing}
|
|
560
|
+
|
|
561
|
+
if full_capture:
|
|
562
|
+
b64_full_capture = encode_document(full_capture)
|
|
563
|
+
to_send['full_capture'] = b64_full_capture
|
|
564
|
+
elif har:
|
|
565
|
+
b64_har = encode_document(har)
|
|
566
|
+
to_send['har_file'] = b64_har
|
|
567
|
+
|
|
568
|
+
if html:
|
|
569
|
+
b64_html = encode_document(html)
|
|
570
|
+
to_send['html_file'] = b64_html
|
|
571
|
+
|
|
572
|
+
if last_redirected_url:
|
|
573
|
+
to_send['landing_page'] = last_redirected_url
|
|
574
|
+
|
|
575
|
+
if screenshot:
|
|
576
|
+
b64_screenshot = encode_document(screenshot)
|
|
577
|
+
to_send['screenshot_file'] = b64_screenshot
|
|
578
|
+
else:
|
|
579
|
+
raise PyLookylooError("Full capture or at least har-file are required")
|
|
580
|
+
|
|
581
|
+
r = self.session.post(urljoin(self.root_url, str(PurePosixPath('json', 'upload'))), json=to_send)
|
|
582
|
+
r.raise_for_status()
|
|
583
|
+
json_response = r.json()
|
|
584
|
+
uuid = json_response['uuid']
|
|
585
|
+
messages = json_response['messages']
|
|
586
|
+
|
|
587
|
+
if not uuid:
|
|
588
|
+
raise PyLookylooError('Unable to get UUID from lookyloo instance.')
|
|
589
|
+
if quiet:
|
|
590
|
+
return uuid
|
|
591
|
+
return uuid, messages
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pylookyloo
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.25.0
|
|
4
4
|
Summary: Python CLI and module for Lookyloo
|
|
5
5
|
Home-page: https://github.com/lookyloo/PyLookyloo
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -25,7 +25,7 @@ Classifier: Topic :: Security
|
|
|
25
25
|
Provides-Extra: docs
|
|
26
26
|
Requires-Dist: Sphinx (<7.2) ; (python_version < "3.9") and (extra == "docs")
|
|
27
27
|
Requires-Dist: Sphinx (>=7.2,<8.0) ; (python_version >= "3.9") and (extra == "docs")
|
|
28
|
-
Requires-Dist: requests (>=2.
|
|
28
|
+
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
|
29
29
|
Project-URL: Documentation, https://pylookyloo.readthedocs.io/en/latest/
|
|
30
30
|
Project-URL: Repository, https://github.com/lookyloo/PyLookyloo
|
|
31
31
|
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pylookyloo/__init__.py,sha256=_JYXwXHL7ShZkeruvGd8qDTpxNRfuDjvV65SOMMU6yc,1922
|
|
2
|
+
pylookyloo/api.py,sha256=YLMcYNZa9kE0U-3v569amCUtRKUWSGphHf1BPu4Zs1I,27335
|
|
3
|
+
pylookyloo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
pylookyloo-1.25.0.dist-info/LICENSE,sha256=4C4hLYrIkUD96Ggk-y_Go1Qf7PBZrEm9PSeTGe2nd4s,1516
|
|
5
|
+
pylookyloo-1.25.0.dist-info/METADATA,sha256=GCazh6CEBrAsHOMVxlaZTx-alAU2H4YNSQUHrAIb5Qs,2340
|
|
6
|
+
pylookyloo-1.25.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
7
|
+
pylookyloo-1.25.0.dist-info/entry_points.txt,sha256=y2c0Ujg8co6Xyf7MoxStVU-fLQMZBSGAg-KFidmsha4,44
|
|
8
|
+
pylookyloo-1.25.0.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
pylookyloo/__init__.py,sha256=_JYXwXHL7ShZkeruvGd8qDTpxNRfuDjvV65SOMMU6yc,1922
|
|
2
|
-
pylookyloo/api.py,sha256=pUNC0DoaxYlvhOkDKHF4bSqRwABiCv1UkZgPz8zRIBM,21392
|
|
3
|
-
pylookyloo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
pylookyloo-1.23.1.dist-info/LICENSE,sha256=4C4hLYrIkUD96Ggk-y_Go1Qf7PBZrEm9PSeTGe2nd4s,1516
|
|
5
|
-
pylookyloo-1.23.1.dist-info/METADATA,sha256=qltwCMFtvxCE5IO22c5uEaAukdc1l0h_hSEFVdoRI6Q,2340
|
|
6
|
-
pylookyloo-1.23.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
7
|
-
pylookyloo-1.23.1.dist-info/entry_points.txt,sha256=y2c0Ujg8co6Xyf7MoxStVU-fLQMZBSGAg-KFidmsha4,44
|
|
8
|
-
pylookyloo-1.23.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|