1 背景

文档主要内容是Obsidian基于​QuickAdd 宏配置 和 ​JS删除脚本,快速删除远程picgo图床图片——通过 FTP 删除远程图片。如果是阿里云或者腾讯云的图床应该也可以实现,只不过要重新写删除脚本了。

        我不知道为什么picgo设计的是只能上传不能删除,写文档的时候上传了不合适的图片其实挺常见的,上传容易,要删除的时候就很麻烦,其实想要研究这个问题很长时间了,一直到最近失业了才有闲暇来解决。
        网上看到了PicList实现了云端删除功能,设想你写着文档还得打开另一个软件,还得找图片,还得删。有这个空挡我其实直接登上服务器也能删,感觉不是很方便。
        这篇文档介绍的这个方法只能说是相对方便一点,因为使用快捷键启动QuickAdd插件后,还得点一下自定义的删除菜单,要是能直接用快捷键删除就更好了。

2 安装依赖

前置条件: 已安装node 、npm

1
npm install -g basic-ftp 

实际上npm install basic-ftp应该也可以,但是一直没找到应该安装的路径,在vault根目录安装basic-ftp失败,ctrl+shift+i 查看obsidian 的console 总是提示找不到包。猜测是 ​QuickAdd 的 Node.js 环境隔离导致的,Obsidian 插件运行在特殊的 Electron 环境中,经测试直接安装依赖到插件目录依然无效,只能全局安装了。

3 创建删除脚本 (vault/ftp_delete_image.js)

脚本存放路径在仓库根目录:vault/ 或者仓库下的子目录: vaul/{任意子目录}/

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
const fs = require('fs');
//const ftp = require('basic-ftp');
//找不到包,只能指定全局目录了
const path = require('path');
const ftp = require(path.join('D:\\npm\\node_modules', 'basic-ftp'));

module.exports = async (params) => {
// 输入框测试
//const { inputPrompt } = params.quickAddApi;
//const val = await inputPrompt("输入测试内容");
//console.log(`你输入了: ${val}`);


// 弹窗提示1
// const { quickAddApi } = params; // 引入 QuickAdd API
// try {
// const result = await deleteFtpImage();
// await quickAddApi.infoDialog("操作结果", result.message); // 显示操作结果
// } catch (error) {
// await quickAddApi.infoDialog("操作失败", `删除失败:${error.message}`); // 显示失败提示
// }

// 弹窗提示2
try {
const result = await deleteFtpImage();
showPopup("操作结果", result.message);
} catch (error) {
showPopup("操作失败", `删除失败:${error.message}`);
}
};



function showPopup(header, message) {
const popup = document.createElement('div');
popup.innerHTML = `
<strong style="font-size: 18px; display: block;">${header}</strong>
<p style="margin: 10px 0 0;">${message}</p>
`;
popup.style.position = 'fixed';
popup.style.top = '50%';
popup.style.left = '50%';
popup.style.transform = 'translate(-50%, -50%)';
popup.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
popup.style.color = 'white';
popup.style.padding = '20px';
popup.style.borderRadius = '8px';
popup.style.boxShadow = '0 4px 10px rgba(0, 0, 0, 0.5)';
popup.style.zIndex = '9999';
popup.style.textAlign = 'center';
document.body.appendChild(popup);
// 自动关闭时间3s
const AUTO_CLOSE_TIME = 3000;
setTimeout(() => {
popup.remove();
}, AUTO_CLOSE_TIME);
}

/**
* 删除 FTP 上的远程图片文件
* @param {string} imageUrl - 图片的 HTTP URL
*从 HTTP URL 解析出 FTP 路径
*示例输入: http://192.168.200.104:8002/images/2025/03/29/20250329211710191.png
*目标路径: /PicGo/2025/03/29/20250329211710191.png
*/
async function deleteFtpImage() {
// 优先级:命令行参数 > 剪贴板内容
console.log("命令行参数:", process.argv);
let imageUrl;
if (process.argv.length >= 3 && process.argv[2].startsWith('http')) {
imageUrl = process.argv[0];
console.log("从命令行获取的URL:", imageUrl); // 调试输出

} else {
// 从剪贴板读取(需 Obsidian 剪贴板权限)
imageUrl = await navigator.clipboard.readText();
console.log("从剪贴板获取的URL:", imageUrl); // 调试输出

if (!imageUrl) {
console.error("错误:未提供图片 URL,且剪贴板为空");
process.exit(1);
}
}

// obsidian第一次点击图片,总会把')'也带上,只能先删了
imageUrl = imageUrl.replace(/\)$/, ""); // 使用正则表达式移除尾部右括号
console.log("已处理的URL:", imageUrl); // 输出处理后的 URL

