Swift Chat Platform License Build Status

聊天更新日志前提条件开始上手创建新 Bot生成 Xcode 项目API 概览调试说明示例文档支持许可证

用于在 Swift 中创建 Telegram Bot 的 SDK。

示例项目

购物清单 Bot。

单词反转 Bot。

琐碎 Bot

import TelegramBotSDK

let bot = TelegramBot(token: "my token")
let router = Router(bot: bot)

router["greet"] = { context in
    guard let from = context.message?.from else { return false }
    context.respondAsync("Hello, \(from.firstName)!")
    return true
}

router[.newChatMembers] = { context in
    guard let users = context.message?.newChatMembers else { return false }
    for user in users {
        guard user.id != bot.user.id else { continue }
        context.respondAsync("Welcome, \(user.firstName)!")
    }
    return true
}

while let update = bot.nextUpdateSync() {
	try router.process(update: update)
}

fatalError("Server stopped due to error: \(bot.lastError)")

Telegram 聊天

加入我们在 Telegram 上的聊天:swiftsdkchat

最新动态

发行说明包含每个版本中的重大更改以及迁移说明。

前提条件

在 OS X 上,使用最新的 Xcode 9 版本。

在 Linux 上,安装 Swift 4.2 或更高版本以及 libcurl4-openssl-dev 包。请注意,shopster-bot 示例无法在 Linux 上构建,因为 GRDB 尚不支持 Linux,但除此之外,该库应该是可用的。

开始上手

请先熟悉 Telegram 网站上的文档

创建新 Bot

在 Telegram 中,添加 BotFather。向他发送以下命令

/newbot
BotName
username_of_my_bot

BotFather 将返回一个令牌。

为您的 Bot 创建一个项目

mkdir hello-bot
cd hello-bot
swift package init --type executable

创建 Package.swift

// swift-tools-version:5.1
import PackageDescription

let package = Package(
    name: "hello-bot",
    products: [
        // Products define the executables and libraries produced by a package, and make them visible to other packages.
        .executable(
            name: "hello-bot",
            targets: ["hello-bot"]
        ),
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
        .package(name: "TelegramBotSDK", url: "https://github.com/zmeyc/telegram-bot-swift.git", from: "2.0.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name: "hello-bot",
            dependencies: ["TelegramBotSDK"]),
    ]
)

创建 Sources/main.swift

import Foundation
import TelegramBotSDK

let token = readToken(from: "HELLO_BOT_TOKEN")
let bot = TelegramBot(token: token)

while let update = bot.nextUpdateSync() {
    if let message = update.message, let from = message.from, let text = message.text {
        bot.sendMessageAsync(chatId: .chat(from.id),
                             text: "Hi \(from.firstName)! You said: \(text).\n")
    }
}

fatalError("Server stopped due to error: \(String(describing: bot.lastError))")

不要将您的令牌提交到 git!

readToken 从环境变量或文件中读取令牌。因此,要么创建一个环境变量

export HELLO_BOT_TOKEN='token'

要么将令牌保存到一个文件并将其添加到 .gitignore

echo token > HELLO_BOT_TOKEN

构建您的 Bot

swift build

并运行它

./.build/x86_64-apple-macosx10.10/debug/hello-bot

更多详情请访问 Wiki:New Bot

生成 Xcode 项目

这很简单

swift package generate-xcodeproj

打开生成的 hello-bot.xcodeproj 并将活动 scheme 切换到底部的一个

不要忘记将您的令牌添加到 Xcode 中的环境变量(Scheme settings -> Run)。

按下 CMD-R 启动 Bot。

API 概览

类型和请求名称

SDK 类型和请求名称与原始 Telegram 的名称非常相似。

在适当的地方添加了 Swift 类型和枚举

if entity.type == .botCommand { ... }

在大多数情况下,也提供了接受字符串的原始方法。如果所需的枚举 case 尚未添加,它们可以用作后备方案

if entity.typeString == "bot_command" { ... }

为了允许访问 SDK 中仍然缺失的字段,每个数据类型都有一个包含原始 json 结构的 json 成员

if entity.json["type"].stringValue == "bot_command" { ... }

所有类型都符合 JsonConvertible 协议,并且可以从 json 创建或序列化回 json。使用 debugDescription 方法获取人类可读的 json,或使用 description 获取可以发送到服务器的 json。

