RunJS 使用示例

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
         ? `![${altText}](${newImageUrl} "${title}")`
         : `![${altText}](${newImageUrl})`;

      // 替换原始 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");

image.png

留下你的脚步
推荐阅读