#telegram-bot #user #message #telegram-api #serialization

botapi

一个稍微有点能力的自动生成的Telegram机器人API封装器

42个版本

新版本 0.0.46 2024年8月16日
0.0.44 2024年7月14日
0.0.36 2024年1月6日
0.0.35 2023年12月30日
0.0.3 2022年11月8日

#205Web编程

Download history 163/week @ 2024-05-11 12/week @ 2024-05-18 153/week @ 2024-05-25 182/week @ 2024-06-01 17/week @ 2024-06-08 137/week @ 2024-06-15 24/week @ 2024-06-22 280/week @ 2024-06-29 22/week @ 2024-07-06 141/week @ 2024-07-13 3/week @ 2024-07-20 102/week @ 2024-07-27 12/week @ 2024-08-03 100/week @ 2024-08-10

每月219 次下载

MIT 许可证

5.5MB
128K SLoC

botapi-rs

自动生成的Telegram机器人API封装器。生成基于serde的全异步Telegram机器人API封装器。

这个库旨在超越仅仅生成HTTP封装器,更干净地将Telegram类型和方法映射到Rust设计模式,同时保持低开销并避免不必要的克隆或分配。

方法尽可能利用泛型参数、切片和原始类型,并且所有API类型都提供易于使用的构建器模式接口。

特性

  • 自动与最新版本的Telegram API保持同步
  • 最小化冗余和样板代码
  • 全异步支持,与tokio兼容
  • 支持长轮询和Webhooks
  • 支持大文件上传和本地API服务器
  • 自动生成文档
  • 自动处理Telegram的速率限制

将botapi添加到您的项目中

您可以通过cargo-edit添加

cargo add botapi

或编辑您的Cargo.toml文件以包含

[dependencies]
botapi = { version = "0.0.40", features = [] }

有一个可用的功能: rhai,该功能为所有Telegram类型启用rhai脚本支持(更多信息请参见下文)。普通用户不需要此功能。

选择示例

一些您可以使用此库执行的操作示例,这不是完整的文档,只是作为介绍。

创建新的客户端

机器人API客户端允许低成本的Clone,以便移动到其他作用域。客户端支持可选的"auto-wait"功能,允许在处理带有retry_after参数的速率限制错误时进行延迟自动重试。如果您有自己的429错误处理方法,可以禁用此功能。

let client = BotBuilder::new("12345:mytoken")
    .unwrap()
    .auto_wait(true) // Automatically retry after 429 errors
    .build();

let handle = client.clone();

async move {
    // async context, cloned client is moved
    let me = handle.get_me().await.unwrap();
}

简单回复机器人

回复所有消息为 不要哭!!

// Setup long polling with default updates
let longpoller = LongPoller::new(&client, None);
let mut upd = longpoller.get_updates().await;
while let Some(update) = upd.next().await {
    if let UpdateExt::Message(message) = update {
        client.build_send_message(message.chat.id, "Not cry!")
        .reply_parameters(
            &ReplyParametersBuilder::new(message.message_id).build(),
        )
        .build()
        .await?;
    }
}

反高级机器人

对聊天成员加入和禁止高级用户做出反应

let updates = vec![
        "update_id",
        "message",
        "edited_message",
        "channel_post",
        "edited_channel_post",
        "inline_query",
        "chosen_inline_result",
        "callback_query",
        "shipping_query",
        "pre_checkout_query",
        "poll",
        "poll_answer",
        "my_chat_member",
        "chat_member",
        "chat_join_request",
    ]
    .into_iter()
    .map(|v| v.to_owned())
    .collect();

// Setup long polling with all updates including joins
let longpoller = LongPoller::new(&client, Some(updates));
let mut upd = longpoller.get_updates().await;
while let Some(update) = upd.next().await {
    if let UpdateExt::ChatMember(member) = update {
        TG.client()
            .build_ban_chat_member(member.chat.id, member.from.id)
            .build()
            .await?;

        client.build_send_message(member.chat.id, "Begone, blue star!")
            .build()
            .await?;
    }
}

设置Webhooks

注意: 这需要支持TLS的反向代理。此库不支持TLS。

