Hexo + TG bot 实现自动推送文章到telegram频道
突发奇想,hexo能不能实现自动推送功能。选择tg主要是因为tg的bot很强大(对比tx的小肚鸡肠,已经非常不错了)。翻了一圈,发现基本都是基于github action实现的推送。对github action实在不熟悉,复现也失败了,可能是因为需要完全通过github action构建并更新(?。只能曲线救国,结合github action的例子和tg api doc,用shell脚本完成自动推送。
核心流程为:申请bot
→ 编写shell(linux)
→ 添加脚本启动
申请tg bot
首先添加一个叫 BotFather 的机器人,用来申请bot
接下来会依次要求填写 name
和 username
name
和 username
是不同的。name
指bot显示的名字,username
指bot实际的名字(用来搜索的名字)
这里 name
为 gubai's bot
,username
为 gubai_blog_bot
申请成功后的消息中, t.me/gubai_blog_bot
可以访问bot,下面提供的token是这个bot的唯一标识,非常重要,不能泄露
但是访问bot发现没有任何响应,因为还没有编写逻辑
将bot拉入希望推送文章的频道/群组(别忘记给权限)
tg中每个聊天窗口有一个独一无二的 ChatID
邀请bot @get_id_bot 进入频道,发送命令 /my_id@get_id_bot
得到ChatID -1002492764196
为了规范频道,可以将所有和bot交互内容都删除。至于用来获得id的bot,可以踢掉,也可以禁言
编写shell
在任意目录看着顺眼的位置创建一个.sh文件,名字随意,这里是 TG_push.sh
,给执行权限
1 2 3 4 # !/bin/bash TG_BOT_TOKEN=xxx # 填入自己的bot token ChatID=xxx # 填入id
推送的思路有很多种,这里列出我的两种思路。
第二种是后来想起Front-matter不被渲染后实践的,效果很好,比第一种方式稳定,而且支持多文章自选推送。
下面详细介绍第二种方式的实现,第一种方式的代码会放到最后。
设想格式为:
1 2 3 4 5 6 7 8 9 10 11 12 ———————————— 主页:xxxxx 博客:xxxxx ———————————— 推送时间:xxxx ————update———— 标题:xxx 链接:xxx ———————————— 标题:xxx 链接:xxx ————————————
首先先完成 update
以上的部分,继续编写shell
1 2 3 4 5 6 7 8 9 10 11 currentTime=$(date +"%Y-%m-%d %H:%M:%S") #获取当前时间 commitMsg=" ———————————————— main: https://www.gubaiovo.com blog: https://blog.gubaiovo.com ———————————————— Time: ${currentTime} ——————UPDATE—————— "
这里要注意,换行不要直接写 \n
进行换行,会被各种神秘转义无法变成换行符。但是像上面在shell中编写时直接换行是可以的,而且最终呈现的效果也是换行。
这里可以先发送一次进行测试了。运行下方shell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # !/bin/bash TG_BOT_TOKEN=xxxxxx ChatID=xxxxxx currentTime=$(date +"%Y-%m-%d %H:%M:%S") #获取当前时间 commitMsg=" ———————————————— main: xxx blog: xxx ———————————————— Time: ${currentTime} ——————UPDATE—————— " curl -s -X POST https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage -d chat_id=$ChatID -d text="$commitMsg"
如果运行后像这样正常输出了,那么就完成一半了
如果运行后后台很久没反应,那么看看是不是代理出问题了
下面继续编写一个循环,用来遍历所有文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 path=/home/gubai/www/blog/source/_posts # 换成你的文章路径 # 这个函数用来追加需要push的文章信息 addUpdate() { local commitMsg="$1" local title="$2" local addrlink="$3" local link="https://blog.gubaiovo.com/posts/$addrlink.html" commitMsg+=$(\nprintf "Title: %s\nURL: %s\n" "$title" "$link") commitMsg+=$(printf "\n ————————————————") echo "$commitMsg" } for file in "$path"/*; do # 获取文件的前20行,也就是包含Front matter的范围。这里20可以根据你的Front matter的行数进行设定 fileContent=$(head -n 20 "$file") # 获取push字段。如果你用了其他字段作为key,那么将 'push:' 改为你设定的字段 push=$(echo "$fileContent" | grep 'push:' | awk -F': ' '{print $2}') # true用来决定推送,如果你用了其他的value来决定推送,改成你设定的value if [ "$push" == "true" ]; then # 提取 title 和 abbrlink title=$(echo "$fileContent" | grep 'title:' | awk -F': ' '{print $2}') addrlink=$(echo "$fileContent" | grep 'abbrlink: ' | awk -F': ' '{print $2}') # 这里调用了上方addUpdate()函数,用来追加message commitMsg=$(addUpdate "$commitMsg" "$title" "$addrlink") # 将推送后的文章的push字段设为false,防止重复push(如果用了其他的key和value,记得更改) sed -i 's/push: false/push: false/' "$file" fi done
现在shell脚本可以遍历所有文件,获得 push
字段,根据对应的value决定是否追加到message
目前为止的完整shell如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 # !/bin/bash TG_BOT_TOKEN=xxxxxx ChatID=xxxxxx currentTime=$(date +"%Y-%m-%d %H:%M:%S") #获取当前时间 path=/home/gubai/www/blog/source/_posts # 换成你的文章路径 commitMsg=" ———————————————— main: xxx blog: xxx ———————————————— Time: ${currentTime} ——————UPDATE—————— " # 这个函数用来追加需要push的文章信息 addUpdate() { local commitMsg="$1" local title="$2" local addrlink="$3" local link="https://blog.gubaiovo.com/posts/$addrlink.html" commitMsg+=$(\nprintf "Title: %s\nURL: %s\n" "$title" "$link") commitMsg+=$(printf "\n ————————————————") echo "$commitMsg" } for file in "$path"/*; do # 获取文件的前20行,也就是包含Front matter的范围。这里20可以根据你的Front matter的行数进行设定 fileContent=$(head -n 20 "$file") # 获取push字段。如果你用了其他字段作为key,那么将 'push:' 改为你设定的字段 push=$(echo "$fileContent" | grep 'push:' | awk -F': ' '{print $2}') # true用来决定推送,如果你用了其他的value来决定推送,改成你设定的value if [ "$push" == "true" ]; then # 提取 title 和 abbrlink title=$(echo "$fileContent" | grep 'title:' | awk -F': ' '{print $2}') addrlink=$(echo "$fileContent" | grep 'abbrlink: ' | awk -F': ' '{print $2}') # 这里调用了上方addUpdate()函数,用来追加message commitMsg=$(addUpdate "$commitMsg" "$title" "$addrlink") # 将推送后的文章的push字段设为false,防止重复push(如果用了其他的key和value,记得更改) sed -i 's/push: false/push: false/' "$file" fi done curl -s -X POST https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage -d chat_id=$ChatID -d text="$commitMsg"
虽然现在能实现获取需要push的文章并进行推送,但是,如果没有需要push的文章呢?
假设现在所有文章都没有push字段,或者push字段不是true,那么,最终的message是下面内容
1 2 3 4 5 6 7 8 ———————————————— main: xxx blog: xxx ———————————————— Time: ${currentTime} ——————UPDATE——————
bot会将这个板子推到频道,这显然不是我们想要的,我们想要的是没有需要push的就不push了。因此可以加一个变量 flag
用来标记是否需要推送。flag
初始化为 false
,如果在上方循环遍历中 if [ "$push" == "true" ]
成立了,那么就让 flag
为 true
。在发送请求时先检测flag值,如果flag为true,那么就发送请求
按照这个思路完善脚本,在最开头添加
在 if [ "$push" == "true" ]
判断中添加
1 2 3 4 5 6 7 8 if [ "$push" == "true" ]; then # 设为true 确定需要推送 flag=true title=$(echo "$fileContent" | grep 'title:' | awk -F': ' '{print $2}') addrlink=$(echo "$fileContent" | grep 'abbrlink: ' | awk -F': ' '{print $2}') commitMsg=$(addUpdate "$commitMsg" "$title" "$addrlink") sed -i 's/push: false/push: false/' "$file" fi
在发送请求前添加
1 2 3 if [ "$flag" == false ]; then exit 0 fi
如果flag为false,那么直接退出,也就不发送请求了。
修改后的shell
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 # !/bin/bash TG_BOT_TOKEN=xxxx ChatID=xxxx path=/home/gubai/www/blog/source/_posts currentTime=$(date +"%Y-%m-%d %H:%M:%S") flag=false commitMsg=" ———————————————— main: xxx blog: xxx ———————————————— Time: ${currentTime} ——————UPDATE—————— " addUpdate() { local commitMsg="$1" local title="$2" local addrlink="$3" local link="https://blog.gubaiovo.com/posts/$addrlink.html" commitMsg+=$(\nprintf "Title: %s\nURL: %s\n" "$title" "$link") commitMsg+=$(printf "\n ————————————————") echo "$commitMsg" } for file in "$path"/*; do fileContent=$(head -n 20 "$file") push=$(echo "$fileContent" | grep 'push:' | awk -F': ' '{print $2}') if [ "$push" == "true" ]; then flag=true title=$(echo "$fileContent" | grep 'title:' | awk -F': ' '{print $2}') addrlink=$(echo "$fileContent" | grep 'abbrlink: ' | awk -F': ' '{print $2}') commitMsg=$(addUpdate "$commitMsg" "$title" "$addrlink") sed -i 's/push: false/push: false/' "$file" fi done if [ "$flag" == false ]; then exit 0 fi curl -s -X POST https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage -d chat_id=$ChatID -d text="$commitMsg"
现在基本可以使用了。但是在使用过程中,会发现一个问题:title中特殊符号截断字符串
这是我在推送我的Ollama API调用时发现的,效果如下
可以看到,lua文章是正常的,但是ollama的文章因为 &
被截断了。我们需要对title进行编码
添加编码函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # URL 编码函数 urlencode() { local length="${#1}" local i=0 local c local encoded="" while [ $i -lt $length ]; do c="${1:$i:1}" # 使用 iconv 检查字符是否为ascii,避免非ascii码文字(如中文)错误编码 if echo "$c" | iconv -f UTF-8 -t ISO-8859-1 >/dev/null 2>&1; then case $c in [a-zA-Z0-9.~_-]) encoded+="$c" ;; *) encoded+=$(printf '%%%02X' "'$c") ;; esac else encoded+="$c" fi ((i++)) done echo "$encoded" }
调用函数,编码title
1 2 3 4 5 6 ... title=$(echo "$fileContent" | grep 'title:' | awk -F': ' '{print $2}') addrlink=$(echo "$fileContent" | grep 'abbrlink: ' | awk -F': ' '{print $2}') encodedTitle=$(urlencode "$title") commitMsg=$(addUpdate "$commitMsg" "$encodedTitle" "$abbrlink") ...
这样便正确实现了推送功能
自动调用推送
现在确实实现了推送,但是怎么才能在 hexo d
时自动调用呢
两种思路,一种为在shell cd xxx
进入blog根目录,调用hexo d
1 2 3 4 5 6 7 8 9 10 11 12 # !/bin/bash # 这两行位置没有影响 cd /home/gubai/www/blog hexo d TG_BOT_TOKEN=xxx ChatID=xxxx path=/home/gubai/www/blog/source/_posts currentTime=$(date +"%Y-%m-%d %H:%M:%S") flag=false ...
还有一种思路,使用hexo插件 hexo-deployer-shell
,这个插件为deploy添加新的类型 shell
,能够执行shell命令
github: https://github.com/HakurouKen/hexo-deployer-shell
i 1 npm install hexo-deployer-shell --save
安装后进入 hexo 的 _config.yml
,找到 deploy
项,像这样填写
1 2 3 4 5 6 deploy: - type: git repository: xxx branch: xxx - type: shell command: ~/www/blog/TG_push.sh
hexo在执行 hexo d
时,会按照 deploy
的顺序依次执行 git 和 shell,参考hexo文档
ALL CODE
第一种方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 # !/bin/bash TG_BOT_TOKEN=xxxxx ChatID=xxxxx path=xxxx currentTime=$(date +"%Y-%m-%d %H:%M:%S") # 获取文件夹中最新的文件名 latestFile=$(ls -t $path | head -n 1) # 获取最新文件的修改时间 latestFileTime=$(stat -c %Y "$path/$latestFile") # 获取当前时间的时间戳 currentTimeStamp=$(date +%s) # 计算时间差(秒) timeDiff=$((currentTimeStamp - latestFileTime)) # 如果时间差超过1800秒(30分钟),则不进行推送 if [ $timeDiff -gt 1800 ]; then exit 0 fi # 获取文件的前20行 fileContent=$(head -n 20 "$path/$latestFile") # URL 编码函数 urlencode() { local length="${#1}" local i=0 local c local encoded="" while [ $i -lt $length ]; do c="${1:$i:1}" # 使用 iconv 检查字符是否为中文 if echo "$c" | iconv -f UTF-8 -t ISO-8859-1 >/dev/null 2>&1; then case $c in [a-zA-Z0-9.~_-]) encoded+="$c" ;; *) encoded+=$(printf '%%%02X' "'$c") ;; esac else encoded+="$c" fi ((i++)) done echo "$encoded" } # 提取 title 和 abbrlink title=$(echo "$fileContent" | grep 'title:' | awk -F': ' '{print $2}') addrlink=$(echo "$fileContent" | grep 'abbrlink: ' | awk -F': ' '{print $2}') encodedTitle=$(urlencode "$title") commitMsg=" 推送时间:$currentTime 更新:$encodedTitle 文章链接:https://blog.gubaiovo.com/posts/$addrlink.html " curl -s -X POST https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage -d chat_id=$ChatID -d text="$commitMsg"
第二种方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 # !/bin/bash TG_BOT_TOKEN=xxxx ChatID=xxxxx path=xxxxx currentTime=$(date +"%Y-%m-%d %H:%M:%S") flag=false # URL 编码函数 urlencode() { local length="${#1}" local i=0 local c local encoded="" while [ $i -lt $length ]; do c="${1:$i:1}" # 使用 iconv 检查字符是否为中文 if echo "$c" | iconv -f UTF-8 -t ISO-8859-1 >/dev/null 2>&1; then case $c in [a-zA-Z0-9.~_-]) encoded+="$c" ;; *) encoded+=$(printf '%%%02X' "'$c") ;; esac else encoded+="$c" fi ((i++)) done echo "$encoded" } addUpdate() { local commitMsg="$1" local title="$2" local addrlink="$3" local link="https://blog.gubaiovo.com/posts/$addrlink.html" commitMsg+=$(printf "\nTitle: %s\nURL: %s\n" "$title" "$link") commitMsg+=$(printf "\n————————————————") echo "$commitMsg" } commitMsg=" ———————————————— main: https://www.gubaiovo.com blog: https://blog.gubaiovo.com ———————————————— Time: ${currentTime} ——————UPDATE—————— " for file in "$path"/*; do # 获取文件的前20行 fileContent=$(head -n 20 "$file") push=$(echo "$fileContent" | grep 'push:' | awk -F': ' '{print $2}') if [ "$push" == "true" ]; then flag=true # 提取 title 和 abbrlink title=$(echo "$fileContent" | grep 'title:' | awk -F': ' '{print $2}') addrlink=$(echo "$fileContent" | grep 'abbrlink: ' | awk -F': ' '{print $2}') encodedTitle=$(urlencode "$title") commitMsg=$(addUpdate "$commitMsg" "$encodedTitle" "$addrlink") sed -i 's/push: false/push: false/' "$file" fi done if [ "$flag" == false ]; then exit 0 fi curl -s -X POST https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage -d chat_id=$ChatID -d text="$commitMsg"
顾白的tg频道 https://t.me/gubaiblog