├── README.md
├── pom.xml
└── src
└── main
└── java
├── Main.java
└── Wiz.java
/README.md:
--------------------------------------------------------------------------------
1 | # WizExport
2 | 利用为知笔记的 OpenAPI 把笔记下载到本地,支持 markdown 和 html 格式的笔记
3 |
4 | # Usage
5 | 传入用户名和密码,然后指定一个文档保存位置,启动 main 方法就可以进行文档下载
6 | ```Java
7 | public class Main {
8 | public static void main(String[] args) throws IOException {
9 | new Wiz().login("test@123.com", "password")
10 | .downloadTo("C:\\wiz");
11 | }
12 | }
13 | ```
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | wiz
8 | wiz
9 | 1.0-SNAPSHOT
10 |
11 |
12 |
13 | org.jsoup
14 | jsoup
15 | 1.12.2
16 |
17 |
18 | org.apache.httpcomponents
19 | httpclient
20 | 4.5.13
21 |
22 |
23 | com.alibaba
24 | fastjson
25 | 1.2.62
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/main/java/Main.java:
--------------------------------------------------------------------------------
1 | import java.io.IOException;
2 |
3 | public class Main {
4 | public static void main(String[] args) throws IOException {
5 | new Wiz().login("test@123.com", "password")
6 | .downloadTo("C:\\wiz");
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/Wiz.java:
--------------------------------------------------------------------------------
1 | import com.alibaba.fastjson.JSONArray;
2 | import com.alibaba.fastjson.JSONObject;
3 | import com.alibaba.fastjson.JSONPath;
4 | import org.apache.http.Header;
5 | import org.apache.http.HttpHeaders;
6 | import org.apache.http.HttpResponse;
7 | import org.apache.http.client.HttpClient;
8 | import org.apache.http.client.config.RequestConfig;
9 | import org.apache.http.client.methods.HttpGet;
10 | import org.apache.http.client.methods.HttpPost;
11 | import org.apache.http.conn.ssl.NoopHostnameVerifier;
12 | import org.apache.http.entity.StringEntity;
13 | import org.apache.http.impl.client.HttpClients;
14 | import org.apache.http.message.BasicHeader;
15 | import org.apache.http.util.EntityUtils;
16 | import org.jsoup.Jsoup;
17 | import org.jsoup.nodes.Document;
18 |
19 | import java.io.BufferedWriter;
20 | import java.io.File;
21 | import java.io.FileWriter;
22 | import java.io.IOException;
23 | import java.net.URLEncoder;
24 | import java.nio.charset.StandardCharsets;
25 | import java.nio.file.Files;
26 | import java.nio.file.Path;
27 | import java.nio.file.Paths;
28 | import java.util.ArrayList;
29 | import java.util.List;
30 |
31 | public class Wiz {
32 |
33 | private static final String WIZ_AS = "https://as.wiz.cn/as/user/login";
34 | private static final int TIMEOUT = 60 * 1000;
35 |
36 | private HttpClient client;
37 | private String kbServer, kbGuid;
38 |
39 | Wiz() {
40 | this.client = HttpClients.custom()
41 | .setSSLHostnameVerifier(new NoopHostnameVerifier())
42 | .setDefaultRequestConfig(RequestConfig.custom().setConnectTimeout(TIMEOUT).setSocketTimeout(TIMEOUT).build())
43 | .build();
44 | }
45 |
46 | /**
47 | * post /as/user/login
48 | * body: {
49 | * userId,
50 | * password,
51 | * }
52 | */
53 | public Wiz login(String userId, String password) throws IOException {
54 | HttpPost post = new HttpPost(WIZ_AS);
55 | post.setHeader(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json"));
56 | post.setEntity(new StringEntity(String.format("{\"userId\":\"%s\", \"password\":\"%s\"}", userId, password), StandardCharsets.UTF_8));
57 |
58 | HttpResponse response = this.client.execute(post);
59 | String json = EntityUtils.toString(response.getEntity());
60 | Object token = JSONPath.extract(json, "result.token");
61 | if (token == null) {
62 | throw new RuntimeException("login error: " + json);
63 | }
64 |
65 | List headers = new ArrayList<>(1);
66 | headers.add(new BasicHeader("X-Wiz-Token", token.toString()));
67 | this.client = HttpClients.custom()
68 | .setSSLHostnameVerifier(new NoopHostnameVerifier())
69 | .setDefaultHeaders(headers)
70 | .setDefaultRequestConfig(RequestConfig.custom().setConnectTimeout(TIMEOUT).setSocketTimeout(TIMEOUT).build())
71 | .build();
72 | this.kbServer = JSONPath.extract(json, "result.kbServer").toString();
73 | this.kbGuid = JSONPath.extract(json, "result.kbGuid").toString();
74 | return this;
75 | }
76 |
77 | /**
78 | * get /ks/category/all/:kbGuid
79 | */
80 | void downloadTo(String dir) throws IOException {
81 | HttpResponse response = client.execute(new HttpGet(String.format("%s/ks/category/all/%s", kbServer, kbGuid)));
82 | if (response.getStatusLine().getStatusCode() != 200) {
83 | throw new RuntimeException(EntityUtils.toString(response.getEntity()));
84 | }
85 | Object result = JSONPath.extract(EntityUtils.toString(response.getEntity()), "result");
86 | for (Object folder : (JSONArray) result) {
87 | downloadFolder(dir, folder.toString());
88 | }
89 | }
90 |
91 | /**
92 | * get /ks/note/list/category/:kbGuid?category=:folder&withAbstract=true|false&start=:start&count=:count&orderBy=title|created|modified&ascending=asc|desc
93 | */
94 | private void downloadFolder(String dir, String folder) throws IOException {
95 | String url = String.format(
96 | "%s/ks/note/list/category/%s?category=%s&withAbstract=false&start=0&count=100&orderBy=created&ascending=desc",
97 | kbServer, kbGuid, URLEncoder.encode(folder, "UTF-8")
98 | );
99 | HttpResponse docListResp = client.execute(new HttpGet(url));
100 | String docListJson = EntityUtils.toString(docListResp.getEntity());
101 |
102 | Path docPath = Files.createDirectories(Paths.get(dir, folder));
103 | Object docList = JSONPath.extract(docListJson, "result");
104 | for (Object doc : (JSONArray) docList) {
105 | downloadDoc(docPath, (JSONObject) doc);
106 | }
107 | }
108 |
109 | /**
110 | * get /ks/note/view/:kbGuid/:docGuid/
111 | */
112 | private void downloadDoc(Path docPath, JSONObject doc) {
113 | String docGuid = doc.get("docGuid").toString();
114 | String docTitle = doc.get("title").toString();
115 | try {
116 | String url = String.format("%s/ks/note/view/%s/%s", kbServer, kbGuid, docGuid);
117 | HttpResponse docHtmlResp = client.execute(new HttpGet(url));
118 | String docHtml = EntityUtils.toString(docHtmlResp.getEntity());
119 |
120 | docTitle = processDocTitle(docTitle);
121 | if (docTitle.endsWith(".md")) {
122 | String markdown = htmlToMarkdown(docHtml);
123 | Path docFile = Files.createFile(docPath.resolve(docTitle));
124 | Files.write(docFile, markdown.getBytes(StandardCharsets.UTF_8));
125 | normaliseMarkdown(docFile);
126 | } else {
127 | Path file = Files.createFile(docPath.resolve(docTitle + ".html"));
128 | Files.write(file, docHtml.getBytes(StandardCharsets.UTF_8));
129 | }
130 | } catch (Exception e) {
131 | System.out.println(String.format("Download %s fail, %s", docTitle, e.toString()));
132 | }
133 | }
134 |
135 | private String processDocTitle(String docTitle) {
136 | docTitle = docTitle.replace("|", "");
137 | docTitle = docTitle.replace("/", "");
138 | docTitle = docTitle.replace(">", "");
139 | docTitle = docTitle.replace(":", "");
140 | docTitle = docTitle.replace("“", "");
141 | docTitle = docTitle.replace("\"", "");
142 | return docTitle;
143 | }
144 |
145 | private void normaliseMarkdown(Path docFile) throws IOException {
146 | List lines = Files.readAllLines(docFile);
147 | Files.delete(docFile);
148 | try (BufferedWriter fileWriter = new BufferedWriter(new FileWriter(new File(docFile.toUri())))) {
149 | for (String line : lines) {
150 | String newLine = line;
151 | if (line.length() > 2) {
152 | newLine = line.substring(2);
153 | }
154 | newLine = newLine.replaceAll(" ", " ");
155 | fileWriter.write(newLine);
156 | fileWriter.newLine();
157 | }
158 | }
159 | }
160 |
161 | private String htmlToMarkdown(String docHtml) {
162 | Document.OutputSettings outputSettings = new Document.OutputSettings();
163 | outputSettings.prettyPrint(true);
164 | outputSettings.charset(StandardCharsets.UTF_8);
165 | return Jsoup.parse(Jsoup.parse(docHtml).normalise().outputSettings(outputSettings).outerHtml()).wholeText();
166 | }
167 | }
168 |
--------------------------------------------------------------------------------