-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathsave_safari_webarchive.swift
executable file
·135 lines (114 loc) · 3.64 KB
/
save_safari_webarchive.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#!/usr/bin/env swift
/// Save a web page as a Safari webarchive.
///
/// Usage: save_safari_webarchive [URL] [OUTPUT_PATH]
///
/// This will save the page to the desired file, but may fail for
/// several reasons:
///
/// - the web page can't be loaded
/// - the web page returns a non-200 status code
/// - there's already a file at that path (it won't overwrite an existing
/// webarchive)
///
/// For a detailed explanation of the code in this script, see
/// https://alexwlchan.net/2024/creating-a-safari-webarchive/
///
/// The canonical copy of this script lives in GitHub, see
/// https://github.com/alexwlchan/safari-webarchiver
import WebKit
let SCRIPT_VERSION = "1.0.1"
/// Print an error message and terminate the process if there are
/// any errors while loading a page.
class ExitOnFailureDelegate: NSObject, WKNavigationDelegate {
var urlString: String
init(_ urlString: String) {
self.urlString = urlString
}
func webView(
_: WKWebView,
didFail: WKNavigation!,
withError error: Error
) {
fputs("Failed to load \(self.urlString): \(error.localizedDescription)\n", stderr)
exit(1)
}
func webView(
_: WKWebView,
didFailProvisionalNavigation: WKNavigation!,
withError error: Error
) {
fputs("Failed to load \(self.urlString): \(error.localizedDescription)\n", stderr)
exit(1)
}
func webView(
_: WKWebView,
decidePolicyFor navigationResponse: WKNavigationResponse,
decisionHandler: (WKNavigationResponsePolicy) -> Void
) {
if let httpUrlResponse = (navigationResponse.response as? HTTPURLResponse) {
if httpUrlResponse.statusCode != 200 {
fputs("Failed to load \(self.urlString): got status code \(httpUrlResponse.statusCode)\n", stderr)
exit(1)
}
}
decisionHandler(.allow)
}
}
extension WKWebView {
/// Load the given URL in the web view.
///
/// This method will block until the URL has finished loading.
func load(_ urlString: String) {
let delegate = ExitOnFailureDelegate(urlString)
webView.navigationDelegate = delegate
if let url = URL(string: urlString) {
let request = URLRequest(url: url)
self.load(request)
while (self.isLoading) {
RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.1))
}
} else {
fputs("Unable to use \(urlString) as a URL\n", stderr)
exit(1)
}
}
/// Save a copy of the web view's contents as a webarchive file.
///
/// This method will block until the webarchive has been saved,
/// or the save has failed for some reason.
func saveAsWebArchive(savePath: URL) {
var isSaving = true
self.createWebArchiveData(completionHandler: { result in
do {
let data = try result.get()
try data.write(
to: savePath,
options: [Data.WritingOptions.withoutOverwriting]
)
isSaving = false
} catch {
fputs("Unable to save webarchive file: \(error.localizedDescription)\n", stderr)
exit(1)
}
})
while (isSaving) {
RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.1))
}
}
}
if CommandLine.arguments.count == 2 && CommandLine.arguments[1] == "--version" {
let filename = (CommandLine.arguments[0] as NSString).lastPathComponent
print("\(filename) \(SCRIPT_VERSION)")
exit(0)
}
guard CommandLine.arguments.count == 3 else {
fputs("Usage: \(CommandLine.arguments[0]) <URL> <OUTPUT_PATH>\n", stderr)
exit(1)
}
let urlString = CommandLine.arguments[1]
let savePath = URL(fileURLWithPath: CommandLine.arguments[2])
let webView = WKWebView()
webView.load(urlString)
webView.saveAsWebArchive(savePath: savePath)
print("Saved webarchive to \(savePath)")