diff --git a/source/_posts/use_js_save_time_geek.md b/source/_posts/use_js_save_time_geek.md
new file mode 100644
index 0000000..31e57e7
--- /dev/null
+++ b/source/_posts/use_js_save_time_geek.md
@@ -0,0 +1,418 @@
+title: 使用油猴脚本保存极客时间文章
+tags: []
+categories: []
+date: 2023-08-03 16:58:17
+---
+# 背景
+
+去年购买了极客时间的年度会员,只有一年有效期。所以想用一些方式保存一下来购买的内容。市面上没有成熟的方案,所以决定自己编写
+
+# 使用前提
+开始前需要读者具备一定的开发基础。最好会点简单的JS。能使用浏览器开发者工具
+需要准备以下内容:
+1. edge 或chrome 并安装油猴插件
+2. 文本编辑器
+3. 一双手(逃)
+
+# 分析网站
+
+分析网站分析什么?
+主要分析页面及各种HTTP请求,哪种对我们更便捷。
+## 页面抓取可行性
+
+
+问题:
+1. 超链接无法抓取到
+
+## 请求抓取可行性
+现代的互联网公司的网站,基本都是前后端分离的,也就是请求的数据基本都是JSON,这为抓取数据带来了便利。
+
+**找到请求正文的请求**
+页面刷新一下,在所有网络请求一个一个看返回内容
+
+找到真正的获取正文请求是`https://time.geekbang.org/serv/v1/article`,该请求是一个POST请求。看一下POST的数据
+```json
+{
+ "id":***,
+ "include_neighbors":true,
+ "is_freelyread":true
+}
+```
+其他的不要在意,看一下这个ID,是不是和页面路径的`https://time.geekbang.org/column/article/***`后的ID一样?
+那就简单了,拿到页面的URL把ID取出来
+
+然后分析返回内容
+
+这里展示核心内容
+```json
+{
+ "data":{
+ "text_read_version":0,
+ "audio_size":10270592,
+ "product_type":"p29",
+ "audio_dubber":"霍泰稳",
+ "is_finished":false,
+ "like":{
+ "had_done":false,
+ "count":0
+ },
+ "article_content":"****",
+ "article_title":"第306期 | 放下纠结,你就远离了拖延症",
+ "article_cshort":"今天和大家分享一个关于拖延症的话题,我猜这个话题会有很多人感兴趣。",
+ "author_name":"霍太稳",
+ "neighbors":{
+ "left":{
+ "article_title":"第305期 | 开心的时候,去跑步,心烦的时候,去跑步",
+ "id":256918
+ },
+ "right":{
+ "article_title":"第307期 | 幸福其实很简单,忘掉自己就好",
+ "id":257894
+ }
+ },
+ "product_id":100024801,
+ "had_liked":false,
+ "id":257009,
+ "article_summary":"今天和大家分享一个关于拖延症的话题,我猜这个话题会有很多人感兴趣。",
+ "column_id":172,
+ "audio_md5":"de4f0391304d351d9c5dd6e93f038356"
+ },
+ "code":0
+}
+```
+
+注意看`article_content` 这里就是文章正文。我们只需要抓这部分就好了。
+因为抓取请求相对比较麻烦,所以我们再发起一次请求,拿到返回内容更简单。
+
+
+# 开始编写
+1. 引入JQuery
+2. 设定可行的页面
+
+```js
+// ==UserScript==
+// @name 保存极客时间正文
+// @namespace http://tampermonkey.net/
+// @version 0.1
+// @match https://time.geekbang.org/column/article/*
+// @description try to take over the world!
+// @author You
+// @require https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js
+// @grant none
+// ==/UserScript==
+
+(function () {
+ 'use strict';
+
+
+ let btn = document.createElement("button");
+ btn.innerHTML = "下载";
+ btn.style = "position: fixed;z-index: 999; left: 90%; top: 20px;";
+
+ btn.onclick = function () {
+ //code
+ saveContext()
+ }
+
+ document.body.append(btn);
+ function saveContext() {
+ var url = window.location.href
+ var id = url.replace("https://time.geekbang.org/column/article/", "")
+ var json = { "id": id, "include_neighbors": true, "is_freelyread": true }
+
+ $.ajax({
+ url: "https://time.geekbang.org/serv/v1/article",
+ data: JSON.stringify(json),
+ type: "POST",
+ dataType: "json",
+ contentType: "application/json",
+ success: function (data) {
+ console.log(data);
+ var context = data.data.article_content;
+ var title = data.data.article_title;
+
+ const link = document.createElement('a');
+
+ var textBlob = new Blob([context], { type: 'text/plain' });
+
+ link.setAttribute('href', URL.createObjectURL(textBlob));
+ link.setAttribute('download', title + ".html");
+ link.click();
+ }
+ });
+ }
+
+})();
+
+```
+
+以上内容直接贴到新脚本即可使用
+
+
+# 全自动
+既然一次抓取成功了,那能不能全自动呢?
+当然可以了。还记得返回中的`right`的内容吗?这是下一节的ID和标题。所以我们只需要在文档保存后,拿到这个ID,然后跳转到下一节即可。
+
+在下载完成的地方增加以下代码
+```js
+if (data.data.neighbors.right.id) {
+ var nextId = data.data.neighbors.right.id;
+ window.location.href = "https://time.geekbang.org/column/article/" + nextId;
+}
+
+```
+
+然后再增加一个执行的代码
+```
+ setInterval(function () { btn.click(); }, 10000);
+```
+为什么10秒呢,因为请求过于频繁会强制退出登录。所以控制一下速度。
+
+# 完整脚本
+
+```json
+// ==UserScript==
+// @name copy to markdown
+// @namespace http://tampermonkey.net/
+// @version 0.1
+// @match https://time.geekbang.org/column/article/*
+// @description try to take over the world!
+// @author You
+// @require https://unpkg.com/turndown/dist/turndown.js
+// @require https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js
+// @grant none
+// ==/UserScript==
+
+(function () {
+ 'use strict';
+
+
+ let btn = document.createElement("button");
+ btn.innerHTML = "下载";//innerText也可以,区别是innerText不会解析html
+ btn.style = "position: fixed;z-index: 999; left: 90%; top: 20px;";
+
+ btn.onclick = function () {
+ //code
+ saveContext()
+ }
+
+ document.body.append(btn);
+ setInterval(function () { btn.click(); }, 10000);
+ function saveContext() {
+ var url = window.location.href
+ var id = url.replace("https://time.geekbang.org/column/article/", "")
+ var json = { "id": id, "include_neighbors": true, "is_freelyread": true }
+
+ $.ajax({
+ url: "https://time.geekbang.org/serv/v1/article",
+ data: JSON.stringify(json),
+ type: "POST",
+ dataType: "json",
+ contentType: "application/json",
+ success: function (data) {
+ console.log(data);
+ var context = data.data.article_content;
+ var title = data.data.article_title;
+
+ const link = document.createElement('a');
+
+ var textBlob = new Blob([context], { type: 'text/plain' });
+
+ link.setAttribute('href', URL.createObjectURL(textBlob));
+ link.setAttribute('download', title + ".html");
+ link.click();
+
+ if (data.data.neighbors.right.id) {
+ var nextId = data.data.neighbors.right.id;
+ window.location.href = "https://time.geekbang.org/column/article/" + nextId;
+ }
+
+
+ }
+ });
+ }
+})();
+```
+
+# 转换成markdown
+
+没有在脚本中保存为markdown,因为需要处理图片之类的信息。决定事后用Java代码处理。
+
+设想处理流程如下:
+1. 解析Html讲图片从网络地址,换成本地地址
+2. 转换成Markdown
+
+## 调研
+java中的html解析,直接用Jsoup
+markdown转换使用CopyDown
+
+这两个包的Maven坐标:
+```xml
+
+ io.github.furstenheim
+ copy_down
+ 1.0
+
+
+ org.jsoup
+ jsoup
+ 1.13.1
+
+```
+
+## 开始编写
+
+### 文件的读取及写入
+
+主要是提供一个文件的读取和写入方法
+
+```java
+
+ public static String getFileString(File file) {
+ try {
+ if (file.exists() && file.isFile()) {
+ List lines = Files.readAllLines(Paths.get(file.toURI()), StandardCharsets.UTF_8);
+ StringBuilder sb = new StringBuilder();
+ for (String line : lines) {
+ sb.append(line).append("\n");
+ }
+ return sb.toString();
+ } else {
+ return "未找到文件";
+ }
+ } catch (Exception e) {
+ logger.error("getFileString error!", e);
+ return "读取出现异常";
+ }
+ }
+
+ public static void saveToFile(File file, String context) {
+ try {
+ FileOutputStream fileOutputStream = null;
+ if (!file.exists()) {
+ file.createNewFile();
+ }
+ fileOutputStream = new FileOutputStream(file);
+ fileOutputStream.write(context.getBytes(StandardCharsets.UTF_8));
+ fileOutputStream.flush();
+ fileOutputStream.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+```
+### 保存图片文件
+
+```java
+ private static String downFile(File assetsDir, String remoteUrl) {
+ try {
+ String uuid = UUID.randomUUID().toString().replace("-", "")+".jpg";
+ runtime.exec("curl -o " + uuid + " \"" + remoteUrl + "\"", null, assetsDir);
+ return uuid;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+```
+
+此处极为省事,直接执行curl保存到目录中
+
+
+### markdown转换代码
+```java
+
+ public static String htmlTansToMarkdown(String htmlStr) {
+ OptionsBuilder optionsBuilder = OptionsBuilder.anOptions();
+ Options options = optionsBuilder.withBr("-").withCodeBlockStyle(CodeBlockStyle.FENCED)
+ // more options
+ .build();
+ CopyDown converter = new CopyDown(options);
+ return converter.convert(htmlStr);
+ }
+
+```
+
+可以参见官方文档:[copy-down](https://github.com/furstenheim/copy-down)
+
+### 解析并替换图片地址
+
+传入保存的文件夹,原始的HTML文件
+
+```java
+
+ public static void convert(String dir, File file) {
+ File assetsDir = new File(dir + "/assets");
+ assetsDir.mkdirs();
+ String html = FileUtils.getFileString(file);
+
+ Document doc = Jsoup.parse(html);
+ Elements imageList = doc.getElementsByTag("img");
+ Elements h1List = doc.getElementsByTag("title");
+ for (Element element : imageList) {
+ String url = element.attributes().get("src");
+ String uuid = downFile(assetsDir, url);
+ element.attributes().remove("src");
+ element.attributes().add("src", "assets/" + uuid);
+ element.attributes().remove("title");
+ }
+ String markdown = htmlTansToMarkdown(doc.outerHtml());
+
+ FileUtils.saveToFile(new File(dir + "/" + file.getName().replace(".html", ".md")), markdown);
+ }
+```
+
+至此文件已经生成了。但是我们有一个目录的文件需要执行啊,所以在main方法遍历文件夹
+
+
+### 遍历文件夹
+
+```java
+ public static void main(String[] args) {
+ File file = new File("HTML路径");
+ String dir = "markdown保存目录";
+ if (file.isDirectory()) {{
+ for (File listFile : file.listFiles()) {
+ convert(dir, listFile);
+ }
+ }
+ }
+
+```
+
+查看执行效果:
+
+目录:
+![](/files/assets/20230803_183347.png)
+
+
+图片:
+![](/files/assets/20230803_183713.png)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/files/assets/20230803_183226.txt b/source/files/assets/20230803_183226.txt
new file mode 100644
index 0000000..fd8b2a4
--- /dev/null
+++ b/source/files/assets/20230803_183226.txt
@@ -0,0 +1 @@
+https://github.com/EslaMx7/PasteIntoFile
\ No newline at end of file
diff --git a/source/files/assets/20230803_183347.png b/source/files/assets/20230803_183347.png
new file mode 100644
index 0000000..0113c98
Binary files /dev/null and b/source/files/assets/20230803_183347.png differ
diff --git a/source/files/assets/20230803_183713.png b/source/files/assets/20230803_183713.png
new file mode 100644
index 0000000..bf69d21
Binary files /dev/null and b/source/files/assets/20230803_183713.png differ