Greasy Fork is available in English.
点击按钮即可将文章 原文 + Telegraph 自动保存到 Telegram 机器人。配置方法看说明。
// ==UserScript==
// @name Save to Telegraph
// @version 1.002
// @description 点击按钮即可将文章 原文 + Telegraph 自动保存到 Telegram 机器人。配置方法看说明。
// @match *://*/*
// @author yzcjd
// @author2 ChatGPT4辅助
// @namespace http://greasyfork.icu/users/1171320
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @run-at document-end
// @license MIT
// ==/UserScript==
(function () {
'use strict';
/***********************
* 读取配置
***********************/
let BOT_TOKEN = GM_getValue("BOT_TOKEN", "");
let CHAT_ID = GM_getValue("CHAT_ID", "");
let TELEGRAPH_TOKEN = GM_getValue("TELEGRAPH_TOKEN", "");
let MENU_HIDDEN = GM_getValue("MENU_HIDDEN", false);
const menuHandles = {};
/***********************
* 菜单管理
***********************/
function registerMenus(){
for (let key in menuHandles){
GM_unregisterMenuCommand(menuHandles[key]);
}
if(!MENU_HIDDEN){
menuHandles.bot = GM_registerMenuCommand("设置 BOT_TOKEN", () => {
const v = prompt("输入 BOT_TOKEN", BOT_TOKEN);
if(v!==null){
BOT_TOKEN = v.trim();
GM_setValue("BOT_TOKEN", BOT_TOKEN);
}
});
menuHandles.chat = GM_registerMenuCommand("设置 CHAT_ID", () => {
const v = prompt("输入 CHAT_ID", CHAT_ID);
if(v!==null){
CHAT_ID = v.trim();
GM_setValue("CHAT_ID", CHAT_ID);
}
});
menuHandles.tele = GM_registerMenuCommand("设置 TELEGRAPH_TOKEN", () => {
const v = prompt("输入 TELEGRAPH_TOKEN", TELEGRAPH_TOKEN);
if(v!==null){
TELEGRAPH_TOKEN = v.trim();
GM_setValue("TELEGRAPH_TOKEN", TELEGRAPH_TOKEN);
}
});
menuHandles.hide = GM_registerMenuCommand("隐藏菜单", () => {
MENU_HIDDEN = true;
GM_setValue("MENU_HIDDEN", true);
registerMenus();
});
}else{
menuHandles.show = GM_registerMenuCommand("显示菜单", () => {
MENU_HIDDEN = false;
GM_setValue("MENU_HIDDEN", false);
registerMenus();
});
}
}
registerMenus();
document.documentElement.setAttribute("translate", "no");
/***********************
* 按钮
***********************/
const style = document.createElement('style');
style.textContent = `
#save-button {
position: fixed;
top: 90px;
right: 5px;
z-index: 99999;
background: #f5f5f5;
color: #000;
padding: 4px 8px;
border-radius: 6px;
border: 1px solid #ccc;
cursor: pointer;
font-size: 12px;
transform: scale(0.75);
transition: all 0.3s ease;
}
#save-button.success {
background: #4CAF50;
color: #fff;
border-color: #4CAF50;
}`;
document.head.appendChild(style);
const btn = document.createElement("div");
btn.id = "save-button";
btn.innerText = "save";
document.body.appendChild(btn);
/***********************
* 工具函数
***********************/
function escapeMarkdown(text) {
return text.replace(/[_*\[\]()]/g, "\\$&");
}
function absoluteUrl(url) {
try {
return new URL(url, location.href).href;
} catch {
return url;
}
}
/***********************
* DOM → Telegraph JSON
***********************/
function nodeToTelegraph(node) {
if (node.nodeType === 3) {
const text = node.textContent;
return text.trim() ? text : null;
}
if (node.nodeType !== 1) return null;
const allowedTags = [
"p","b","strong","i","em",
"a","img","h2","h3","h4",
"blockquote","ul","ol","li",
"pre","code","br"
];
const tag = node.tagName.toLowerCase();
// 视频
if (tag === "video") {
const src = node.src || node.querySelector("source")?.src;
if (src) {
return {
tag: "p",
children: [
{ tag: "a", attrs: { href: absoluteUrl(src) }, children: ["🎬 视频链接"] }
]
};
}
}
if (tag === "iframe") {
if (node.src) {
return {
tag: "p",
children: [
{ tag: "a", attrs: { href: absoluteUrl(node.src) }, children: ["🎬 视频链接"] }
]
};
}
}
if (!allowedTags.includes(tag)) {
return Array.from(node.childNodes)
.map(child => nodeToTelegraph(child))
.flat()
.filter(Boolean);
}
let obj = { tag };
if (tag === "a" && node.href) {
obj.attrs = { href: absoluteUrl(node.href) };
}
if (tag === "img") {
let src =
node.src ||
node.getAttribute("data-src") ||
node.getAttribute("data-original");
if (!src || src.startsWith("data:image")) return null;
src = absoluteUrl(src);
src = src.replace(/^https?:\/\//, "");
obj.attrs = {
src: "https://images.weserv.nl/?url=" + src
};
}
let children = [];
if (tag === "p" && node.style && node.style.textIndent) {
children.push(" ");
}
children = children.concat(
Array.from(node.childNodes)
.map(child => nodeToTelegraph(child))
.flat()
.filter(Boolean)
);
if (children.length > 0) obj.children = children;
return obj;
}
/***********************
* 创建 Telegraph
***********************/
async function createPage(title, nodes) {
const form = new URLSearchParams();
form.append("access_token", TELEGRAPH_TOKEN);
form.append("title", title);
form.append("content", JSON.stringify(nodes));
form.append("return_content", "false");
const response = await fetch("https://api.telegra.ph/createPage", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: form.toString()
});
return await response.json();
}
function splitNodes(nodes, max = 20000) {
const parts = [];
let current = [];
let size = 0;
for (let n of nodes) {
const str = JSON.stringify(n);
size += str.length;
if (size > max) {
parts.push(current);
current = [];
size = str.length;
}
current.push(n);
}
if (current.length) parts.push(current);
return parts;
}
function getSuffix(i, total) {
if (total === 1) return "存档";
if (total === 2) return ["上", "下"][i];
if (total === 3) return ["上", "中", "下"][i];
return (i + 1).toString();
}
async function sendMessage(text) {
await fetch(`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
chat_id: CHAT_ID,
text: text,
parse_mode: "Markdown",
disable_web_page_preview: true
})
});
}
/***********************
* 主逻辑
***********************/
btn.onclick = async function () {
try {
if (!BOT_TOKEN || !CHAT_ID || !TELEGRAPH_TOKEN) {
alert("请先填写 Token");
return;
}
btn.innerText = "处理中...";
const titleRaw =
document.querySelector('meta[property="og:title"]')?.content
|| document.title;
const title = escapeMarkdown(titleRaw);
const url = location.href;
let article =
document.querySelector("article") ||
document.querySelector("main") ||
document.body;
const clone = article.cloneNode(true);
clone.querySelectorAll("script,style,noscript").forEach(e => e.remove());
const nodes = Array.from(clone.childNodes)
.map(n => nodeToTelegraph(n))
.flat()
.filter(Boolean);
if (nodes.length === 0) {
alert("正文提取失败");
btn.innerText = "save";
return;
}
let result = await createPage(titleRaw, nodes);
let links = [];
if (result.ok) {
links.push(result.result.url);
} else if (result.error === "CONTENT_TOO_BIG") {
const parts = splitNodes(nodes);
for (let i = 0; i < parts.length; i++) {
const partTitle = `${titleRaw} (${getSuffix(i, parts.length)})`;
const r = await createPage(partTitle, parts[i]);
if (r.ok) links.push(r.result.url);
}
} else {
console.error(result);
alert("Telegraph 创建失败");
btn.innerText = "save";
return;
}
if (links.length === 0) {
alert("保存失败");
btn.innerText = "save";
return;
}
let archive;
if (links.length === 1) {
archive = `[存档](${links[0]})`;
} else {
archive = links
.map((u, i) => `[${getSuffix(i, links.length)}](${u})`)
.join(" ");
}
const message =
`[${title}](${url}) || ${archive}`;
await sendMessage(message);
btn.classList.add("success");
btn.innerText = "✓ 已保存";
} catch (err) {
console.error(err);
alert("脚本异常");
btn.innerText = "save";
}
};
})();