RunJS使用示例
// demo
console.log(siyuan);
console.log(plugin);
console.log(client);
console.log(thisBlock);
async function main() {
const response = await client.pushMsg({
msg: "This is a notification message", timeout: 7000,
});
console.log(response);
}
main();
plugin.saveAction(thisBlock.id, "Test Code");
// 隐藏所有已完成任务
async function main() {
// css 代码
const styleContent = `
[data-type="NodeListItem"][data-subtype="t"].protyle-task--done {
display: none;
}
`;
// 定义 style 的内容
const styleId = "ShowOrHideAllDoneTask";
// 判断是否加载
const isLoading = args.includes('load');
// 获取存储状态
let status = await api.getFile('/data/storage/'+styleId+'.json');
if(!status || status.code === 404) {
status = 'show';
} else {
status = status.status || 'show';
}
if(!isLoading) status = status === 'show' ? 'hide' : 'show';
if (status === 'show') {
// 获取现有的 <style> 元素
let styleElement = document.getElementById(styleId);
// 如果存在,则删除它
if(styleElement) styleElement.remove();
if(!isLoading){
await client.pushMsg({
msg: "已显示完成的任务", timeout: 3000,
});
await api.putFile('/data/storage/'+styleId+'.json', false, new Blob(['{"status":"show"}']));
}
} else {
// 如果不存在,则创建并添加它
const newStyle = document.createElement("style");
newStyle.id = styleId;
newStyle.textContent = styleContent;
document.head.appendChild(newStyle);
if(!isLoading){
await client.pushMsg({
msg: "已隐藏完成的任务", timeout: 3000,
});
await api.putFile('/data/storage/'+styleId+'.json', false, new Blob(['{"status":"hide"}']));
}
}
}
main();
plugin.saveAction(thisBlock.id, "显示/隐藏所有已完成任务");
// 上传到pipe
// 缺点:只能新增,不能更新(更新需要用下面那个示例)
// 暂不支持本地图片资源
// 如果不想把文档同时同步到链滴社区需要在Pipe->设置->账号中将”B3log Key“置空
// pipe长期未维护,请做好备份,勿发布重要资源
// 导出文档id(支持后代文档同时导出)
const docIds = [
'20240714035932-xws1cyt', // 思源
'20240802063219-pjexngc', // js
];
// pipe cookie(过期后可以重新到pipe网站复制新的cookie)
const pipeCookie = `
`;
async function main() {
docIds.forEach(async docId => {
//导出文档
const result = await siyuan.fetchSyncPost('/api/export/exportMd', {id:docId||getCurrentDocId()});
let zipPath = '/temp' + decodeURIComponent(result.data.zip);
zipPath = convertPath(zipPath);
// 上传文件
const url = 'https://pipe.b3log.org/api/console/import/md';
const filePath = window.siyuan.config.system.workspaceDir + zipPath;;
const data = await requestApi(url, 'POST', [
'--form', `file=@${filePath}`
]);
if(!data || data.code !== 0) {
client.pushErrMsg({msg:data.msg||'未知错误'});
} else {
client.pushMsg({msg: zipPath.split('/').pop()+'发布成功'});
}
});
}
async function requestApi(url, method='GET', args = []) {
const { spawn } = require('child_process');
// 构造 curl 参数
const curlArgs = [
...[
'--location',
'--request', `${method}`,
url,
'--header', `Cookie: ${pipeCookie.trim()}`,
],
...args
];
return new Promise((resolve, reject) => {
try {
// 执行 curl 命令
const curlProcess = spawn('curl', curlArgs);
// 监听标准输出
curlProcess.stdout.on('data', (data) => {
try {
const response = JSON.parse(data);
resolve(response);
} catch (e) {
reject(new Error(`Failed to parse response: ${data}`));
}
});
} catch (e) {
reject(new Error(`Failed to run spawn: ${e.message || '未知错误'}`));
}
});
}
function convertPath(inputPath) {
if(typeof path === 'undefined') path = require('path')
// 如果运行在 Windows 上,转换为 Windows 路径
if (process.platform === 'win32') {
return path.win32.normalize(inputPath);
}
// 否则保持原始路径(Linux/macOS 格式)
return path.posix.normalize(inputPath);
}
main();
plugin.saveAction(thisBlock.id, "上传到pipe");
/*
async function uploadFile(zipPath) {
try {
// 创建 Headers 对象并设置必要的头部信息
const myHeaders = new Headers();
myHeaders.append("Content-Type", "multipart/form-data");
myHeaders.append("cookie", pipeCookie.trim());
myHeaders.append("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36");
// 创建 FormData 并附加文件
const formdata = new FormData();
const zip = await getFile(zipPath);
formdata.append("file", new Blob([zip])); // 这里需要确保文件名是正确的
// 设置请求选项
const requestOptions = {
method: 'POST',
headers: myHeaders,
body: formdata,
credentials: 'include' // 确保 Cookie 正确传递
};
// 发送请求并等待响应
const response = await fetch("https://pipe.b3log.org/api/console/import/md", requestOptions);
// 检查响应状态
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// 获取响应文本
const result = await response.json();
if(!result || result.code !== 0) {
client.pushErrMsg({msg: result.msg||'未知错误'});
} else {
client.pushMsg({msg: '发布成功'});
}
} catch (error) {
console.error('Error:', error);
}
}
*/
// 发布当前文档到pipe
// 支持上传本地图片到链滴社区图床
// 如果不想把文档同时同步到链滴社区需要在Pipe->设置->账号中将”B3log Key“置空
// pipe长期未维护,请做好备份,勿发布重要资源
// pipe cookie(过期后可以重新到pipe网站复制新的cookie)
const pipeCookie = `
`;
// 禁止发布的文档列表
const disabledIds = [
//'',
];
// 链滴社区token,用于上传图片
const ld246Token = '';
// main
async function main() {
const currDocId = getCurrentDocId();
if(disabledIds.includes(currDocId)) {
client.pushErrMsg({msg:'此文档不允许发布'});
return;
}
// 获取Markdown文本
const doc = await fetchSyncPost("/api/lute/copyStdMarkdown", {id: currDocId});
if(!doc || doc.code !== 0) {
client.pushErrMsg({msg:doc.msg || '获取文档失败'});
return;
}
let markdown = doc.data || '';
markdown = await replaceImageLinks(markdown);
// 获取文档是否存在
const title = getCurrentDocTitle();
let articleId = '';
let list = {};
for(let i=0;i<3;i++){
try {
list = await requestApi('https://pipe.b3log.org/api/console/articles?p=1&key='+title+'&r='+new Date().getTime());
if(list && list.data && list.data.articles) {
articleId = list.data.articles.find(item => item.title === title)?.id;
}
break;
} catch(e) {
console.log('trys '+(i+1) + ' '+e.message);
await sleep(1000 * (i+1));
}
}
// 获取tag
let tags = [];
let docInfo = await query(`select hpath, tag from blocks where id = '${getCurrentDocId()}'`);
docInfo = docInfo[0];
if(docInfo) {
const hpath = docInfo['hpath']?.split('/')[1];
if(hpath) tags.push(hpath);
const tag = docInfo['tag']?.replace(/#/g, '') // 去掉所有的 '#'
?.split(/\s+/) // 按空格分割
?.filter(Boolean);
if(tag.length > 0) tags = [...tags, ...tag];
tags = tags.join(',');
}
// 数据
const data = JSON.stringify({
"title": title || '未命名文档',
"content": markdown || '',
"path": "",
"tags": tags || '笔记',
"commentable": true,
"topped": false,
"abstract": "",
"syncToCommunity": false,
"time": formatDateWithTimezone(new Date())
});
let response;
if(articleId) {
// 更新
response = await requestApi('https://pipe.b3log.org/api/console/articles/'+articleId, 'PUT', ['--data-raw', data]);
} else {
// 新增
response = await requestApi('https://pipe.b3log.org/api/console/articles', 'POST', ['--data-raw', data]);
}
if(!response || response.code !== 0) {
client.pushErrMsg({msg:data.msg||'发布失败'});
} else {
client.pushMsg({msg:'发布成功'});
}
}
// 获取当前文档id
function getCurrentDocId() {
return (document.querySelector('[data-type="wnd"].layout__wnd--active .protyle:not(.fn__none)')||document.querySelector('[data-type="wnd"] .protyle:not(.fn__none)'))?.querySelector('.protyle-title')?.dataset?.nodeId;
}
function getCurrentDocTitle() {
return (document.querySelector('[data-type="wnd"].layout__wnd--active .protyle:not(.fn__none)')||document.querySelector('[data-type="wnd"] .protyle:not(.fn__none)'))?.querySelector('.protyle-title__input')?.textContent;
}
async function fetchSyncPost(url, data, method = 'POST') {
return await (await fetch(url, {method: method, body: JSON.stringify(data||{})})).json();
}
function requestApi(url, method = 'GET', args = []) {
const { spawn } = require('child_process');
// 定义 curl 命令及其参数
const curlArgs = [...[
'--location',
'--request', method,
url,
'--header', `Cookie: ${pipeCookie.trim()}`,
'--header', 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36',
'--header', 'Content-Type: application/json;charset=UTF-8',
'--max-time', '60', // 设置最大总超时时间为 60 秒
], ...args];
return new Promise((resolve, reject) => {
// 启动子进程
const curlProcess = spawn('curl', curlArgs);
// 监听标准输出
let stdoutData = '';
curlProcess.stdout.on('data', (chunk) => {
stdoutData += chunk.toString();
});
// 监听标准错误输出
let stderrData = '';
curlProcess.stderr.on('data', (chunk) => {
stderrData += chunk.toString();
});
// 监听进程退出事件
curlProcess.on('close', (code) => {
if (code !== 0) {
reject(new Error(`Error output: ${stderrData}`));
return;
}
try {
const response = JSON.parse(stdoutData);
resolve(response);
} catch (e) {
//client.pushErrMsg({msg:'解析json失败'});
reject(new Error(`Failed to parse response: ${stdoutData}`));
}
});
});
}
function convertPath(inputPath) {
if(typeof path === 'undefined') path = require('path')
// 如果运行在 Windows 上,转换为 Windows 路径
if (process.platform === 'win32') {
return path.win32.normalize(inputPath);
}
// 否则保持原始路径(Linux/macOS 格式)
return path.posix.normalize(inputPath);
}
function formatDateWithTimezone(date) {
const timezoneOffset = -date.getTimezoneOffset();
const offsetHours = Math.floor(timezoneOffset / 60).toString().padStart(2, '0');
const offsetMinutes = (timezoneOffset % 60).toString().padStart(2, '0');
const offsetString = `${timezoneOffset >= 0 ? '+' : '-'}${offsetHours}:${offsetMinutes}`;
const isoString = date.toISOString().replace(/\..+/, '');
return isoString.replace('Z', offsetString)+'+08:00';
}
// 替换图片链接
async function replaceImageLinks(markdown) {
// 正则表达式:匹配带标题的图片语法
// const regex = /!\[([^\]]*)\]\(([^)]+?)(?:\s+"([^"]+)")?\)/g;
// 正则表达式:匹配以 assets/ 开头的图片路径
const regex = /!\[([^\]]*)\]\((assets\/[^)]+?)(?:\s+"([^"]+)")?\)/ig;
let newMarkdown = markdown; // 新的 Markdown 文本
const matches = [];
let match;
// 匹配所有图片
while ((match = regex.exec(markdown)) !== null) {
const altText = match[1]; // alt text
const imagePath = match[2]; // 图片路径
const title = match[3] || ''; // 可选的标题
// 上传图片并获取新 URL
const newImageUrl = await uploadImage(imagePath);
if(!newImageUrl) return newMarkdown;
// 构造新的图片语法
const newImageSyntax = title
? ``
: ``;
// 替换原始 Markdown 中的图片路径
newMarkdown = newMarkdown.replace(match[0], newImageSyntax);
}
return newMarkdown;
}
async function uploadImage(imagePath) {
const filePath = '/data/'+imagePath; // 文件路径
const uploadUrl = "https://ld246.com/upload/editor"; // 上传地址
const authToken = ld246Token; // 链滴社区授权令牌
// 调用上传函数并获取结果
const uploadResult = await uploadFile(filePath, uploadUrl, authToken);
if(!uploadResult?.data?.succMap) return '';
if(!Object.values(uploadResult.data.succMap)[0]) return '';
return Object.values(uploadResult.data.succMap)[0];
}
async function getFile(path) {
return fetch("/api/file/getFile", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
path,
}),
}).then((response) => {
if (response.ok) {
// 返回 Blob 对象而不是文本
return response.blob();
} else {
throw new Error("Failed to get file content");
}
}).catch((error) => {
console.error(error);
});
}
// 提取文件名的函数
function extractFileNameFromPath(filePath) {
// 使用 split() 方法按 '/' 分割路径,取最后一部分
const parts = filePath.split('/');
return parts[parts.length - 1];
}
// 封装上传文件的函数
async function uploadFile(filePath, uploadUrl, authToken) {
try {
// 获取文件内容作为 Blob
const fileBlob = await getFile(filePath);
// 自动提取文件名
const fileName = extractFileNameFromPath(filePath);
// 创建 FormData 并添加文件
const formdata = new FormData();
formdata.append("file[]", fileBlob, fileName);
// 设置请求头,包括 Authorization
const myHeaders = new Headers();
myHeaders.append("Authorization", `token ${authToken}`);
myHeaders.append("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36");
const requestOptions = {
method: 'POST',
headers: myHeaders,
body: formdata,
redirect: 'follow'
};
// 发送请求并解析响应为 JSON
const response = await fetch(uploadUrl, requestOptions);
const result = await response.json();
// 返回上传结果
return result;
} catch (error) {
console.error('Error:', error);
throw error; // 抛出错误以便调用者处理
}
}
async function query(sql) {
const result = await fetchSyncPost('/api/query/sql', { "stmt": sql });
if (result.code !== 0) {
console.error("查询数据库出错", result.msg);
return [];
}
return result.data;
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
main();
plugin.saveAction(thisBlock.id, "发布当前文档到pipe");