请求

同步和异步

请求名称与 Telegram 的名称非常相似,但有两个版本:synchronousasynchronous,方法后缀分别为 SyncAsync

let fromId: ChatId = .chat(12345678) // your user id
bot.sendMessageSync(fromId, "Hello!") // blocks until the message is sent
bot.sendMessageSync(fromId, "Bye.")

这些方法返回服务器响应,如果发生错误,则返回 nil。如果返回 nil,可以通过查询 bot.lastError 获取详细信息。

guard let sentMessage = bot.sendMessageSync(fromId, "Hello") else {
    fatalError("Unable to send message: \(bot.lastError.unwrapOptional)")
}

不要在实际应用中使用同步方法,因为它们很慢。在调试或在 REPL 中进行实验时使用它们。更多详情:使用 Swift REPL 调用 API 方法

默认情况下,完成处理程序在主线程上调用。

bot.sendMessageAsync(fromId, "Hello!") { result, error in
  // message sent!
  bot.sendMessageAsync(fromId, "Bye.")
}
// execution continues immediately

在完成处理程序中,result 包含服务器响应,如果发生错误,则为 nil。可以通过查询 error 获取详细信息。

为了简单起见,可以同步处理消息,但异步响应以避免阻塞下一个消息的处理。因此,一个典型的 Bot 主循环可能如下所示

while let update = bot.nextUpdateSync() {
  // process the message and call Async methods
}

请求参数

在大多数情况下,应显式指定参数名称

bot.sendLocationAsync(chat_id: chatId, latitude: 50.4501, longitude: 30.5234)

sendMessageSync/AsyncrespondSync/Async 函数是例外,它们经常被使用。在它们中可以省略参数名称

bot.sendMessageAsync(chatId: chatId, text: "Text")
bot.sendMessageAsync(chatId, "Text") // will also work

也可以传递 Optional 参数

let markup = ForceReply()
bot.sendMessageAsync(chatId: chatId, text: "Force reply",
    reply_markup: markup, disable_notification: true)

如果您遇到某种情况,其中参数尚未添加到方法签名中,您可以在参数列表的末尾传递一个包含任何参数的字典

let markup = ForceReply()
bot.sendMessageAsync(chatId: chatId, text: "Force reply",
    ["reply_markup": markup, "disable_notification": true])

也可以为请求设置默认参数值

bot.defaultParameters["sendMessage"] = ["disable_notification": true]

在字典中,nil 值将被视为 无值,并且不会发送到 Telegram 服务器。

可用请求

查看 TelegramBot/Requests 子目录以获取可用请求的列表。

如果您发现缺少某个请求,请创建一个 issue,我们会将其添加。在此之前,可以像这样调用任意不受支持的端点

let user: User? = requestSync("sendMessage", ["chat_id": chatId, "text": text])

或异步版本

requestAsync("sendMessage", ["chat_id": chatId, "text": text]) { (result: User?, error: DataTaskError?) -> () in
    ...
}

这些方法会自动反序列化 json 响应。

显式指定结果类型非常重要。结果类型应符合 JsonConvertible 协议。BoolInt 已经符合 JsonConvertible

JSON 类本身也符合 JsonConvertible,因此如果需要,您可以请求原始 json

let user: JSON? = requestSync("sendMessage", ["chat_id": chatId, "text": text])

路由

Router 将文本命令和其他事件映射到其处理函数,并帮助解析命令参数。

let router = Router(bot)
router["command1"] = handler1
router["command2"] = handler2
router[.event] = handler3
...
router.process(update: update)

可以在单个规则中指定多个命令

router["Full Command Name", "command"] = handler

也支持多词命令

router["list add"] = onListAdd
router["list remove"] = onListRemove

路由器可以链式连接。这有助于创建上下文相关的路由器,并回退到全局路由器。

router1.unmatched = router2.handler

处理程序

处理程序接受 Context 参数并返回 Bool

因此,在处理程序中检查先决条件,如果不满足,则返回 false

router["reboot"] = { context in
    guard let fromId = context.fromId where isAdmin(fromId) else { return false }

    context.respondAsync("I will now reboot the PC.") { _ in
        reboot()
    }

    return true
}

