这篇文章是用来补充之前我之前发的 Typora + 图床的视频,具体效果可以看以下视频,视频中介绍了我的实现思路和具体效果。因为图床域名和工具的更新,视频中有些地方不再适用,看到这个视频时不时还有播放和点赞(其实没几个😂),决定补充下。

原视频中表述可能略显啰嗦,目的是为了让大家知其所以然,将过程分析出来,这样大家遇到什么问题,都可以按照思路解决。例如有一次路过图床的域名改了,如果你看了我的视频,你肯定可以知道在哪里配置这个链接。

img

配置步骤

1. 安装配置 Node.js

实现图片上传需要用到 Node.js,安装和配置方式自行搜索,这里不做赘述。

2. npm 安装 picgo,修改上传模块代码

执行命令安装 picgo,请务必安装 1.4.4 版本。

1
npm install -g picgo@1.4.4

然后进入 Node.js 全局包路径,找到 picgo 模块,编辑 picgo\dist\src\plugins\uploader\smms.js 文件,替换为以下内容保存。

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
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

/**
* 获取上传请求参数
* @param {string} fileName 文件名称
* @param {Buffer} image 文件
*/
const postOptions = (fileName, image, config) => {
return {
method: 'POST',
url: 'https://imgse.com/json', // 请求地址
headers: {
contentType: 'multipart/form-data',
'User-Agent': 'PicGo',
'Cookie': config.Cookie // 从配置中读取cookie
},
formData: {
auth_token: config.auth_token, // 从配置中读取token
type: 'file',
action: 'upload',
source: {
value: image,
options: {
filename: fileName
}
}
}
};
};

/**
* 处理上传
* @param {PicGo} ctx picgo核心对象
*/
const handle = async (ctx) => {
// 读取配置文件内容
const smmsConfig = ctx.getConfig('picBed.smms');
if (!smmsConfig) {
throw new Error('Can\'t find smms config, please provide api token, see https://sm.ms/home/apitoken');
}
// 获取上传图片对象
const imgList = ctx.output;
for (const img of imgList) {
if (img.fileName && img.buffer) {
let image = img.buffer;
if (!image && img.base64Image) {
image = Buffer.from(img.base64Image, 'base64');
}
// 传入图片名称、图片buffer、配置,获取上传参数
const postConfig = postOptions(img.fileName, image, smmsConfig);
// 执行上传请求,获取响应,转换为json对象
let body = await ctx.Request.request(postConfig);
body = JSON.parse(body);

// 解析响应
if (body.status_code === 200) {
delete img.base64Image;
delete img.buffer;
// 从响应对象中获取url,并设置上
img.imgUrl = body.image.url;
}
else {
ctx.emit('notification', {
title: '上传失败',
body: body.error.message
});
throw new Error(body.error.message);
}
}
}
return ctx;
};

/**
* smms图传配置文件约束
* @param {PicGo} ctx picgo核心对象
*/
const config = (ctx) => {
const userConfig = ctx.getConfig('picBed.smms') || {};
const config = [
{
name: 'token',
message: 'api token',
type: 'input',
default: userConfig.token || '',
required: true
}
];
return config;
};

exports.default = {
name: 'SM.MS图床',
handle,
config
};




3. 配置 Typora,设置为命令行上传

文件 > 偏好设置 > 图像 > 上传服务设定,上传服务选择 Custom Command,命令填写格式:[node路径] [picgo路径] upload,示例如下,根据自己的实际安装路径填写。

1
C:\Develop\nodejs\node C:\Develop\nodejs\node_global\node_modules\picgo\bin\picgo upload

4. 编辑配置文件,配置路过图床的 cookie 和 token

创建配置文件,路径:C:\Users$username.picgo\config.json,内容如下,cookie 和 token 的获取方式见原视频。

1
2
3
4
5
6
7
8
9
10
11
12


{
"picBed": {
"smms": {
"Cookie": "自行替换为路过图床的cookie",
"auth_token": "自行替换为路过图床的token"
},
"uploader": "smms"
},
"picgoPlugins": {}
}