Webhook::new(
    &client,
    BotUrl::Host("https://bothook.tsinghua.edu.cn"),
    false,
    ([0, 0, 0, 0], 8080).into(),
    None,
)
.get_updates()
.await?
.for_each_concurrent(
    None,
    |update| async move { log::info!("update {:?}", update); },
)
.await

BoxWrapper<Unbox<T>>是什么意思?

警告:以下是一些技术内容。TL;DR:在大多数情况下,包括所有botapi函数,您可以像处理BoxWrapper<Box<T>>和BoxWrapper<Unbox<T>>一样处理T。如果您需要从BoxWrapper获取T,请使用.into()或传递引用。这不是错误,这是必需的。请相信我!

技术解释

由于Rust不允许递归类型(包含自身实例的类型),除非有像Rc或Box这样的间接形式,因此Telegram的JSON类型与Rust自身的类型系统存在固有的不兼容性。

由于这个库是自动生成的,我们需要使用Box打破一些循环,以便拥有有效的类型。我决定最小化Box化字段是一个好主意,因为它可以使复制和分配Telegram类型更高效,但实际上这样做相当于找到最小反馈弧集,这是一个NP完全问题!哎呀。

目前存在的一些针对此问题的近似算法,其运行时间在多项式时间内,但会导致类型在添加新的Telegram类型时被Box化,这会导致每次Telegram版本发布时API更改被破坏。我提出的解决方案是将任何JSON类型(即可能导致循环的类型)泛化到Box或Unbox(一个不是盒子的包装器)。这允许API保持稳定。Box和Unbox都实现了Deref,T始终实现了From>> / BoxWrapper>>,因此这种权宜之计的额外开销应该很小,从用户的角度来看。

关于.noskip()方法的处理是什么情况?

API类型T上的T.noskip()方法从T构建了一个NoSkipT类型。这个类型与其原始类型serde兼容(这意味着你可以序列化一个T,然后将其反序列化为NoSkipT),但它不使用任何字段的#[serde)]["Option::is_none""属性。

你需要这样做是因为如果你使用像rmp_serde这样的序列化器,它将结构体序列化为数组。当serde使用数组时跳过字段会导致反序列化时出错。这是一个边缘情况,但有其用处。

Rhai绑定

Rhai是一种简单的嵌入式脚本语言,旨在允许与Rust类型进行一些互操作性。由于rhai需要编写一些样板代码才能与某些Rust类型(如枚举)交互,因此这个库也可以选择性地自动生成所有Telegram类型的rhai样板代码。是的,这需要与botapi库本身集成,因为需要自动生成。

由于这会增加相当大的二进制大小,因此默认情况下是禁用的,受rhai功能的控制。启用rhai支持不会以任何方式影响性能,除了二进制大小。

启用rhai功能

Cargo.toml中启用

[dependencies]
botapi = { version = "0.0.40", features = [ "rhai" ] }

初始化rhai引擎

为了在rhai环境中与botapi类型交互,必须将每个所需类型注册到rhai引擎中。这可以一次处理所有类型,也可以一次处理一个类型。

let mut engine = Engine::new();
// Register all types
botapi::gen_types::rhai_helpers::setup_all_rhai(&mut engine);

// Register just one type (recursively registers all fields)
Message::setup_rhai(&mut engine);

rhai绑定的示例用法

botapi类型中的大多数字段都可以直接供rhai脚本访问。主要的例外是枚举,它们通过类似以下API公开

botapi::gen_types::Message类型检查枚举字段示例中,匿名函数的m参数假定是botapi::gen_types::Message

|m| if m.from.value.is_premium.value {
  "no premium users allowed"
}  else {
  "nice"
}

检查botapi::gen_types::Message是否具有文本的示例

// check if value is a unit
|m| m.text.value == ()

// check if enum_type is None
|m m.text.enum_type == "None"

构建文档

文档将与库本身自动生成。文档可在 https://docs.rs/botapi 上实时查看,或者您可以通过运行以下命令在离线状态下查看文档:

cargo build
cargo doc --open

https://github.com/fmeef/dijkstra_bot:一个使用此库的模块化Telegram机器人框架。

https://github.com/PaulSonOfLars/telegram-bot-api-spec:本项目的灵感来源和API规范的来源。(感谢Paul!)

依赖项

~11-24MB
~365K SLoC