pylookyloo 1.24.0__py3-none-any.whl → 1.26.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pylookyloo might be problematic. Click here for more details.
- pylookyloo/api.py +148 -3
- {pylookyloo-1.24.0.dist-info → pylookyloo-1.26.1.dist-info}/METADATA +4 -3
- pylookyloo-1.26.1.dist-info/RECORD +8 -0
- pylookyloo-1.24.0.dist-info/RECORD +0 -8
- {pylookyloo-1.24.0.dist-info → pylookyloo-1.26.1.dist-info}/LICENSE +0 -0
- {pylookyloo-1.24.0.dist-info → pylookyloo-1.26.1.dist-info}/WHEEL +0 -0
- {pylookyloo-1.24.0.dist-info → pylookyloo-1.26.1.dist-info}/entry_points.txt +0 -0
pylookyloo/api.py
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import os
|
|
5
6
|
import base64
|
|
6
7
|
import warnings
|
|
7
8
|
|
|
9
|
+
from datetime import datetime
|
|
8
10
|
from importlib.metadata import version
|
|
9
11
|
from io import BytesIO, StringIO
|
|
10
|
-
from typing import Any, TypedDict, overload
|
|
12
|
+
from typing import Any, TypedDict, overload, Literal
|
|
11
13
|
from urllib.parse import urljoin, urlparse
|
|
12
14
|
from pathlib import PurePosixPath, Path
|
|
13
15
|
|
|
@@ -40,6 +42,7 @@ class CaptureSettings(TypedDict, total=False):
|
|
|
40
42
|
timezone_id: str | None
|
|
41
43
|
locale: str | None
|
|
42
44
|
color_scheme: str | None
|
|
45
|
+
java_script_enabled: bool
|
|
43
46
|
viewport: dict[str, int] | None
|
|
44
47
|
referer: str | None
|
|
45
48
|
|
|
@@ -145,6 +148,7 @@ class Lookyloo():
|
|
|
145
148
|
timezone_id: str | None=None,
|
|
146
149
|
locale: str | None=None,
|
|
147
150
|
color_scheme: str | None=None,
|
|
151
|
+
java_script_enabled: bool=True,
|
|
148
152
|
viewport: dict[str, int] | None=None,
|
|
149
153
|
referer: str | None=None,
|
|
150
154
|
listing: bool | None=None,
|
|
@@ -167,6 +171,7 @@ class Lookyloo():
|
|
|
167
171
|
timezone_id: str | None=None,
|
|
168
172
|
locale: str | None=None,
|
|
169
173
|
color_scheme: str | None=None,
|
|
174
|
+
java_script_enabled: bool | None=None,
|
|
170
175
|
viewport: dict[str, int] | None=None,
|
|
171
176
|
referer: str | None=None,
|
|
172
177
|
listing: bool | None=None,
|
|
@@ -193,6 +198,7 @@ class Lookyloo():
|
|
|
193
198
|
:param timezone_id: The timezone, warning, it m ust be a valid timezone (continent/city)
|
|
194
199
|
:param locale: The locale of the browser
|
|
195
200
|
:param color_scheme: The prefered color scheme of the browser (light or dark)
|
|
201
|
+
:param java_script_enabled: If False, no JS will run during the capture.
|
|
196
202
|
:param viewport: The viewport of the browser used for capturing
|
|
197
203
|
:param referer: The referer URL for the capture
|
|
198
204
|
:param listing: If False, the capture will be not be on the publicly accessible index page of lookyloo
|
|
@@ -244,6 +250,8 @@ class Lookyloo():
|
|
|
244
250
|
to_send['locale'] = locale
|
|
245
251
|
if color_scheme:
|
|
246
252
|
to_send['color_scheme'] = color_scheme
|
|
253
|
+
if java_script_enabled is not None:
|
|
254
|
+
to_send['java_script_enabled'] = java_script_enabled
|
|
247
255
|
if viewport:
|
|
248
256
|
to_send['viewport'] = viewport
|
|
249
257
|
if referer:
|
|
@@ -283,6 +291,14 @@ class Lookyloo():
|
|
|
283
291
|
else:
|
|
284
292
|
raise AuthError('Unable to initialize API key')
|
|
285
293
|
|
|
294
|
+
def get_user_config(self) -> dict[str, Any] | None:
|
|
295
|
+
'''Get the configuration enforced by the server for the current user (requires an authenticated user, use init_apikey first)
|
|
296
|
+
'''
|
|
297
|
+
if not self.apikey:
|
|
298
|
+
raise AuthError('You need to initialize the apikey to use this method (see init_apikey)')
|
|
299
|
+
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', 'get_user_config'))))
|
|
300
|
+
return r.json()
|
|
301
|
+
|
|
286
302
|
def misp_export(self, tree_uuid: str) -> dict[str, Any]:
|
|
287
303
|
'''Export the capture in MISP format'''
|
|
288
304
|
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', tree_uuid, 'misp_export'))))
|
|
@@ -320,6 +336,13 @@ class Lookyloo():
|
|
|
320
336
|
r = self.session.post(urljoin(self.root_url, str(PurePosixPath('admin', tree_uuid, 'hide'))))
|
|
321
337
|
return r.json()
|
|
322
338
|
|
|
339
|
+
def remove_capture(self, tree_uuid: str) -> dict[str, str]:
|
|
340
|
+
'''Remove a capture, it will be impossible to get it by UUID (requires an authenticated user, use init_apikey first)'''
|
|
341
|
+
if not self.apikey:
|
|
342
|
+
raise AuthError('You need to initialize the apikey to use this method (see init_apikey)')
|
|
343
|
+
r = self.session.post(urljoin(self.root_url, str(PurePosixPath('admin', tree_uuid, 'remove'))))
|
|
344
|
+
return r.json()
|
|
345
|
+
|
|
323
346
|
def get_redirects(self, capture_uuid: str) -> dict[str, Any]:
|
|
324
347
|
'''Returns the initial redirects.
|
|
325
348
|
|
|
@@ -433,13 +456,22 @@ class Lookyloo():
|
|
|
433
456
|
r = self.session.get(urljoin(self.root_url, str(PurePosixPath('json', 'stats'))))
|
|
434
457
|
return r.json()
|
|
435
458
|
|
|
436
|
-
|
|
459
|
+
@overload
|
|
460
|
+
def get_takedown_information(self, capture_uuid: str, filter_contacts: Literal[True]) -> list[str]:
|
|
461
|
+
...
|
|
462
|
+
|
|
463
|
+
@overload
|
|
464
|
+
def get_takedown_information(self, capture_uuid: str, filter_contacts: Literal[False]=False) -> list[dict[str, Any]]:
|
|
465
|
+
...
|
|
466
|
+
|
|
467
|
+
def get_takedown_information(self, capture_uuid: str, filter_contacts: bool=False) -> list[dict[str, Any]] | list[str]:
|
|
437
468
|
'''Returns information required to request a takedown for a capture
|
|
438
469
|
|
|
439
470
|
:param capture_uuid: UUID of the capture
|
|
471
|
+
:param filter_contacts: If True, will only return the contact emails and filter out the invalid ones.
|
|
440
472
|
'''
|
|
441
473
|
r = self.session.post(urljoin(self.root_url, str(PurePosixPath('json', 'takedown'))),
|
|
442
|
-
json={'capture_uuid': capture_uuid})
|
|
474
|
+
json={'capture_uuid': capture_uuid, 'filter': filter_contacts})
|
|
443
475
|
return r.json()
|
|
444
476
|
|
|
445
477
|
def compare_captures(self, capture_left: str, capture_right: str, /, *, compare_settings: CompareSettings | None=None) -> dict[str, Any]:
|
|
@@ -464,8 +496,121 @@ class Lookyloo():
|
|
|
464
496
|
return r.json()
|
|
465
497
|
|
|
466
498
|
def send_mail(self, tree_uuid: str, email: str = '', comment: str | None = None) -> bool | dict[str, Any]:
|
|
499
|
+
'''Reports a capture by sending an email to the investigation team
|
|
500
|
+
|
|
501
|
+
:param tree_uuid: UUID of the capture
|
|
502
|
+
:param email: Email of the reporter, used by the analyst to get in touch
|
|
503
|
+
:param comment: Description of the URL, will be given to the analyst
|
|
504
|
+
'''
|
|
467
505
|
to_send = {'email': email}
|
|
468
506
|
if comment:
|
|
469
507
|
to_send['comment'] = comment
|
|
470
508
|
r = self.session.post(urljoin(self.root_url, str(PurePosixPath('json', tree_uuid, 'report'))), json=to_send)
|
|
471
509
|
return r.json()
|
|
510
|
+
|
|
511
|
+
def get_recent_captures(self, timestamp: str | datetime | float | None=None) -> list[str]:
|
|
512
|
+
'''Gets the uuids of the most recent captures
|
|
513
|
+
|
|
514
|
+
:param timestamp: Oldest timestamp to consider
|
|
515
|
+
'''
|
|
516
|
+
if timestamp:
|
|
517
|
+
if isinstance(timestamp, datetime):
|
|
518
|
+
timestamp = timestamp.timestamp()
|
|
519
|
+
url = urljoin(self.root_url, str(PurePosixPath('json', 'recent_captures', str(timestamp))))
|
|
520
|
+
else:
|
|
521
|
+
url = urljoin(self.root_url, str(PurePosixPath('json', 'recent_captures')))
|
|
522
|
+
r = self.session.get(url)
|
|
523
|
+
return r.json()
|
|
524
|
+
|
|
525
|
+
def get_categories_captures(self, category: str | None=None) -> list[str] | dict[str, list[str]] | None:
|
|
526
|
+
'''Get uuids for a specific category or all categorized uuids if category is None
|
|
527
|
+
|
|
528
|
+
:param category: The category according to which the uuids are to be returned
|
|
529
|
+
'''
|
|
530
|
+
if category:
|
|
531
|
+
url = urljoin(self.root_url, str(PurePosixPath('json', 'categories', category)))
|
|
532
|
+
else:
|
|
533
|
+
url = urljoin(self.root_url, str(PurePosixPath('json', 'categories')))
|
|
534
|
+
r = self.session.get(url)
|
|
535
|
+
return r.json()
|
|
536
|
+
|
|
537
|
+
@overload
|
|
538
|
+
def upload_capture(self, *, quiet: Literal[True],
|
|
539
|
+
listing: bool = False,
|
|
540
|
+
full_capture: Path | BytesIO | str | None = None,
|
|
541
|
+
har: Path | BytesIO | str | None = None,
|
|
542
|
+
html: Path | BytesIO | str | None = None,
|
|
543
|
+
last_redirected_url: str | None = None,
|
|
544
|
+
screenshot: Path | BytesIO | str | None = None) -> str:
|
|
545
|
+
...
|
|
546
|
+
|
|
547
|
+
@overload
|
|
548
|
+
def upload_capture(self, *, quiet: Literal[False]=False,
|
|
549
|
+
listing: bool = False,
|
|
550
|
+
full_capture: Path | BytesIO | str | None = None,
|
|
551
|
+
har: Path | BytesIO | str | None = None,
|
|
552
|
+
html: Path | BytesIO | str | None = None,
|
|
553
|
+
last_redirected_url: str | None = None,
|
|
554
|
+
screenshot: Path | BytesIO | str | None = None) -> tuple[str, dict[str, str]]:
|
|
555
|
+
...
|
|
556
|
+
|
|
557
|
+
def upload_capture(self, *, quiet: bool = False,
|
|
558
|
+
listing: bool = False,
|
|
559
|
+
full_capture: Path | BytesIO | str | None = None,
|
|
560
|
+
har: Path | BytesIO | str | None = None,
|
|
561
|
+
html: Path | BytesIO | str | None = None,
|
|
562
|
+
last_redirected_url: str | None = None,
|
|
563
|
+
screenshot: Path | BytesIO | str | None = None) -> str | tuple[str, dict[str, str]]:
|
|
564
|
+
'''Upload a capture via har-file and others
|
|
565
|
+
|
|
566
|
+
:param quiet: Returns the UUID only, instead of the the UUID and the potential error / warning messages
|
|
567
|
+
:param listing: if true the capture should be public, else private - overwritten if the full_capture is given and it contains no_index
|
|
568
|
+
:param full_capture: path to the capture made by another instance
|
|
569
|
+
:param har: Harfile of the capture
|
|
570
|
+
:param html: rendered HTML of the capture
|
|
571
|
+
:param last_redirected_url: The landing page of the capture
|
|
572
|
+
:param screenshot: Screenshot of the capture
|
|
573
|
+
'''
|
|
574
|
+
def encode_document(document: Path | BytesIO | str) -> str:
|
|
575
|
+
if isinstance(document, str):
|
|
576
|
+
if not os.path.exists(document):
|
|
577
|
+
raise FileNotFoundError(f'{document} does not exist')
|
|
578
|
+
document = Path(document)
|
|
579
|
+
if isinstance(document, Path):
|
|
580
|
+
with document.open('rb') as f:
|
|
581
|
+
document = BytesIO(f.read())
|
|
582
|
+
return base64.b64encode(document.getvalue()).decode()
|
|
583
|
+
|
|
584
|
+
to_send: dict[str, Any] = {'listing': listing}
|
|
585
|
+
|
|
586
|
+
if full_capture:
|
|
587
|
+
b64_full_capture = encode_document(full_capture)
|
|
588
|
+
to_send['full_capture'] = b64_full_capture
|
|
589
|
+
elif har:
|
|
590
|
+
b64_har = encode_document(har)
|
|
591
|
+
to_send['har_file'] = b64_har
|
|
592
|
+
|
|
593
|
+
if html:
|
|
594
|
+
b64_html = encode_document(html)
|
|
595
|
+
to_send['html_file'] = b64_html
|
|
596
|
+
|
|
597
|
+
if last_redirected_url:
|
|
598
|
+
to_send['landing_page'] = last_redirected_url
|
|
599
|
+
|
|
600
|
+
if screenshot:
|
|
601
|
+
b64_screenshot = encode_document(screenshot)
|
|
602
|
+
to_send['screenshot_file'] = b64_screenshot
|
|
603
|
+
else:
|
|
604
|
+
raise PyLookylooError("Full capture or at least har-file are required")
|
|
605
|
+
|
|
606
|
+
r = self.session.post(urljoin(self.root_url, str(PurePosixPath('json', 'upload'))), json=to_send)
|
|
607
|
+
r.raise_for_status()
|
|
608
|
+
json_response = r.json()
|
|
609
|
+
uuid = json_response['uuid']
|
|
610
|
+
messages = json_response['messages']
|
|
611
|
+
|
|
612
|
+
if not uuid:
|
|
613
|
+
raise PyLookylooError('Unable to get UUID from lookyloo instance.')
|
|
614
|
+
if quiet:
|
|
615
|
+
return uuid
|
|
616
|
+
return uuid, messages
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pylookyloo
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.26.1
|
|
4
4
|
Summary: Python CLI and module for Lookyloo
|
|
5
5
|
Home-page: https://github.com/lookyloo/PyLookyloo
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -24,8 +24,9 @@ Classifier: Topic :: Internet
|
|
|
24
24
|
Classifier: Topic :: Security
|
|
25
25
|
Provides-Extra: docs
|
|
26
26
|
Requires-Dist: Sphinx (<7.2) ; (python_version < "3.9") and (extra == "docs")
|
|
27
|
-
Requires-Dist: Sphinx (>=7.2,<8.0) ; (python_version >= "3.9") and (extra == "docs")
|
|
28
|
-
Requires-Dist:
|
|
27
|
+
Requires-Dist: Sphinx (>=7.2,<8.0) ; (python_version >= "3.9" and python_version < "3.10") and (extra == "docs")
|
|
28
|
+
Requires-Dist: Sphinx (>=8,<9) ; (python_version >= "3.10") and (extra == "docs")
|
|
29
|
+
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
|
29
30
|
Project-URL: Documentation, https://pylookyloo.readthedocs.io/en/latest/
|
|
30
31
|
Project-URL: Repository, https://github.com/lookyloo/PyLookyloo
|
|
31
32
|
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pylookyloo/__init__.py,sha256=_JYXwXHL7ShZkeruvGd8qDTpxNRfuDjvV65SOMMU6yc,1922
|
|
2
|
+
pylookyloo/api.py,sha256=2U7ExW9Cr7dJUOXXop_UIULR4Zru4SogC2Cu_wJ6OcM,28683
|
|
3
|
+
pylookyloo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
pylookyloo-1.26.1.dist-info/LICENSE,sha256=4C4hLYrIkUD96Ggk-y_Go1Qf7PBZrEm9PSeTGe2nd4s,1516
|
|
5
|
+
pylookyloo-1.26.1.dist-info/METADATA,sha256=NwfOYpAMGHvhLk1kQzntIwKqR07HNrER1-0_SXzmGhY,2450
|
|
6
|
+
pylookyloo-1.26.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
7
|
+
pylookyloo-1.26.1.dist-info/entry_points.txt,sha256=y2c0Ujg8co6Xyf7MoxStVU-fLQMZBSGAg-KFidmsha4,44
|
|
8
|
+
pylookyloo-1.26.1.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
pylookyloo/__init__.py,sha256=_JYXwXHL7ShZkeruvGd8qDTpxNRfuDjvV65SOMMU6yc,1922
|
|
2
|
-
pylookyloo/api.py,sha256=nbuCJmeGy34ZCFH5kZeVwJlysgmn_h6ZiQrqYAmA5iM,21748
|
|
3
|
-
pylookyloo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
pylookyloo-1.24.0.dist-info/LICENSE,sha256=4C4hLYrIkUD96Ggk-y_Go1Qf7PBZrEm9PSeTGe2nd4s,1516
|
|
5
|
-
pylookyloo-1.24.0.dist-info/METADATA,sha256=GV8FRY1VU92q8jN8-Ko5Tohul-Vf0J0a7AM_S0Vsrvw,2340
|
|
6
|
-
pylookyloo-1.24.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
7
|
-
pylookyloo-1.24.0.dist-info/entry_points.txt,sha256=y2c0Ujg8co6Xyf7MoxStVU-fLQMZBSGAg-KFidmsha4,44
|
|
8
|
-
pylookyloo-1.24.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|