使用网络挂钩防止从 Telegram Bot API 获取旧更新
Prevent getting old updates from Telegram Bot API using a web hook
我正在编写一个 Telegram 机器人,我正在使用 official bot API。我有一个 webhook 服务器可以处理请求并为每个请求发送 200 OK
响应。
在服务器停止之前,webhook 已分离,因此 Telegram 不再发送更新。但是,每当我打开机器人并再次设置 webhook URL 时,Telegram 就会开始用旧更新淹没 webhook 服务器。
有什么方法可以避免这种情况,而无需重复请求 /getUpdates
直到我到达最后更新?
这是我的代码的高度简化版本:
var http = require('http'),
unirest = require('unirest'),
token = '***';
// Attach the webhook
unirest.post('https://api.telegram.org/bot' + token + '/setWebhook')
.field('url', 'https://example.com/api/update')
.end();
process.on('exit', function() {
// Detach the webhook
unirest.post('https://api.telegram.org/bot' + token + '/setWebhook')
.field('url', '')
.end();
});
// Handle requests
var server = http.createServer(function(req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.end('Thanks!');
});
server.listen(80);
提前致谢。
当您的服务器启动时,您可以记录时间戳,然后用它来与传入的消息 date
值进行比较。如果日期 >= 您开始时的时间戳...消息可以被处理。
我不确定您是否可以通过某种方式告诉 Telegram 您只对新更新感兴趣,他们的重试机制是一项功能,因此不会错过消息......即使您的机器人处于离线状态。
我遇到了同样的问题,然后我尝试使用
重置默认 webhook
https://api.telegram.org/bot[mybotuniqueID]/setWebhook?url=
在那之后,我验证了当前的 getUpdates 查询是相同的旧更新,但我通过电报的机器人聊天发送了新请求
https://api.telegram.org/bot[mybotuniqueID]/getUpdates
当我再次设置 webhook 时,webhook 读取相同的旧更新。也许 getUpdates 方法没有刷新 JSON 内容。
注意:
就我而言,它工作正常,直到我决定从 botfather
更改/设置隐私机器人设置
在 webhook 模式下,Telegram 服务器每分钟发送一次更新,直到收到来自 webhook 程序的 OK 响应。
所以我推荐这些步骤:
- 检查您的 webhook 程序,您将其地址指定为
setWebhook
方法的 url 参数。在浏览器中调用它的地址。它不会产生要查看的输出,但会清除您的程序中可能没有错误。
- 在您的程序中包含一个生成“200 OK Status”header 输出的命令,以确保程序将此 header 发送到 Telegram 服务器。
最好的方法是使用 update_id
,这是一个特定的数字,随着每个新请求(即更新)而增加。如何实现?
首先,让我们从以下anonymous class(使用PHP7)开始:
$lastUpdateId = new class()
{
const FILE_PATH = "last-update-id.txt";
private $value = 1;
public function __construct()
{
$this->ensureFileExists();
$this->value = filesize(self::FILE_PATH) == 0
? 0 : (int)(file_get_contents(self::FILE_PATH));
}
public function set(int $lastUpdateId)
{
$this->ensureFileExists();
file_put_contents(self::FILE_PATH, $lastUpdateId);
$this->value = $lastUpdateId;
}
public function get(): int
{
return $this->value;
}
public function isNewRequest(int $updateId): bool
{
return $updateId > $this->value;
}
private function ensureFileExists()
{
if (!file_exists(self::FILE_PATH)) {
touch(self::FILE_PATH);
}
}
};
class 的作用很明确:通过普通文件处理最后一个 update_id
。
注意:class尽量短。它不提供错误检查。请改用您的自定义实现(例如,使用 SplFileObject
而不是 file_{get|put}_contents()
函数)。
现在,有两种获取更新的方法:长轮询 xor WebHooks(有关每种方法和所有 JSON 属性的更多详细信息,请查看 Telegram bot API)。在这两种情况下都应使用上述代码(或类似代码)。
注意:目前两种方法无法同时使用
长轮询方法(默认)
通过这种方式,您可以向 Telegram 机器人 API 发送 HTTPS 请求,并且您会在 JSON 格式的对象中获得更新作为响应。因此,可以完成以下工作以获得新的更新(API, why using offset):
$botToken = "<token>";
$updates = json_decode(file_get_contents("https://api.telegram.org/bot{$botToken}/getUpdates?offset={$lastUpdateId->get()}"), true);
// Split updates from each other in $updates
// It is considered that one sample update is stored in $update
// See the section below
parseUpdate($update);
WebHook 方法(首选)
要求您的服务器支持 HTTPS POST 方法,这是当前获取更新的最佳方式。
最初,您必须使用以下请求 (more details) 为您的机器人启用 WebHooks:
https://api.telegram.org/bot<token>/setWebhook?url=<file>
将 <token>
替换为您的机器人令牌,并将 <file>
替换为将接受新请求的文件地址。同样,它必须是 HTTPS。
好的,最后一步是在指定的位置创建文件 URL:
// The update is sent
$update = $_POST;
// See the section below
parseUpdate($update);
从现在开始,您的机器人的所有请求和更新都将直接发送到该文件。
实施parseUpdate()
其实施完全取决于您。但是,为了展示如何在实现中使用上面的 class,这是一个示例和简短的实现:
function parseUpdate($update)
{
// Validate $update, first
// Actually, you should have a validation class for it
// Here, we suppose that: $update["update_id"] !== null
if ($lastUpdateId->isNewRequest($update["update_id"])) {
$lastUpdateId->set($update["update_id"]);
// New request, go on
} else {
// Old request (or possible file error)
// You may throw exceptions here
}
}
尽情享受吧!
编辑:感谢@Amir 建议版本使这个答案更加完整和有用。
我正在编写一个 Telegram 机器人,我正在使用 official bot API。我有一个 webhook 服务器可以处理请求并为每个请求发送 200 OK
响应。
在服务器停止之前,webhook 已分离,因此 Telegram 不再发送更新。但是,每当我打开机器人并再次设置 webhook URL 时,Telegram 就会开始用旧更新淹没 webhook 服务器。
有什么方法可以避免这种情况,而无需重复请求 /getUpdates
直到我到达最后更新?
这是我的代码的高度简化版本:
var http = require('http'),
unirest = require('unirest'),
token = '***';
// Attach the webhook
unirest.post('https://api.telegram.org/bot' + token + '/setWebhook')
.field('url', 'https://example.com/api/update')
.end();
process.on('exit', function() {
// Detach the webhook
unirest.post('https://api.telegram.org/bot' + token + '/setWebhook')
.field('url', '')
.end();
});
// Handle requests
var server = http.createServer(function(req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.end('Thanks!');
});
server.listen(80);
提前致谢。
当您的服务器启动时,您可以记录时间戳,然后用它来与传入的消息 date
值进行比较。如果日期 >= 您开始时的时间戳...消息可以被处理。
我不确定您是否可以通过某种方式告诉 Telegram 您只对新更新感兴趣,他们的重试机制是一项功能,因此不会错过消息......即使您的机器人处于离线状态。
我遇到了同样的问题,然后我尝试使用
重置默认 webhookhttps://api.telegram.org/bot[mybotuniqueID]/setWebhook?url=
在那之后,我验证了当前的 getUpdates 查询是相同的旧更新,但我通过电报的机器人聊天发送了新请求
https://api.telegram.org/bot[mybotuniqueID]/getUpdates
当我再次设置 webhook 时,webhook 读取相同的旧更新。也许 getUpdates 方法没有刷新 JSON 内容。
注意: 就我而言,它工作正常,直到我决定从 botfather
更改/设置隐私机器人设置在 webhook 模式下,Telegram 服务器每分钟发送一次更新,直到收到来自 webhook 程序的 OK 响应。 所以我推荐这些步骤:
- 检查您的 webhook 程序,您将其地址指定为
setWebhook
方法的 url 参数。在浏览器中调用它的地址。它不会产生要查看的输出,但会清除您的程序中可能没有错误。 - 在您的程序中包含一个生成“200 OK Status”header 输出的命令,以确保程序将此 header 发送到 Telegram 服务器。
最好的方法是使用 update_id
,这是一个特定的数字,随着每个新请求(即更新)而增加。如何实现?
首先,让我们从以下anonymous class(使用PHP7)开始:
$lastUpdateId = new class()
{
const FILE_PATH = "last-update-id.txt";
private $value = 1;
public function __construct()
{
$this->ensureFileExists();
$this->value = filesize(self::FILE_PATH) == 0
? 0 : (int)(file_get_contents(self::FILE_PATH));
}
public function set(int $lastUpdateId)
{
$this->ensureFileExists();
file_put_contents(self::FILE_PATH, $lastUpdateId);
$this->value = $lastUpdateId;
}
public function get(): int
{
return $this->value;
}
public function isNewRequest(int $updateId): bool
{
return $updateId > $this->value;
}
private function ensureFileExists()
{
if (!file_exists(self::FILE_PATH)) {
touch(self::FILE_PATH);
}
}
};
class 的作用很明确:通过普通文件处理最后一个 update_id
。
注意:class尽量短。它不提供错误检查。请改用您的自定义实现(例如,使用 SplFileObject
而不是 file_{get|put}_contents()
函数)。
现在,有两种获取更新的方法:长轮询 xor WebHooks(有关每种方法和所有 JSON 属性的更多详细信息,请查看 Telegram bot API)。在这两种情况下都应使用上述代码(或类似代码)。
注意:目前两种方法无法同时使用
长轮询方法(默认)
通过这种方式,您可以向 Telegram 机器人 API 发送 HTTPS 请求,并且您会在 JSON 格式的对象中获得更新作为响应。因此,可以完成以下工作以获得新的更新(API, why using offset):
$botToken = "<token>";
$updates = json_decode(file_get_contents("https://api.telegram.org/bot{$botToken}/getUpdates?offset={$lastUpdateId->get()}"), true);
// Split updates from each other in $updates
// It is considered that one sample update is stored in $update
// See the section below
parseUpdate($update);
WebHook 方法(首选)
要求您的服务器支持 HTTPS POST 方法,这是当前获取更新的最佳方式。
最初,您必须使用以下请求 (more details) 为您的机器人启用 WebHooks:
https://api.telegram.org/bot<token>/setWebhook?url=<file>
将 <token>
替换为您的机器人令牌,并将 <file>
替换为将接受新请求的文件地址。同样,它必须是 HTTPS。
好的,最后一步是在指定的位置创建文件 URL:
// The update is sent
$update = $_POST;
// See the section below
parseUpdate($update);
从现在开始,您的机器人的所有请求和更新都将直接发送到该文件。
实施parseUpdate()
其实施完全取决于您。但是,为了展示如何在实现中使用上面的 class,这是一个示例和简短的实现:
function parseUpdate($update)
{
// Validate $update, first
// Actually, you should have a validation class for it
// Here, we suppose that: $update["update_id"] !== null
if ($lastUpdateId->isNewRequest($update["update_id"])) {
$lastUpdateId->set($update["update_id"]);
// New request, go on
} else {
// Old request (or possible file error)
// You may throw exceptions here
}
}
尽情享受吧!
编辑:感谢@Amir 建议版本使这个答案更加完整和有用。