最近借助 golang 的 go-telegram-bot-api/telegram-bot-api 实现了个 Telegram 机器人,用来处理一些事务,比如部署网站什么的。后来又实现了一个功能,将发送给机器人的文件同步到 onedrive 上去。图片什么的都没问题,大一点的文件就获取不了下载链接了,群里问了下原来是 TG 官方对机器人下载文件有 20MB 的大小限制。。。😭

第二天有人发了个关于 Local Bot API Server 的链接,其实就在 Telegram bot api 文档里,只是之前没仔细看完。原来可以在自己的服务器上部署一个 bot api,将机器人接到这个 api 上就能突破官方的限制了,Local Bot API Server 主要功能有:

  • 下载没有文件大小限制
  • 上传的文件最高可达 2000 MB
  • 使用文件的本地路径和文件 URI 方案上传文件
  • 将 HTTP 网址用于 Webhook
  • 使用任何本地IP地址作为 Webhook
  • 将任何端口用于 Webhook
  • 设置 max_webhook_connections 最多为100000
  • 接受本地绝对路径作为 file_path 字段的值,而无需在getFile请 求之后下载文件

部署 Local Bot API Server

这一步是超级简单的,因为有一个保姆式的编译指南 Telegram Bot API server build instructions generator

但是我在 1核1G 的机器上编译阶段老是不成功,可能是资源占用高的原因,于是我用笔记本编译之后把可执行文件 telegram-bot-api 上传到了服务器,不过需要注意的是,指南中的依赖还是需要安装的:

sudo apt-get install make git zlib1g-dev libssl-dev gperf cmake g++

然后在 telegram 官网 获取 api_idapi_hash

这时执行以下命令 local bot api server 就在服务器上跑起来啦

telegram-bot-api --api-id API_ID --api-hash API_HASH --local

需要加上 --local 才能解锁上述独有功能

有机器人接入的时候会在当前目录生成一个以该机器人 token 命名的目录,所有类型的文件都保存会在这里

机器人接入 Local Bot API Server

代码上主要是以下逻辑:

  • 先用原来的 api 收取官方服务器上的未读消息
  • 调用 logOut 方法从官方服务器登出
  • 将 api 的 “https://telegram.org” 替换为自己的 url,如 “http://127.0.0.1:8081”, 因为 local server 只接受 “http”,默认监听 “8081” 端口。后面的 token + method 参数保持不变
  • 使用替换后的 api 获取消息

使用 go-telegram-bot-api/telegram-bot-api 包遇到的问题

emmmm 这个包截至我写这篇博客还不支持 local bot api server,主要是 api 在包里被定义为了常量,无法修改,以及没有 logOut 方法。。。只能手动改了。。

首先在 config.go 中把常量 APIEndpointFileEndpoint 改为变量,使用 init() 函数在程序跑起来之前为变量赋初值,之后调用 SwitchAPIEndpointToLocal 函数修改 api:

// APIEndpoint is the endpoint for all API methods,    
// with formatting for Sprintf.
var APIEndpoint string
// FileEndpoint is the endpoint for downloading a file from Telegram.
var FileEndpoint string

func init() {
    APIEndpoint = "https://api.telegram.org/bot%s/%s"        
    FileEndpoint = "https://api.telegram.org/file/bot%s/%s"
}

// SwitchAPIEndpointToLocal switch the API endpoint to local bot api server
// with customize IP or domain, and port
// local bot api server only support HTTP requests
func SwitchAPIEndpointToLocal(host, port string) {
    APIEndpoint = fmt.Sprintf("http://%s:%s", host, port) + "/bot%s/%s"
}

然后在 bot.go 中添加 logOut() 方法:

// LogOut log out from the cloud Bot API server before launching the bot locally
func (bot *BotAPI) LogOut() error {
    _, err := bot.MakeRequest("logOut", nil)
    if err != nil {
        return err
    }

    return nil
}

但在 github 上 fork 的仓库修改之后在 golang 中导入的依然是源仓库的代码!

我在这里折腾了好久,使用指定的 commit 也不行,最后在 stack overflow 上看到原来这时 golang 的问题,如果要用必须清除跟源仓库的一切联系。。好在这个包并不复杂,把文件复制过来作为一个本地包即可。

其次需要注意的是这个包使用一个 goroutine 来接收消息,需要:

  • 先用 bot.StopReceivingUpdates() 来停止接受消息
  • 然后再用 bot.LogOut() 登出
  • 最后用 SwitchAPIEndpointToLocal() 切换 api
// 消息接收 channel 是 goroutine,logOut 之后会请求失败导致报错
bot.StopReceivingUpdates()
time.Sleep(time.Second)

// logOut 依然需要用官方 api 来调用
err := bot.LogOut()
if err != nil {
    log.Panic(err)
}

tgbotapi.ReplaceEndpointWithLocal()

顺序不对也会报错 😂

切换 api 之后调用 tgbotapi.NewBotAPI(config.BotToken)bot.GetUpdatesChan() 生成新的 bot 对象和消息 channel 就行啦!

bot, err := tgbotapi.NewBotAPI(config.BotToken)
if err != nil {
    log.Panic(err)
}
// Debug 字段需要赋值,不然会报错
bot.Debug = false

u := tgbotapi.NewUpdate(0)
u.Timeout = 60
updates, err := bot.GetUpdatesChan(u)
if err != nil {
    log.Panic(err)
}

如果需要把文件下载到本地,调用 bot.GetFile() 即可~

_, err := bot.GetFile(tgbotapi.FileConfig{FileID:message.Video.FileID})
if err != nil {
    log.Error("failed to download file", err)
}

Comments