Telegram Bot 开发小记

那个代表自由的小飞机!

Posted by Donggu Ho on 2018-03-15

TL;DR
Telegram(电报)是一个墙外的跨平台开源即时聊天工具,有包括群聊和频道(类似微信公众号)等功能。而 Telegram Bot 是在 Telegram 平台上的机器人账号,开发者可以以非常便捷的方式注册账号并进行各种功能的开发。

由于各种原因,在注册Telegram三年之后终于拿起来正式重度使用了。对于 Telegram 而言,其非常强盛的 Bot 生态不得不提。Telegram 可以说是开发者的乐土,各种各样的 API 和交互支持让你在对话的任何时刻都能得到 Telegram bot 的加持。从行内到命令到按钮,Telegram 仿佛就是一个 Geek 们用来 hack 世界的 portal 呢……前段时间开发 Telegram Channel 的自动推送机器人的时候翻了一下国内的文档发现不算多,基本都是聊天机器人的开发,但是除了聊天还能干很多事情啊!遂写Blog【

Telegram Bot

首先来注册一个 bot 吧。Bot 的注册也极其简单(非常 Telegram 风格了):

  1. 打开与 @BotFather 的对话框
  2. 发送/start
  3. 发送/newbot
  4. 发送 Bot 的名字(昵称)
  5. 指定 Bot 的用户名(ID)
  6. 好了完成

