完整 Taio Action 脚本:优先自动读取剪切板发文

下面是已修改的完整 Taio Action 脚本:优先自动读取剪切板内容作为 Markdown 发文;若剪切板为空,再回退到动作输入。其余逻辑保持不变。

  
// Taio Action: Publish Markdown to Blog via /api/publish (blog only) - smart title - clipboard first
  

  
const BASE_URL = "https://yourdomain"; // 修改为你的后端地址
  
const ENDPOINT = `${BASE_URL}/api/publish`;
  
const TARGETS = ["blog"];
  

  
// 标题最长长度(中文/英文字符都按 1 计)
  
const TITLE_MAX_LEN = 40;
  

  
function defaultTags() {
  
  const d = new Date();
  
  return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, "0")}`;
  
}
  

  
// 优先从剪切板读取 Markdown,若为空再使用动作输入
  
const rawInput = ($clipboard && $clipboard.text) ? $clipboard.text : $actions.inputValue;
  
const md = (rawInput || "").trim();
  
if (!md) {
  
  $actions.reject("没有内容:请把要发布的 Markdown 放到剪贴板,或作为动作输入。");
  
}
  

  
// 智能截断为标题:优先按句号/问号/感叹号/换行截断,再按长度截断
  
function smartCut(str, maxLen) {
  
  const s = str.replace(/\s+/g, " ").trim();
  
  const punctIdx = (() => {
  
    const idx = [
  
      s.indexOf("。"),
  
      s.indexOf("!"),
  
      s.indexOf("?"),
  
      s.indexOf(". "),
  
      s.indexOf("! "),
  
      s.indexOf("? "),
  
      s.indexOf("\n")
  
    ].filter(i => i >= 0);
  
    return idx.length ? Math.min(...idx) : -1;
  
  })();
  
  let cut = punctIdx >= 0 ? s.slice(0, punctIdx) : s;
  
  if (cut.length > maxLen) cut = cut.slice(0, maxLen);
  
  return cut || "Untitled";
  
}
  

  
// 解析 YAML Front Matter 与标题
  
function parseMeta(markdown) {
  
  const meta = {};
  
  let body = markdown;
  

  
  // 兼容 \n 和 \r\n 的 front matter
  
  const fm = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
  
  if (fm) {
  
    body = markdown.slice(fm[0].length);
  
    fm[1].split(/\r?\n/).forEach(line => {
  
      const m = line.match(/^([^:#\s][^:]*):\s*(.*)$/);
  
      if (m) {
  
        const k = m[1].trim().toLowerCase();
  
        let v = m[2].trim();
  
        if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
  
          v = v.slice(1, -1);
  
        }
  
        if (k === "tags") {
  
          try {
  
            meta.tags = v.startsWith("[")
  
              ? JSON.parse(v.replace(/'/g, '"'))
  
              : v.split(",").map(s => s.trim()).filter(Boolean);
  
          } catch {
  
            meta.tags = v.split(",").map(s => s.trim()).filter(Boolean);
  
          }
  
        } else {
  
          meta[k] = v;
  
        }
  
      }
  
    });
  
  }
  

  
  // 标题:front matter -> 第一个 H1 -> 正文智能截断
  
  let title = meta.title;
  
  if (!title) {
  
    const h1 = body.match(/^\s{0,3}#\s+(.+?)\s*$/m);
  
    if (h1) title = h1[1].trim();
  
  }
  
  if (!title) {
  
    const firstNonEmptyLine = body.split(/\r?\n/).find(l => l.trim().length > 0) || "";
  
    title = smartCut(firstNonEmptyLine, TITLE_MAX_LEN);
  
  } else if (title.length > TITLE_MAX_LEN) {
  
    title = smartCut(title, TITLE_MAX_LEN);
  
  }
  

  
  return {
  
    title: title || "Untitled",
  
    tags: Array.isArray(meta.tags) ? meta.tags : [],
  
    body,
  
  };
  
}
  

  
// Markdown 转纯文本(供 content 字段)
  
function mdToPlain(markdown) {
  
  return markdown
  
    .replace(/```[\s\S]*?```/g, " ")
  
    .replace(/`+/g, "")
  
    .replace(/^\s{0,3}#{1,6}\s+/gm, "")
  
    .replace(/!\[([^\]]*)\]\((?:[^()]|\([^()]*\))*\)/g, "$1")
  
    .replace(/\[([^\]]+)\]\((?:[^()]|\([^()]*\))*\)/g, "$1")
  
    .replace(/(\*\*|__|\*|_)/g, "")
  
    .replace(/<[^>]+>/g, "")
  
    .replace(/[ \t]+\n/g, "\n")
  
    .replace(/\n{3,}/g, "\n\n")
  
    .trim();
  
}
  

  
function httpPostJSON(url, json) {
  
  return new Promise((resolve, reject) => {
  
    $http.request({
  
      method: "POST",
  
      url,
  
      header: { "Content-Type": "application/json" },
  
      body: json,
  
      handler: resp => {
  
        try {
  
          const status = resp.response ? resp.response.statusCode : resp.statusCode;
  
          const data = resp.data;
  
          if (status >= 200 && status < 300) resolve(data);
  
          else reject(new Error(`HTTP ${status}: ${typeof data === "string" ? data : JSON.stringify(data)}`));
  
        } catch (e) {
  
          reject(e);
  
        }
  
      }
  
    });
  
  });
  
}
  

  
(async () => {
  
  try {
  
    const { title, tags, body } = parseMeta(md);
  
    const content_md = body;
  
    const content_plain = mdToPlain(content_md);
  
    const tagStr = (Array.isArray(tags) && tags.length) ? tags.join(",") : defaultTags();
  

  
    const payload = {
  
      title,                 // 改进后的智能标题
  
      content: content_plain,
  
      content_md: content_md,
  
      tags: tagStr,
  
      targets: TARGETS
  
    };
  

  
    const result = await httpPostJSON(ENDPOINT, payload);
  
    const blog = result && result.blog;
  

  
    if (blog && blog.status === "success") {
  
      const url = blog.redirect_url || "(无跳转链接)";
  
      $actions.resolve(`博客发布成功:${url}`);
  
    } else {
  
      const msg = blog ? (blog.message || JSON.stringify(blog)) : JSON.stringify(result);
  
      $actions.reject(`博客发布失败:${msg}`);
  
    }
  
  } catch (err) {
  
    $actions.reject(`发布失败:${err.message || err}`);
  
  } finally {
  
    $actions.finish();
  
  }
  
})();