FAQ

1. token找不到?

试试查看路过图床的网页源代码,搜索 token。

2. cookie 和 token 过期,总是要刷新吗?

是的,路过图床没有提供相关 API。我是一种思路的写一个定时任务,定时从路过图床获取 cookie 和 token,刷新到本地配置文件中。以下是我的 Java 实现,写的不好,大家可以自行使用自己擅长的语言实现,这里只提供一种思路。

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
package top.y1j;

import com.fasterxml.jackson.databind.ObjectMapper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.*;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;


import java.io.FileWriter;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class RefreshTokenJob {
private static final Logger log = LoggerFactory.getLogger(RefreshTokenJob.class);

private String tokenPrefix = "PF.obj.config.auth_token = \"" ;
private int tokenLength = 40;
private String homePageUrl = "https://imgse.com/login";
private String loginUrl = "https://imgse.com/login";
private String filePath = "C:\\Users\\${User}\\.picgo\\config.json";
private String username = "账号";
private String password = "密码";
//@Autowired
private RestTemplate restTemplate = new RestTemplate() ;

/**
* 路过图床同步 token 和 cookie
* <p>
* <p>
* <p>
* ** *
*
* @throws Exception
*/
@Scheduled(cron = "0 0/25 * * * ? ")
public void execute() throws Exception {
log.info("访问路过图床主页, 获取token和cookie");
HttpHeaders startHeader = new HttpHeaders();
startHeader.add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 SLBrowser/9.0.3.5211 SLBChan/112");
HttpEntity<MultiValueMap<String, String>> startRequest = new HttpEntity<>(startHeader);
URI uri = new URI(homePageUrl);

//System.out.println("restTemplate.exchange(uri, HttpMethod.GET, startRequest, String.class) = " + restTemplate.exchange(uri, HttpMethod.POST, startRequest, String.class));
ResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.POST, startRequest, String.class);




StringBuilder cookieBuilder = new StringBuilder();
List<String> cookieList = response.getHeaders().get("Set-Cookie");
for (String item : cookieList) {
String substring = item.substring(0, item.indexOf(";") + 1);
cookieBuilder.append(substring);
}
String cookie = cookieBuilder.toString();
String body = response.getBody();
//System.out.println("body = " + body);
int index = body.indexOf(tokenPrefix) + tokenPrefix.length();
String token = body.substring(index, index + tokenLength);
log.info("拿到数据, 开始登录, token: {}, cookie: {}", token, cookie);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.add("cookie", cookie);
headers.add("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 SLBrowser/9.0.3.5211 SLBChan/112");
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("login-subject", username);
params.add("password", password);
params.add("auth_token", token);
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers);
// 执行HTTP请求
ResponseEntity<String> loginResponse = restTemplate.exchange(loginUrl, HttpMethod.POST, requestEntity, String.class);
List<String> loginCookieList = loginResponse.getHeaders().get("Set-Cookie");
for (String item : loginCookieList) {
String substring = item.substring(0, item.indexOf(";") + 1);
cookieBuilder.append(substring);
}
Map<String, Object> config = this.getConfig(token, cookieBuilder.toString());
ObjectMapper mapper = new ObjectMapper();
String configJson = mapper.writeValueAsString(config);
FileWriter fileWriter = new FileWriter(filePath);
fileWriter.write(configJson);
fileWriter.close();
log.info("配置更新成功");
}

private Map<String, Object> getConfig(String token, String cookie) {
Map<String, Object> config = new HashMap<>();
Map<String, Object> picBed = new HashMap<>();
Map<String, Object> smms = new HashMap<>();
smms.put("auth_token", token);
smms.put("Cookie", cookie);
picBed.put("smms", smms);
picBed.put("uploader", "smms");
config.put("picBed", picBed);
return config;
}


public static void main(String[] args) throws Exception {
RefreshTokenJob refreshTokenJob = new RefreshTokenJob();
refreshTokenJob.execute();
}
}