├── 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 | --------------------------------------------------------------------------------