pywaybackup 1.2.3__tar.gz → 1.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/PKG-INFO +5 -1
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/README.md +4 -0
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/pywaybackup/Exception.py +45 -7
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/pywaybackup/SnapshotCollection.py +7 -1
- pywaybackup-1.2.4/pywaybackup/__version__.py +1 -0
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/pywaybackup/archive.py +23 -20
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/pywaybackup/helper.py +12 -1
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/pywaybackup/main.py +2 -0
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/pywaybackup.egg-info/PKG-INFO +5 -1
- pywaybackup-1.2.3/pywaybackup/__version__.py +0 -1
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/LICENSE +0 -0
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/pywaybackup/Verbosity.py +0 -0
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/pywaybackup/__init__.py +0 -0
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/pywaybackup/arguments.py +0 -0
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/pywaybackup.egg-info/SOURCES.txt +0 -0
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/pywaybackup.egg-info/dependency_links.txt +0 -0
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/pywaybackup.egg-info/entry_points.txt +0 -0
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/pywaybackup.egg-info/requires.txt +0 -0
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/pywaybackup.egg-info/top_level.txt +0 -0
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/setup.cfg +0 -0
- {pywaybackup-1.2.3 → pywaybackup-1.2.4}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pywaybackup
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.4
|
|
4
4
|
Summary: Download snapshots from the Wayback Machine
|
|
5
5
|
Home-page: https://github.com/bitdruid/python-wayback-machine-downloader
|
|
6
6
|
Author: bitdruid
|
|
@@ -26,6 +26,10 @@ Internet-archive is a nice source for several OSINT-information. This tool is a
|
|
|
26
26
|
|
|
27
27
|
This tool allows you to download content from the Wayback Machine (archive.org). You can use it to download either the latest version or all versions of web page snapshots within a specified range.
|
|
28
28
|
|
|
29
|
+
## Info
|
|
30
|
+
|
|
31
|
+
Linux recommended: On windows machines, the path length is limited. It can only be overcome by editing the registry. Files which exceed the path length will not be downloaded.
|
|
32
|
+
|
|
29
33
|
## Installation
|
|
30
34
|
|
|
31
35
|
### Pip
|
|
@@ -11,6 +11,10 @@ Internet-archive is a nice source for several OSINT-information. This tool is a
|
|
|
11
11
|
|
|
12
12
|
This tool allows you to download content from the Wayback Machine (archive.org). You can use it to download either the latest version or all versions of web page snapshots within a specified range.
|
|
13
13
|
|
|
14
|
+
## Info
|
|
15
|
+
|
|
16
|
+
Linux recommended: On windows machines, the path length is limited. It can only be overcome by editing the registry. Files which exceed the path length will not be downloaded.
|
|
17
|
+
|
|
14
18
|
## Installation
|
|
15
19
|
|
|
16
20
|
### Pip
|
|
@@ -5,6 +5,10 @@ from datetime import datetime
|
|
|
5
5
|
import linecache
|
|
6
6
|
import traceback
|
|
7
7
|
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
from pywaybackup.__version__ import __version__
|
|
11
|
+
|
|
8
12
|
class Exception:
|
|
9
13
|
|
|
10
14
|
new_debug = True
|
|
@@ -22,19 +26,20 @@ class Exception:
|
|
|
22
26
|
@classmethod
|
|
23
27
|
def exception(cls, message: str, e: Exception, tb=None):
|
|
24
28
|
custom_tb = sys.exc_info()[-1] if tb is None else tb
|
|
25
|
-
original_tb = "".join(traceback.format_exception(type(e), e, e.__traceback__))
|
|
29
|
+
original_tb = cls.relativate_path("".join(traceback.format_exception(type(e), e, e.__traceback__)))
|
|
26
30
|
exception_message = (
|
|
27
31
|
"-------------------------\n"
|
|
28
32
|
f"!-- Exception: {message}\n"
|
|
29
33
|
)
|
|
30
34
|
if custom_tb is not None:
|
|
31
|
-
while custom_tb.tb_next:
|
|
32
|
-
custom_tb = custom_tb.tb_next
|
|
35
|
+
while custom_tb.tb_next: # loop to last traceback frame
|
|
36
|
+
custom_tb = custom_tb.tb_next
|
|
33
37
|
tb_frame = custom_tb.tb_frame
|
|
34
38
|
tb_line = custom_tb.tb_lineno
|
|
35
39
|
func_name = tb_frame.f_code.co_name
|
|
36
|
-
filename = tb_frame.f_code.co_filename
|
|
40
|
+
filename = cls.relativate_path(tb_frame.f_code.co_filename)
|
|
37
41
|
codeline = linecache.getline(filename, tb_line).strip()
|
|
42
|
+
local_vars = tb_frame.f_locals
|
|
38
43
|
exception_message += (
|
|
39
44
|
f"!-- File: {filename}\n"
|
|
40
45
|
f"!-- Function: {func_name}\n"
|
|
@@ -45,24 +50,57 @@ class Exception:
|
|
|
45
50
|
exception_message += "!-- Traceback is None\n"
|
|
46
51
|
exception_message += (
|
|
47
52
|
f"!-- Description: {e}\n"
|
|
48
|
-
"-------------------------"
|
|
53
|
+
"-------------------------"
|
|
54
|
+
)
|
|
49
55
|
print(exception_message)
|
|
50
56
|
if cls.debug:
|
|
51
57
|
debug_file = os.path.join(cls.output, "waybackup_error.log")
|
|
52
58
|
print(f"Exception log: {debug_file}")
|
|
53
59
|
print("-------------------------")
|
|
54
60
|
print(f"Full traceback:\n{original_tb}")
|
|
55
|
-
if cls.new_debug:
|
|
61
|
+
if cls.new_debug: # new run, overwrite file
|
|
56
62
|
cls.new_debug = False
|
|
57
63
|
f = open(debug_file, "w")
|
|
58
64
|
f.write("-------------------------\n")
|
|
65
|
+
f.write(f"Version: {__version__}\n")
|
|
66
|
+
f.write("-------------------------\n")
|
|
59
67
|
f.write(f"Command: {cls.command}\n")
|
|
60
68
|
f.write("-------------------------\n\n")
|
|
61
|
-
else:
|
|
69
|
+
else: # current run, append to file
|
|
62
70
|
f = open(debug_file, "a")
|
|
63
71
|
f.write(datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "\n")
|
|
64
72
|
f.write(exception_message + "\n")
|
|
73
|
+
f.write("!-- Local Variables:\n")
|
|
74
|
+
for var_name, value in local_vars.items():
|
|
75
|
+
if var_name in ["status_message", "headers"]:
|
|
76
|
+
continue
|
|
77
|
+
value = cls.relativate_path(str(value))
|
|
78
|
+
value = value[:666] + " ... " if len(value) > 666 else value
|
|
79
|
+
f.write(f" -- {var_name} = {value}\n")
|
|
80
|
+
f.write("-------------------------\n")
|
|
65
81
|
f.write(original_tb + "\n")
|
|
82
|
+
f.close()
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def relativate_path(cls, input: str) -> str:
|
|
86
|
+
try:
|
|
87
|
+
path_pattern = re.compile(r'File "([^"]+)"')
|
|
88
|
+
if os.path.isfile(input): # case single path
|
|
89
|
+
return os.path.relpath(input, os.getcwd())
|
|
90
|
+
input_modified = ""
|
|
91
|
+
input_lines = input.split('\n')
|
|
92
|
+
if len(input_lines) == 1: # case single line
|
|
93
|
+
return input
|
|
94
|
+
for line in input.split('\n'): # case multiple lines
|
|
95
|
+
match = path_pattern.search(line)
|
|
96
|
+
if match:
|
|
97
|
+
original_path = match.group(1)
|
|
98
|
+
relative_path = os.path.relpath(original_path, os.getcwd())
|
|
99
|
+
line = line.replace(original_path, relative_path)
|
|
100
|
+
input_modified += line + "\n"
|
|
101
|
+
return input_modified
|
|
102
|
+
except ValueError:
|
|
103
|
+
return input
|
|
66
104
|
|
|
67
105
|
@staticmethod
|
|
68
106
|
def exception_handler(exception_type, exception, traceback):
|
|
@@ -31,7 +31,13 @@ class SnapshotCollection:
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
@classmethod
|
|
34
|
-
def
|
|
34
|
+
def count(cls, collection=False, success=False, fail=False):
|
|
35
|
+
if collection:
|
|
36
|
+
return len(cls.SNAPSHOT_COLLECTION)
|
|
37
|
+
if success:
|
|
38
|
+
return len([entry for entry in cls.SNAPSHOT_COLLECTION if entry["file"]])
|
|
39
|
+
if fail:
|
|
40
|
+
return len([entry for entry in cls.SNAPSHOT_COLLECTION if not entry["file"]])
|
|
35
41
|
return len(cls.SNAPSHOT_COLLECTION)
|
|
36
42
|
|
|
37
43
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.2.4"
|
|
@@ -13,7 +13,7 @@ from datetime import datetime, timezone
|
|
|
13
13
|
|
|
14
14
|
from socket import timeout
|
|
15
15
|
|
|
16
|
-
from pywaybackup.helper import url_get_timestamp, url_split, move_index, sanitize_filename
|
|
16
|
+
from pywaybackup.helper import url_get_timestamp, url_split, move_index, sanitize_filename, check_nt
|
|
17
17
|
|
|
18
18
|
from pywaybackup.SnapshotCollection import SnapshotCollection as sc
|
|
19
19
|
|
|
@@ -83,7 +83,7 @@ def save_page(url: str):
|
|
|
83
83
|
|
|
84
84
|
def print_list():
|
|
85
85
|
vb.write("")
|
|
86
|
-
count = sc.
|
|
86
|
+
count = sc.count(collection=True)
|
|
87
87
|
if count == 0:
|
|
88
88
|
vb.write("\nNo snapshots found")
|
|
89
89
|
else:
|
|
@@ -104,7 +104,8 @@ def query_list(url: str, range: int, start: int, end: int, explicit: bool, mode:
|
|
|
104
104
|
cdxResult = cdxResult.read()
|
|
105
105
|
vb.write(f"\n-----> {len(cdxResult)} snapshots injected")
|
|
106
106
|
except FileNotFoundError as e:
|
|
107
|
-
|
|
107
|
+
vb.write("\nFILE NOT FOUND -> could not inject CDX data")
|
|
108
|
+
os._exit(1)
|
|
108
109
|
else:
|
|
109
110
|
vb.write("\nQuerying snapshots...")
|
|
110
111
|
query_range = ""
|
|
@@ -134,7 +135,6 @@ def query_list(url: str, range: int, start: int, end: int, explicit: bool, mode:
|
|
|
134
135
|
except requests.exceptions.ConnectionError as e:
|
|
135
136
|
vb.write("\nCONNECTION REFUSED -> could not query cdx server (max retries exceeded)\n")
|
|
136
137
|
os._exit(1)
|
|
137
|
-
#ex.exception("Could not query snapshots", e)
|
|
138
138
|
|
|
139
139
|
if cdxbackup:
|
|
140
140
|
os.makedirs(cdxbackup, exist_ok=True)
|
|
@@ -144,7 +144,7 @@ def query_list(url: str, range: int, start: int, end: int, explicit: bool, mode:
|
|
|
144
144
|
|
|
145
145
|
cdxResult = json.loads(cdxResult)
|
|
146
146
|
sc.create_list(cdxResult, mode)
|
|
147
|
-
vb.write(f"\n-----> {sc.
|
|
147
|
+
vb.write(f"\n-----> {sc.count(collection=True)} snapshots to utilize")
|
|
148
148
|
|
|
149
149
|
|
|
150
150
|
|
|
@@ -156,7 +156,7 @@ def download_list(output, retry, no_redirect, workers, skipset: set = None):
|
|
|
156
156
|
"""
|
|
157
157
|
Download a list of urls in format: [{"timestamp": "20190815104545", "url": "https://www.google.com/"}]
|
|
158
158
|
"""
|
|
159
|
-
if sc.
|
|
159
|
+
if sc.count(collection=True) == 0:
|
|
160
160
|
vb.write("\nNothing to download");
|
|
161
161
|
return
|
|
162
162
|
vb.write("\nDownloading snapshots...", progress=0)
|
|
@@ -188,8 +188,8 @@ def download_list(output, retry, no_redirect, workers, skipset: set = None):
|
|
|
188
188
|
thread.start()
|
|
189
189
|
for thread in threads:
|
|
190
190
|
thread.join()
|
|
191
|
-
successed =
|
|
192
|
-
failed =
|
|
191
|
+
successed = sc.count(success=True)
|
|
192
|
+
failed = sc.count(fail=True)
|
|
193
193
|
vb.write(f"\nFiles downloaded: {successed}")
|
|
194
194
|
vb.write(f"Not downloaded: {failed}\n")
|
|
195
195
|
|
|
@@ -214,7 +214,7 @@ def download_loop(snapshot_queue, output, worker, retry, no_redirect, skipset=No
|
|
|
214
214
|
while not snapshot_queue.empty():
|
|
215
215
|
snapshot = snapshot_queue.get()
|
|
216
216
|
status = f"\n-----> Attempt: [{attempt}/{max_attempt}] Snapshot [{sc.SNAPSHOT_COLLECTION.index(snapshot)+1}/{len(sc.SNAPSHOT_COLLECTION)}] - Worker: {worker}"
|
|
217
|
-
download_status = download(output, snapshot, connection, status, no_redirect
|
|
217
|
+
download_status = download(output, snapshot, connection, status, no_redirect)
|
|
218
218
|
if not download_status:
|
|
219
219
|
if snapshot not in failed_urls:
|
|
220
220
|
failed_urls.append(snapshot)
|
|
@@ -236,7 +236,7 @@ def download_loop(snapshot_queue, output, worker, retry, no_redirect, skipset=No
|
|
|
236
236
|
|
|
237
237
|
|
|
238
238
|
|
|
239
|
-
def download(output, snapshot_entry, connection, status_message, no_redirect=False
|
|
239
|
+
def download(output, snapshot_entry, connection, status_message, no_redirect=False):
|
|
240
240
|
"""
|
|
241
241
|
Download a single URL and save it to the specified filepath.
|
|
242
242
|
If there is a redirect, the function will follow the redirect and update the download URL.
|
|
@@ -284,6 +284,15 @@ def download(output, snapshot_entry, connection, status_message, no_redirect=Fal
|
|
|
284
284
|
output_file = sc.create_output(download_url, snapshot_entry["timestamp"], output)
|
|
285
285
|
output_path = os.path.dirname(output_file)
|
|
286
286
|
|
|
287
|
+
# if output_file is too long for windows, skip download
|
|
288
|
+
if check_nt() and len(output_file) > 255:
|
|
289
|
+
status_message = f"{status_message}\n" + \
|
|
290
|
+
f"PATH TOO LONG TO SAVE FILE -> HTTP: {response_status} - {response_status_message}\n" + \
|
|
291
|
+
f" -> URL: {download_url}"
|
|
292
|
+
sc.snapshot_entry_modify(snapshot_entry, "file", "PATH TOO LONG TO SAVE FILE")
|
|
293
|
+
vb.write(status_message)
|
|
294
|
+
return True
|
|
295
|
+
|
|
287
296
|
# case if output_path is a file, move file to temporary name, create output_path and move file into output_path
|
|
288
297
|
if os.path.isfile(output_path):
|
|
289
298
|
move_index(existpath=output_path)
|
|
@@ -293,14 +302,6 @@ def download(output, snapshot_entry, connection, status_message, no_redirect=Fal
|
|
|
293
302
|
if os.path.isdir(output_file):
|
|
294
303
|
output_file = move_index(existfile=output_file, filebuffer=response_data)
|
|
295
304
|
|
|
296
|
-
# if filename is too long, skip download
|
|
297
|
-
if len(os.path.basename(output_file)) > 255:
|
|
298
|
-
status_message = f"{status_message}\n" + \
|
|
299
|
-
f"FILENAME TOO LONG -> HTTP: {response_status} - {response_status_message}\n" + \
|
|
300
|
-
f" -> URL: {download_url}"
|
|
301
|
-
vb.write(status_message)
|
|
302
|
-
return True
|
|
303
|
-
|
|
304
305
|
if not os.path.isfile(output_file):
|
|
305
306
|
with open(output_file, 'wb') as file:
|
|
306
307
|
if response.getheader('Content-Encoding') == 'gzip':
|
|
@@ -378,7 +379,7 @@ def csv_close(csv_path: str, url: str):
|
|
|
378
379
|
"""
|
|
379
380
|
try:
|
|
380
381
|
csv_path = csv_filepath(csv_path, url)
|
|
381
|
-
if sc.
|
|
382
|
+
if sc.count(collection=True) > 0:
|
|
382
383
|
if os.path.exists(csv_path): # append to existing file
|
|
383
384
|
existing_rows = set()
|
|
384
385
|
with open(csv_path, mode='r') as file: # read existing rows
|
|
@@ -425,11 +426,13 @@ def skip_open(csv_path: str, url: str) -> tuple:
|
|
|
425
426
|
"""
|
|
426
427
|
try:
|
|
427
428
|
csv_path = csv_filepath(csv_path, url)
|
|
428
|
-
if os.path.
|
|
429
|
+
if os.path.isfile(csv_path) and os.path.getsize(csv_path) > 0:
|
|
429
430
|
csv_file = open(csv_path, mode='r')
|
|
430
431
|
skipset = set(csv_read(csv_file))
|
|
432
|
+
csv_file.close()
|
|
431
433
|
return skipset
|
|
432
434
|
else:
|
|
435
|
+
vb.write("\nNo CSV-file or content found to load skipable URLs")
|
|
433
436
|
return None
|
|
434
437
|
except Exception as e:
|
|
435
438
|
ex.exception("Could not open CSV-file", e)
|
|
@@ -3,6 +3,14 @@ import os
|
|
|
3
3
|
import shutil
|
|
4
4
|
import magic
|
|
5
5
|
|
|
6
|
+
|
|
7
|
+
def check_nt():
|
|
8
|
+
"""
|
|
9
|
+
Check if the OS is Windows.
|
|
10
|
+
"""
|
|
11
|
+
return os.name == "nt"
|
|
12
|
+
|
|
13
|
+
|
|
6
14
|
def sanitize_filename(input: str) -> str:
|
|
7
15
|
"""
|
|
8
16
|
Sanitize a string to be used as (part of) a filename.
|
|
@@ -21,6 +29,7 @@ def url_get_timestamp(url):
|
|
|
21
29
|
timestamp = url.split("id_/")[0].split("/")[-1]
|
|
22
30
|
return timestamp
|
|
23
31
|
|
|
32
|
+
|
|
24
33
|
def url_split(url, index=False):
|
|
25
34
|
"""
|
|
26
35
|
Split a URL into domain, subdir, and filename.
|
|
@@ -38,7 +47,7 @@ def url_split(url, index=False):
|
|
|
38
47
|
filename = "index.html" if index else ""
|
|
39
48
|
subdir = "/".join(path_parts).strip("/")
|
|
40
49
|
# sanitize subdir and filename for windows
|
|
41
|
-
if
|
|
50
|
+
if check_nt():
|
|
42
51
|
special_chars = [":", "*", "?", "&", "=", "<", ">", "\\", "|"]
|
|
43
52
|
for char in special_chars:
|
|
44
53
|
subdir = subdir.replace(char, f"%{ord(char):02x}")
|
|
@@ -46,6 +55,7 @@ def url_split(url, index=False):
|
|
|
46
55
|
filename = filename.replace("%20", " ")
|
|
47
56
|
return domain, subdir, filename
|
|
48
57
|
|
|
58
|
+
|
|
49
59
|
def move_index(existpath: str = None, existfile: str = None, filebuffer: bytes = None):
|
|
50
60
|
"""
|
|
51
61
|
1. If existpath is given but can't be created because a file exists with the same name
|
|
@@ -73,6 +83,7 @@ def move_index(existpath: str = None, existfile: str = None, filebuffer: bytes =
|
|
|
73
83
|
else:
|
|
74
84
|
return os.path.join(existfile, "index.html")
|
|
75
85
|
|
|
86
|
+
|
|
76
87
|
def check_index_mime(filebuffer: bytes) -> bool:
|
|
77
88
|
mime = magic.Magic(mime=True)
|
|
78
89
|
mime_type = mime.from_buffer(filebuffer)
|
|
@@ -14,6 +14,8 @@ def main():
|
|
|
14
14
|
if args.output is None:
|
|
15
15
|
args.output = os.path.join(os.getcwd(), "waybackup_snapshots")
|
|
16
16
|
os.makedirs(args.output, exist_ok=True)
|
|
17
|
+
else:
|
|
18
|
+
os.makedirs(args.output, exist_ok=True)
|
|
17
19
|
|
|
18
20
|
ex.init(args.debug, args.output, command)
|
|
19
21
|
vb.init(args.verbosity)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pywaybackup
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.4
|
|
4
4
|
Summary: Download snapshots from the Wayback Machine
|
|
5
5
|
Home-page: https://github.com/bitdruid/python-wayback-machine-downloader
|
|
6
6
|
Author: bitdruid
|
|
@@ -26,6 +26,10 @@ Internet-archive is a nice source for several OSINT-information. This tool is a
|
|
|
26
26
|
|
|
27
27
|
This tool allows you to download content from the Wayback Machine (archive.org). You can use it to download either the latest version or all versions of web page snapshots within a specified range.
|
|
28
28
|
|
|
29
|
+
## Info
|
|
30
|
+
|
|
31
|
+
Linux recommended: On windows machines, the path length is limited. It can only be overcome by editing the registry. Files which exceed the path length will not be downloaded.
|
|
32
|
+
|
|
29
33
|
## Installation
|
|
30
34
|
|
|
31
35
|
### Pip
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.2.3"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|