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 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 Optional, Dict, Any, List, Union, TypedDict, overload
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: Optional[str]
28
- document_name: Optional[str]
29
- document: Optional[str]
30
- browser: Optional[str]
31
- device_name: Optional[str]
32
- user_agent: Optional[str]
33
- proxy: Optional[Union[str, Dict[str, str]]]
34
- general_timeout_in_sec: Optional[int]
35
- cookies: Optional[List[Dict[str, Any]]]
36
- headers: Optional[Union[str, Dict[str, str]]]
37
- http_credentials: Optional[Dict[str, int]]
38
- geolocation: Optional[Dict[str, float]]
39
- timezone_id: Optional[str]
40
- locale: Optional[str]
41
- color_scheme: Optional[str]
42
- viewport: Optional[Dict[str, int]]
43
- referer: Optional[str]
44
-
45
- listing: Optional[bool]
46
- auto_report: Optional[Union[bool, Dict[str, str]]]
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: Optional[List[str]]
53
- ressources_ignore_regexes: Optional[List[str]]
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: Optional[str]=None,
59
- *, proxies: Optional[Dict[str, str]]=None):
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: Optional[str] = None
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) -> Dict[str, Any]:
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) -> Dict[str, Any]:
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) -> Dict[str, Any]:
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) -> Dict[str, Any]:
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: Optional[str]=None, quiet: bool=False, # type: ignore[no-untyped-def]
113
- document: Optional[Union[Path, BytesIO]]=None,
114
- document_name: Optional[str]=None, **kwargs) -> str:
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: Optional[CaptureSettings]=None) -> str:
132
+ capture_settings: CaptureSettings | None=None) -> str:
130
133
  ...
131
134
 
132
135
  @overload
133
136
  def submit(self, *, quiet: bool=False,
134
- url: Optional[str]=None,
135
- document_name: Optional[str]=None, document: Optional[Union[Path, BytesIO]]=None,
136
- browser: Optional[str]=None, device_name: Optional[str]=None,
137
- user_agent: Optional[str]=None,
138
- proxy: Optional[Union[str, Dict[str, str]]]=None,
139
- general_timeout_in_sec: Optional[int]=None,
140
- cookies: Optional[List[Dict[str, Any]]]=None,
141
- headers: Optional[Union[str, Dict[str, str]]]=None,
142
- http_credentials: Optional[Dict[str, int]]=None,
143
- geolocation: Optional[Dict[str, float]]=None,
144
- timezone_id: Optional[str]=None,
145
- locale: Optional[str]=None,
146
- color_scheme: Optional[str]=None,
147
- viewport: Optional[Dict[str, int]]=None,
148
- referer: Optional[str]=None,
149
- listing: Optional[bool]=None,
150
- auto_report: Optional[Union[bool, Dict[str, str]]]=None
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: Optional[CaptureSettings]=None,
156
- url: Optional[str]=None,
157
- document_name: Optional[str]=None, document: Optional[Union[Path, BytesIO]]=None,
158
- browser: Optional[str]=None, device_name: Optional[str]=None,
159
- user_agent: Optional[str]=None,
160
- proxy: Optional[Union[str, Dict[str, str]]]=None,
161
- general_timeout_in_sec: Optional[int]=None,
162
- cookies: Optional[List[Dict[str, Any]]]=None,
163
- headers: Optional[Union[str, Dict[str, str]]]=None,
164
- http_credentials: Optional[Dict[str, int]]=None,
165
- geolocation: Optional[Dict[str, float]]=None,
166
- timezone_id: Optional[str]=None,
167
- locale: Optional[str]=None,
168
- color_scheme: Optional[str]=None,
169
- viewport: Optional[Dict[str, int]]=None,
170
- referer: Optional[str]=None,
171
- listing: Optional[bool]=None,
172
- auto_report: Optional[Union[bool, Dict[str, str]]]=None
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) -> Dict[str, 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: Optional[str]=None, password: Optional[str]=None, apikey: Optional[str]=None) -> None:
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 misp_export(self, tree_uuid: str) -> Dict[str, Any]:
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) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
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) -> Dict[str, Any]:
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) -> Dict[str, 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.get(urljoin(self.root_url, str(PurePosixPath('admin', tree_uuid, 'rebuild'))))
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) -> Dict[str, 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.get(urljoin(self.root_url, str(PurePosixPath('admin', tree_uuid, 'hide'))))
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) -> Dict[str, Any]:
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) -> Dict[str, Any]:
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) -> Dict[str, Any]:
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) -> List[Dict[str, 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: Dict[str, Union[str, int]] = {'algorithm': algorithm, 'hashes_only': int(hashes_only)}
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) -> Dict[str, Any]:
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) -> Dict[str, Any]:
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) -> Dict[str, Any]:
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) -> Dict[str, Any]:
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
- def get_takedown_information(self, capture_uuid: str) -> Dict[str, Any]:
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: Optional[CompareSettings]=None) -> Dict[str, Any]:
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.23.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.31.0,<3.0.0)
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,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.8.1
2
+ Generator: poetry-core 1.9.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -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,,