const logFile = 'ftp_delete.log';
try {
// 我的PicGo网址规则是这样的,ftp上传后会显示为http, 因此要替换成ftp路径
const ftpPath = imageUrl.replace("http://192.168.200.104:8002/images/", "/PicGo/");
const client = new ftp.Client();
client.ftp.verbose = true;

await client.access({
host: "192.168.200.104",
user: "ftp",
password: "修改为真实密码",
secure: false
});

await client.remove(ftpPath);
console.log(`成功删除文件: ${ftpPath}`);
fs.appendFileSync(logFile, `${new Date().toISOString()} - INFO: 成功删除文件: ${ftpPath}\n`);
client.close();
return { success: true, message: "成功删除文件" };
} catch (err) {
console.error(`删除失败: ${err.message}`);
fs.appendFileSync(logFile, `${new Date().toISOString()} - ERROR: 删除失败: ${err.message}\n`);
return { success: false, message: err.message };
}
}

// 测试时直接调用函数(手动传参:http://192.168.200.104:8002/images/2025/03/29/20250329211710191.png)
// deleteFtpImage();

4 QuickAdd

安装插件,步骤略

4.1 ​配置 QuickAdd 宏

  • 打开 Obsidian 设置 → QuickAdd → ​Manage Macros → 输入宏名称Delete FTP images → Add Macro

  • 点击Configure添加以下 Action:

  • Action 1: Editor commands → ​Copy 主要目的是复制到剪贴板,把图片url传递给脚本

  • Action 2: User scripts选择脚本ftp_delete_image.js如果脚本存放路径不对是找不到脚本的,我花了点时间才发现需要放在仓库根目录

  • Action 3:Editor commands→Select active line

  • Action 4:Editor commands→Cut

    第三四步主要目的是删除当前行。

    添加宏到选项菜单

  • ​打开 Obsidian 设置 → QuickAdd→输入选项菜单名称Delete Images(自定义,任意输入即可)选择MacroAdd Choice

  • 点开Choice后面的选项⚙️→选择Macro。如果以前没用过,这是第一个Macro,QuickAdd会自动关联,可以省略这个步骤


5 使用流程

5.1 QuickAdd绑定快捷键

  • 打开 Obsidian 设置→快捷键→为QuickAdd设置快捷键,比如Alt+Q
  • 如果不绑定直接Ctrl+p→QuickAdd:Run QuckAdd 也能打开菜单,只不过有点慢

5.2 ​手动触发(可选):

  • 按下快捷键 → 输入图片URL → 自动删除远程文件。

    本来是这个方向,觉得不太方便,没有继续,需要修改脚本自行测试:

    1
    2
    3
    const val = await inputPrompt("输入测试内容");.
    deleteFtpImage(val);
    将变量传递给imageUrl就行了,可以不再读取剪贴板

5.3 自动获取图片URL​

  • 直接从当前笔记中提取URL,然后使用quickadd的宏功能调用js脚本删除

  • 左键单击图片→按下快捷键Alt+Q→选择刚才添加的Choice菜单Delete Images

6 参考

https://quickadd.obsidian.guide/docs/Choices/MacroChoice

https://github.com/chhoumann/quickadd/issues/15#issuecomment-864553251

7 思维扩散

  • 如果监听文件自动删除,很自然地就会联想到在Windows上有没有像inotify这样的东西,搜了下还真有——inotify-win,但是看着很长时间没更新了,windows更新这么快,也不知道这个软件还好不好用。如果监听本地文件的话,还要研究怎么实现图片本地链接可以支持多个客户端调用,怎么着也得把图片链接改成远端链接。不划算,这个想法以后再说吧。

  • 还有个想法,不知道有没有什么插件可以监听文本编辑状态,就像编程软件可以跳出提示一样,点击图片直接跳出“删除”选项。

  • QuickAdd应该只支持js, 一开始写了个python版的识别不到,搜索类似quickAdd支持python的插件。

  • 上传删除要用两个插件,这图片处理真是菜。不知道有没有同时支持这两个功能的插件,如果有请告诉我,不胜感激!🤝