小小呵博客
小小呵博客
/
Cloudflare Worker获取Telegram公开频道的信息,支持以Memos的接口形式返回数据
Search

Cloudflare Worker获取Telegram公开频道的信息,支持以Memos的接口形式返回数据

Tags
Published
Author
💡
话不多说💊🎈
首先新建一个频道,并记下频道ID
notion image
新建一个Cloudflare Worker,复制下面的代码粘贴进去
tgtalk.js代码:
addEventListener('fetch', event => { event.respondWith(TelgramChannelStarter(event.request)) }) const ChannelNameList = ['thun888sthinks', 'Thun888sDraftArticles', 'thun888sgrocery']; const deniedRegion = [""]; const baseproxyurl = "https://get-tg-channel-api.hzchu.top/?proxy=" async function TelgramChannelStarter(request) { const url = new URL(request.url); const Region = request.headers.get('cf-ipcountry'); let denied = deniedRegion.includes(Region); const proxyUrl = url.searchParams.get('proxy'); let mid = parseInt(url.searchParams.get('mid')); let cid = parseInt(url.searchParams.get('cid')) || 0; let tagname = url.searchParams.get('tag') || "DefaultTag"; let showtag = url.searchParams.get('showtag') || "true"; let usemd = url.searchParams.get('usemd') || "true"; usemd = usemd === 'true'; showtag = showtag === 'true'; let showview = url.searchParams.get('showview'); showview = showview === 'true'; let usetgclassification = url.searchParams.get('usetgclassification'); usetgclassification = usetgclassification === 'true'; let ChannelName = ChannelNameList[cid] if (!!proxyUrl) { if (!(proxyUrl.match(/\:\/\/(.*?)\.telegram\.org/g) || proxyUrl.match(/\:\/\/(.*)\.cdn\-telegram\.org/g) || proxyUrl.match(/\:\/\/telegram\.org/g))) { return new Response('Proxy URL is not valid'); } return fetch(proxyUrl); } const offset = url.searchParams.get('offset'); let ChannelUrl if (usetgclassification){ ChannelUrl = new URL(`https://t.me/s/${ChannelName}?q=%23${tagname}`); }else{ ChannelUrl = new URL(`https://t.me/s/${ChannelName}`); } if (!!offset) { ChannelUrl.searchParams.set('before', offset); } let getDataFromTelegram = await fetch(ChannelUrl, { "headers": { "x-requested-with": "XMLHttpRequest" }, "method": "POST" }).then(res => res.text()) .then(res => res .replace(/\\n/g, '') .replace(/\\(.)/g, '$1') .replace(/(^\"|\"$)/g, '') ); if (url.searchParams.get('rawHtml') === 'true') { return new Response(getDataFromTelegram, { headers: { "content-type": "text/html;charset=UTF-8", "Access-Control-Allow-Origin": "*" } }); } const nextBeforeMatch = [...getDataFromTelegram.matchAll(/data-before="([0-9]+)"/g)]; const nextBefore = nextBeforeMatch.length > 0 ? Number(nextBeforeMatch[0][1]) : 0; const ChannelMessages = ElementSpliter(getDataFromTelegram, '<div class="tgme_widget_message_wrap'); const ChannelMessageData = {}; ChannelMessages.forEach(ChannelMessage => { const MessageIdMatch = ChannelMessage.match(new RegExp(`data-post\="${ChannelName}\/(\\d+)"`)); if (MessageIdMatch) { const MessageId = MessageIdMatch[1]; // 确保消息ID是唯一的,并且没有重复 if (!ChannelMessageData.hasOwnProperty(MessageId)) { const MessageTextMatch = ChannelMessage.match(/<div class="tgme_widget_message_text js-message_text"(.+?)<\/div>/s); const MessagePhotoMatches = [...ChannelMessage.matchAll(/background-image:url\('(.+?)'\)/g)]; const MessageTimeMatch = ChannelMessage.match(/datetime="(.+?)"/); const MessageViewsMatch = ChannelMessage.match(/<span class="tgme_widget_message_views">\s*(\d+)\s*<\/span>/); let msg_views = MessageViewsMatch ? MessageViewsMatch[1] : "0"; let msg_time = MessageTimeMatch ? Math.round(new Date(MessageTimeMatch[1]).getTime() / 1000) : Math.round(new Date().getTime() / 1000); let msg_text = MessageTextMatch[0].replace(/<div (.*?)>/g, '').replace(/<\/div>/g, ''); msg_text = msg_text.replace(/href=\"\?q=(.+?)\"/g, function (match, p1) { return 'href="https://t.me/s/' + ChannelName + '/' + p1 + '"'; }); // emoji处理 msg_text = msg_text.replace(/<i class="emoji" style="background-image:url\(\'\/\/telegram.org\/img\/emoji\/40\/.+?\)\">/g, '') // msg_text = msg_text.replace(/<i class="emoji".*?url\(\'(.*?)\'\).*?<\/i>/g, function(match, p1) { // // 此处代码结合stellar样式,但不太理想 // return `<span class="tag-plugin emoji"><img src="${baseproxyurl}https:${p1}"></span>`; // }); // 是否显示标签 if (!showtag) { msg_text = msg_text.replace(/<a href="https:\/\/t\.me\/s\/.+?\/%23.+?">#[^<]+<\/a>/g, ''); } // 是否显示浏览量 if (showview) { msg_text = msg_text + '<br>(👀:' + msg_views + ')' } // 是否使用Markdown if (usemd) { // 将<a>标签替换为md格式 msg_text = msg_text.replace(/<a href=\"(.*?)\">(.*?)<\/a>/g, (match, $1, $2) => { if ($2.startsWith('#')) { return `[${$2}](${$1})`; } else { return $2; } }); // 将&#33;替换为! msg_text = msg_text.replace(/&#33;/g, '!'); // 将&#37;替换为% msg_text = msg_text.replace(/&#37;/g, '%'); // 将<br/> 替换为换行 msg_text = msg_text.replace(/<br\/>/g, '\n'); // 将&lt;替换为< msg_text = msg_text.replace(/&lt;/g, '<'); // 将&gt;替换为> msg_text = msg_text.replace(/&gt;/g, '>'); // 将&amp;替换为& msg_text = msg_text.replace(/&amp;/g, '&'); // 将&quot; 替换为" msg_text = msg_text.replace(/&quot;/g, '"'); } if (MessageTextMatch && (!denied && MessageTextMatch[0].includes('#' + tagname))) { let resourceList = MessagePhotoMatches.filter(m => !m[1].includes('//telegram.org/img/emoji/')) .map(m => ({ "createdTs": msg_time, "externalLink": baseproxyurl + m[1], "type": "image/jpeg", "size": 0 })); ChannelMessageData[MessageId] = { id: MessageId, name: MessageId, rowStatus: "NORMAL", creatorId: 1, content: msg_text, createdTs: msg_time, updatedTs: msg_time, displayTs: msg_time, visibility: "PUBLIC", pinned: false, creatorName: ChannelName, creatorUsername: ChannelName, resourceList: resourceList, relationList: [], views: msg_views, }; } } } }); if (mid) { return new Response(JSON.stringify([ChannelMessageData[mid]]), { headers: { "content-type": "application/json;charset=UTF-8", "Access-Control-Allow-Origin": "*" } }); } // 如果设置了limit参数,则按照时间倒序排序,并限制输出的消息数量 let sortedMessages = Object.values(ChannelMessageData).sort((a, b) => b.createdTs - a.createdTs); const messagesCountParam = url.searchParams.get('limit'); const messagesCount = messagesCountParam ? parseInt(messagesCountParam, 10) : sortedMessages.length; sortedMessages = sortedMessages.slice(0, messagesCount); return new Response(JSON.stringify(sortedMessages), { headers: { "content-type": "application/json;charset=UTF-8", "Access-Control-Allow-Origin": "*" } }); } const ElementSpliter = (html, StartElement) => { const Elements = [] const ElementSpliterOnce = (html, StartElement) => { let ElementName = [...StartElement.matchAll(/\<(?<ELENAME>[a-zA-Z0-9]+)\s/g)][0].groups.ELENAME let ElementContent = StartElement for (let Start = html.indexOf(StartElement) + StartElement.length; Start < html.length; Start++) { ElementContent += html[Start] if (ElementContent.endsWith(`</${ElementName}>`)) { const PrefixCount = Object.keys(ElementContent.match(new RegExp(`\<${ElementName}`, 'g'))).length const SuffixCount = Object.keys(ElementContent.match(new RegExp(`\<\/${ElementName}(.*?)>`, 'g')) || []).length if ( PrefixCount === SuffixCount && PrefixCount !== 0 ) { return ElementContent } } } } while (1) { if (html.indexOf(StartElement) === -1) break const SplitOnce = ElementSpliterOnce(html, StartElement) Elements.push(SplitOnce) html = html.replace(SplitOnce, '') } return Elements }
并修改顶部配置
const ChannelNameList = ['thun888sthinks']; //频道ID,可添加多个 const deniedRegion = [""]; 屏蔽区域,如CN,US const baseproxyurl="https://get-tg-channel-api.hzchu.top/?proxy=" //将中间域名替换为你为worker绑定的域名

可传入参数

参数
参数名
默认值
描述
cid
频道id
0
指在代码里配置的第n个频道,并不是上文所述频道id
mid
信息id
/
指定后只返回该信息,信息id可在发送后查看
tag
标签名
DefaultTag
筛选传入的标签返回
limit
获取条数
/
/
offset
获取信息偏移量
/
如果有一条信息id为80的信息,那么当传入80时将返回这条信息之前的所有信息
showtag
是否展示标签
true
/
showview
是否展示访问量
false
启用时会在末尾附上(views:number)
usemd
是否使用Markdown
true
开启后返回未被tg解析过的md语法
usetgclassification
是否使用tg自带的分类
false
开启后使用tg自带的分类来进行标签筛选,防止过久导致无法显示。但注意会有缓存
 
更行了cloudflare worker代码通过输入: https://你的域名/?num=3
可以获取最近的三条信息
众所周知,0是第一个自然数,所以cid传入0时表示获取第1个频道
tag为空时默认返回#DefaultTag的数据,可在代码中自行更改
tag原版是#SFCN,一开始没想明白是什么意思,现在明白了:Suitable For China,可以有别的玩法o( ̄▽ ̄)d
这里有个小功能:如果你输入
https://你的域名/?limit=5
便可以得到最近的五条消息
usemd: 在默认情况下,tg会解析md语法,对于不支持的语法会转义,从而导致前端无法正常解析md语法,在使用该参数后程序会将解析后的文本还原为正常md语法并返回。默认开启
 
我的修改:
tag筛选模糊改成精确
if (MessageTextMatch && (!denied && MessageTextMatch[0].includes('#' + tagname)))
改成了
if (MessageTextMatch && (!denied && new RegExp(#${tagname}(?![0-9a-zA-Z\\u4e00-\\u9fa5])).test(MessageTextMatch[0])))
我的演示:
https://mima.kuhehe.vip
可以输入【公告】两个字试试效果
参考:
字节君字节君stellar主题集成Telegram Channel说说
stellar主题集成Telegram Channel说说

stellar主题集成Telegram Channel说说

前言 众所周知,hexo主题stellar集成的说说功能是利用Github issue API来支持的 何为“说说”,即在某一瞬间,想要记载的内容。因此发说说需要操作方便。 在之前,我使用的是别人提供的微信公众号转Github issue来发说说,但因为某些原因,该公众号关闭了服务 如果有兴趣在微信公众号上发说说的可以参考这篇文章: https://github.com/xaoxuu/hexo-

字节君字节君
Thun888Thun888把Tg Channel接入到Stellar时间线
把Tg Channel接入到Stellar时间线

把Tg Channel接入到Stellar时间线

使用Cloudflare Worker,获取Tg频道内容,并接入到Stellar时间线

Thun888Thun888
 
Copyright 2026 小小呵