处理函数可以标记为 throws 并抛出异常。路由器不会处理它们,只会将异常传递给调用者。

Context 是请求上下文,它包含

var properties = [String: AnyObject]()
properties["myField"] = myValue
try router.process(update: update, properties: properties)

并在处理程序中使用它们

func myHandler(context: Context) -> Bool {
    let myValue = context.properties["myField"] as? MyValueType
    // ...
}

或者创建一个 Context 类别以便更轻松地访问您的属性,例如

extension Context {
    var session: Session { return properties["session"] as! Session }
}

Context 还包含一些辅助方法和变量

context.respondPrivatelyAsync("Command list: ...",
    groupText: "Please find a list of commands in a private message.")

文本命令

路由器可以匹配文本命令

router["start"] = onStart

在私聊和群聊中,命令名称的处理方式有所不同

这可以被覆盖。以下行即使在私聊中也需要斜杠

router["start", .slashRequired] = onStart

默认情况下,路由器不区分大小写。要使其区分大小写,请传递 .caseSensitive 选项

router["command", .caseSensitive] = handler

可以传递多个选项

router["command", [.slashRequired, .caseSensitive]] = handler

在 Telegram 群聊中,用户可以在命令后附加 Bot 名称,例如:/greet@hello_bot。路由器会自动处理从命令名称中删除 @hello_bot 部分。

带参数的文本命令

可以使用 scanWord 方法捕获单词,然后进行处理。

router["two_words"] = { context in
    let word1 = context.args.scanWord()
    let word2 = context.args.scanWord()
}

可以使用 scanWords 捕获单词数组

router["words"] = { context in
    let words = context.args.scanWords() // returns [String] array
}

可以使用 scanIntscanInt64scanDouble 捕获数字。restOfString 将剩余部分捕获为单个字符串。

router["command"] = { context in
    let value1 = context.args.scanInt()
    let value2 = context.args.scanDouble()
    let text = context.args.scanRestOfString()
}

也可以直接访问用于扫描参数的 NSScannercontext.args.scanner

处理程序应读取所有参数,否则用户将看到警告:您的部分输入被忽略:文本

因此,例如,如果有一个命令 swap 期望两个参数,但用户输入:/swap aaa bbb ccc,他将看到

bbb aaa
Part of your input was ignored: ccc

避免警告的一种可能方法是通过调用 context.args.skipRestOfString() 跳过不需要的参数。

此外,警告可以被覆盖

router.partialMatch = { context in
    context.respondAsync("Part of your input was ignored: \(context.args.scanRestOfString())")
    return true
}

其他事件

路由器也可以处理其他事件类型。例如,当新用户加入聊天时,将触发 .new_chat_member 路径

router[.new_chat_member] = { context in
    guard let users = context.message?.newChatMembers else { return false }
    for user in users {
        guard user.id != bot.user.id else { return false }
        context.respondAsync("Welcome, \(user.firstName)!")
    }
    return true
}

查看 TelegramBot/Router/ContentType.swift 文件以获取路由器支持的完整事件列表。

处理未匹配的路径

如果没有匹配的路径,路由器将调用其 unmatched 处理程序,默认情况下,该处理程序将打印“Command not found”。这可以通过设置显式处理程序来覆盖

router.unmatched = { context in
    // Do something else with context.args
    return true
}

调试说明

在调试器中,您可能希望转储 json 结构的内容,但 debugDescription 会丢失其格式。

prettyPrint 辅助函数允许使用缩进打印任何 JsonConvertible

let user: User
user.prettyPrint()

bot.sendMessageSync(fromId, "Hello!")?.prettyPrint()

示例

有 3 个示例项目可用

有关编译和运行 Bot 的详细信息,请访问 Wiki:构建和运行示例项目

文档

其他文档可在Telegram Bot Swift SDK Wiki上找到。

查看 Examples/ 以获取示例 Bot 项目。

此 SDK 仍在开发中,预计 API 会经常更改。

需要帮助?

请在 Github 上提交 issue

如果您缺少特定功能,请创建一个 issue,我们将优先处理。也欢迎 Pull Request。

在我们的 Telegram 聊天中与其他开发者交流:swiftsdkchat

祝您编程愉快!

许可证

Apache License Version 2.0 with Runtime Library Exception。有关更多信息,请参阅 LICENSE.txt。