从以上过程我们可以得知,Telegram Bot 是父系社会(x

新建完毕后,Bot 就跟普通的用户一样可以搜索到和进行对话。不同的是普通用户会显示在线相关的状态,而 Bot 的相关区域则是显示bot。Bot 也可以修改头像、介绍等非必需的基础信息,同样可以通过 BotFather 来完成。
三秒钟就能新建一个 Bot,全程就在 Telegram 应用内完成,论一致性可以说是非常优秀,比起微信的重重绑定和审核可以说是轻而易举。无论是所谓国情也好所谓“克制”的产品精神也罢,电报身后显然是完全不同的一套价值观。自由灯塔Telegram没错了【滑稽

Telegram Bot 的交互种类和形式

首先介绍一下 Telegram Bot 可以进行的交互形式。作为 Telegram 的特殊账号(特权账号了几乎可以说是),Bot 能参与的交互实在是太多了。

一切真人用户能做的事情

首先,Bot 就是一个用户。所以它能够做很多事情:

  • 加入一个群组(群聊)
  • 成为一个频道的管理员,加人、发推送、删推送……
  • 回复别人的话
  • 在私聊以及群聊以及频道发送各种各样的文字图片表情定位音频视频信息(并没有任何条数限制!

各种真人不能做的事情

  • 发送带有可交互的按钮的信息
  • 发送带有复杂格式处理(markdown/html)的信息
  • 回复各种slash command(即命令)
  • 键盘菜单
  • inline 行内 Bot,主要是辅助对话,突出一个挥之即来(后文继续解释)

比如

  • 在群里@机器人进行签到
    一种群里有个小秘书的感觉!
  • 通过机器人来定时发送通知
  • 通过与机器人的对话(可保持上下文)完成一系列动作
    就像新建Bot时所做的一样
  • 在与他人的私聊中随时@inline bot来丰富对话内容
    这是inline bot @gif,你可以在跟任何人说话时@它 并输入关键字,他会搜索到相关动图让你迅速发送。
    @gif @gif
    同样类似的还有 @bold,可以提供信息的加粗/斜体等功能。
  • 使用键盘菜单进行交互
    //此处应有截图
  • 发送带有可交互按钮的信息,方便用户对某条信息进行反馈或更多操作
    比如点赞、投票、阅读原文等。
    // 比如阅读原文

因为 Telegram 官方提供的接口非常丰富了,所以只要发挥创意就可以很轻松地利用这个平台啦!

一个开发实践

首先是官方文档:Telegram Bots API
官方文档其实已经非常详尽,Telegram Bot 的一切动作都由发送 get/post 请求来完成,也基本没有奇怪的 header 要求,非常简明了。

这次的需求来自于想要查看学院网站的通知,网站没有做移动端适配访问起来比较麻烦,而且每天特意去查看有没有新通知感觉也非常煞笔。以前还是提供 RSS 订阅的,然而改版之后连 RSS 都没有了……就只能自己写个脚本爬一下。拿到新通知之后给我发个提醒就行。提醒的方式有很多,可是发邮件发微信都不如在 Telegram 频道发条消息简单——不需要任何环境,或是审核,只要发送一个请求就可以了。频道的设定非常适合通知的分发,想要惠及别人也非常轻松。

实现效果:https://t.me/sseNotice

爬取数据

既然是爬网站……那当然就是无脑选择 python 啊!
用了PyQuery来爬取网页内容,不得不说这个包真的太弱智太友好了写起来仿佛在写自然语言……

1
2
3
4
5
6
7
8
9
10
11
from pyquery import PyQuery as pq

doc = pq('http://sse.tongji.edu.cn/Data/List/xwdt')

for data in doc('.data-list>li').items():
link = data('a')
href = link.attr('href')
url = 'http://sse.tongji.edu.cn' + href
cnt = pq(url)
title = link.text()
html_content=cnt('.view-cnt').text()

每次获取时需要查看相比上一次新出现的通知,这个根据各个网页不同来进行定制。以同济大学软件学院的通知为例,网页 URL 的 ID 号就是时间排序了(版面并非完全按时间排序,有“置顶”),因此需要将最新的通知 ID 号存下来,下次更新的时候比对 ID 就能找到新的通知。略略略

发送到 Telegram

获取到数据之后只要发送请求到 Telegram 即可。基于python环境可以使用requests这种包,当然也可以使用有人已经做好的轮子进行。调试请求还是推荐使用 Postman 的,论 UI 论功能没哪家比得过了……

代理

……毕竟 Telegram 是被墙了的。在请求调试阶段还没写代码的时候,肯定是使用 Postman 调试请求格式。Postman 最近已经不再更新 Chrome 应用版本,转而推广其 Win/Mac 版本软件,pc版多了很多功能,其中最重要的就是设置代理了,因而强推。
代码部分,所以为了方便服务都配置到了咕果云上面,但本地调试时依然需要代理配置。幸好requests包自带了代理设置功能,不然设置 Linux 的全局变量也挺麻烦的。以 Shadowsocks 为例,通常本地代理端口为 localhost:1080,那么把代理设置好并传入参数即可:

1
2
3
4
5
6
proxies = {
"http": "http://localhost:1080",
"https": "http://localhost:1080"
}

requests.post(url, json=json, proxies=proxies) # 传入代理配置

Token

发送请求之前,首先需要获取对应 Bot 的 token。继续去跟 @BotFather 聊天吧,发送/token然后挑选对应 Bot 就行。

请求地址和格式

  • 地址
    Telegram Bot API 的地址拼接非常简单,https://api.telegram.org/bot<token>/METHOD_NAME,比如我的token是12345:glgjssy-qyhfbqz的话(并不),想要发送信息的时候,目标 URL 就是https://api.telegram.org/bot12345:glgjssy-qyhfbqz/sendMessage

  • 请求方法和数据格式
    POST 和 GET 都是支持的,不过要 GET 要注意请求长度和安全性的问题,所以也可以无脑发 POST。Telegram 支持以下四种数据格式:

    • URL query string (即直接在地址中加入参数)
    • application/x-www-form-urlencoded
    • application/json (除文件上传)
    • multipart/form-data (主要用于文件上传)

    ……也可以说是什么都支持了。个人选用人民群众喜闻乐见的 application/json

具体的请求数据格式方面,建议用 Postman 工具调试完毕后再写代码,大大降低不知道错在哪里的迷茫【。按照惯例总会有一个最简单的 API 作为 Hello world 存在,对于 Telegram Bot API 来说就是getMe接口了。不需要任何参数,只要拼接正确的地址且网络通畅就能获得正确的结果,可以作为调试网络和代理使用:

https://api.telegram.org/bot<token>/getMe

作为一个例子,使用sendMessage发送一条带有可交互按钮(此处是一个点击打开google的按钮)的请求数据格式如下:

1
2
3
4
5
6
7
8
9
10
11
 {
"chat_id": 131093671, # 对话id。若是频道则为“@channel_id”
"text": "**hi**", # 发送的信息正文。可以使用有限的几种 Markdown/HTML 进行标记
"reply_markup": { # 按钮
"inline_keyboard": [[{ # 附在信息之后的按钮。是个二维数组是因为可以有若干行,每行有若干个按钮
"text":"go", # 按钮文字内容
"url":"www.google.com" # 点击按钮跳转的目标链接
}]]
},
"parse_mode":"Markdown" # 消息的解析模式
}

效果如图:
msg
下面的 go 就是按钮惹。按钮除了打开链接还能干很多乱七八糟的事情,比如回调点函数什么的。有频道做了个点赞按钮emmm

INSTANT VIEW 即刻阅读

我自己XJB翻的请别给我发律师函(

对于大部分(原则上是所有)网页链接,Telegram 都会显示一个网页的摘要和标题:
preview

然后 Instant View 是什么呢,Instant View 是 Telegram 推出的一个平台,用来把一些网页的内容变得可以快速打开且易于移动设备阅读。阅读体验和现在的微博文章比较接近,点击立即打开,几乎没有加载时间(预加载),而且文字大小和图片排列干净,没有多余的动态效果和链接。和微博不同的是,Instant View 不是一个文章平台,你不需要把你的网页重新发布到 Telegram 上来获取这样的体验,你只需要在 Instant View 平台上添加一些规则来指引平台对相关网页内容进行提取,使其对你的网页进行适配即可。当你把相关模板规则发布出去,所有人浏览该网页的时候就会得到相同的 Instant 体验。具体适配规则等文档可见:Instant View

具体使用体验是这样的:被检测到支持 Instant View 的网页链接除了摘要预览之外还会多出一个 INSTANT VIEW 的按钮,点一下就能 biu 一下打开优秀的内容。

……什么叫包容!什么叫自由!【比划

……然而。因为 Telegram 被墙的原因,Telegram 服务器无法获取国内网页的内容(国内返回数据时被污染),所以 preview 和 instant view 都无法进行。想要彻底解决这个问题,只能把网站架设在墙外的服务器上。对于我这个通知转播来说,通知网页是学院的服务器,我总不能把学院服务器搬到国外(……),所以也可以在国外设置一个代理服务器,让 Telegram 通过代理服务器来访问这些内容。

……但就很麻烦。教练,能不能再简单一点啊?

可以的。虽然 Instant View 不是一个平台,但还有一个平台叫 Telegraph 呀!

Telegra.ph

看清一点哦, Telegram 和 Telegraph 并不是同一个单词【。Telegram 是电报的话, Telegraph 大概是……报文?

一看就知道这两个东西有 py 关系【。 Telegraph(网页是 telegra.ph)是 Telegram 推出的文章平台,以极其简单的新建、发布和阅读友好为特色。具体有多简单……你们点进去看看就知道了。也被墙了
telegraph

是的,不需要注册,不需要登录,只要写,然后发布,你就能获得一个文章链接……大概也就不需要审核的世界才能做到如此粗放自由。Telegraph 在 python 上同样有公开的包用来轻松新建文章获取链接。因为 Telegraph 已经进行了 Instant View 的适配,所以只要把通知生成一篇 Telegraph 文章并发送链接,就能得到完整的 preview 和优秀的移动阅读体验。

1
2
3
4
5
6
7
8
from telegraph import Telegraph

tg = Telegraph()
tg_page = tg.create_page(title=link.text(),
html_content=cnt('.view-cnt').text(),
author_name='SSE Notice Bot',
author_url='https://t.me/sseNotice')
tg_page[url] # 生成的文章链接

如此这般,也算是曲线救国啦。

一点轮子

处于一种对写轮子的好奇开始试图自己写一个 Telegram Bot API 的 python package。基于 requests 进行 API 的处理,目前还在进行中,有兴趣的同学可以 star:

完整代码

就不贴XD

相关链接汇总


下一篇 Blog 大概会写点有毒的 CSS 吧……