Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/rapierorg/telegram-bot-swift
Telegram Bot SDK for Swift (unofficial)
https://github.com/rapierorg/telegram-bot-swift
bot bots swift telegram telegram-bot-api telegram-bot-sdk telegram-bot-swift
Last synced: about 2 months ago
JSON representation
Telegram Bot SDK for Swift (unofficial)
- Host: GitHub
- URL: https://github.com/rapierorg/telegram-bot-swift
- Owner: rapierorg
- License: apache-2.0
- Created: 2015-07-16T20:07:51.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2022-01-02T04:44:36.000Z (almost 3 years ago)
- Last Synced: 2024-05-21T08:11:04.941Z (7 months ago)
- Topics: bot, bots, swift, telegram, telegram-bot-api, telegram-bot-sdk, telegram-bot-swift
- Language: Swift
- Homepage: https://github.com/rapierorg/telegram-bot-swift/wiki
- Size: 993 KB
- Stars: 373
- Watchers: 16
- Forks: 63
- Open Issues: 17
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.txt
- License: LICENSE.SwiftyJSON.txt
Awesome Lists containing this project
- fucking-awesome-swift - Telegram Bot SDK - Unofficial SDK. (Libs / Bots)
- awesome-swift - Telegram Bot SDK - Unofficial SDK. (Libs / Bots)
- awesome-swift - Telegram Bot SDK - Telegram Bot SDK for Swift (unofficial) ` 📝 a year ago ` (Bots [🔝](#readme))
- awesome-swift - Telegram Bot SDK - Unofficial SDK. (Libs / Bots)
- awesome-telegram - telegram-bot-swift
- awesome-telegram - telegram-bot-swift
README
Chat
• Changelog
• Prerequisites
• Getting started
• Creating a new bot
• Generating Xcode project
• API overview
• Debugging notes
• Examples
• Documentation
• Support
• License# SDK for creating Telegram Bots in Swift.
*Sample projects:*
Shopping list bot.
Word reverse bot.
Trivial bot:
```swift
import TelegramBotSDKlet 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 chat
Join our chat in Telegram: [swiftsdkchat](https://telegram.me/swiftsdkchat).
## What's new
[Release notes](https://github.com/zmeyc/telegram-bot-swift/blob/master/CHANGELOG.md) contain the significant changes in each release with migration notes.
## Prerequisites
On OS X, use the latest Xcode 9 release.
On Linux, install `Swift 4.2` or newer and `libcurl4-openssl-dev` package. Note that `shopster-bot` example won't build on Linux because GRDB doesn't support Linux yet, but otherwise the library should be functional.
## Getting started
Please get familiar with the documentation on Telegram website:
* [Bots: An introduction for developers](https://core.telegram.org/bots)
* [Telegram Bot API](https://core.telegram.org/bots/api)
## Creating a new bot
In Telegram, add `BotFather`. Send him these commands:
```
/newbot
BotName
username_of_my_bot
```BotFather will return a token.
Create a project for your bot:
```
mkdir hello-bot
cd hello-bot
swift package init --type executable```
Create `Package.swift`:
```swift
// swift-tools-version:5.1
import PackageDescriptionlet 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"]),
]
)
```Create `Sources/main.swift`:
```swift
import Foundation
import TelegramBotSDKlet 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))")
```> Do not commit your token to git!
`readToken` reads token from environment variable or from a file. So, either create an environment variable:
```
export HELLO_BOT_TOKEN='token'
```Or save the token to a file and add the file to .gitignore:
```
echo token > HELLO_BOT_TOKEN
```Build your bot:
```
swift build
```And run it:
```
./.build/x86_64-apple-macosx10.10/debug/hello-bot
```More details are available on Wiki: [New Bot](https://github.com/zmeyc/telegram-bot-swift/wiki/New-Bot).
## Generating Xcode project
It's easy:
```
swift package generate-xcodeproj
```Open generated `hello-bot.xcodeproj` and switch the active scheme to the bottom one:
Don't forget to add your token to environment variables in Xcode (Scheme settings -> Run).
Press CMD-R to start the bot.
## API overview
### Type and request names
SDK type and request names closely mirror [original Telegram ones](https://core.telegram.org/bots/api).
Swift types and enums were added where appropriate:
```swift
if entity.type == .botCommand { ... }
```In most cases raw methods accepting strings are also available. They can be used as fallbacks if required enum case is not added yet:
```swift
if entity.typeString == "bot_command" { ... }
```To allow accessing fields which are still missing in SDK, every data type has `json` member with original json structure:
```swift
if entity.json["type"].stringValue == "bot_command" { ... }
```All types conform to `JsonConvertible` protocol and can be created from json or serialized back to json. Use `debugDescription` method for human-readable json or `description` for json which can be sent to server.
### Requests
**Sync and Async**
Request names closely mirror Telegram ones, but have two versions: `synchronous` and `asynchronous` with method suffixes `Sync` and `Async` correspondingly.
* Synchronous methods block until the operation is completed.
```swift
let fromId: ChatId = .chat(12345678) // your user id
bot.sendMessageSync(fromId, "Hello!") // blocks until the message is sent
bot.sendMessageSync(fromId, "Bye.")
```These methods return a server response or `nil` in case of error. If `nil` is returned, details can be obtained by querying `bot.lastError`.
```swift
guard let sentMessage = bot.sendMessageSync(fromId, "Hello") else {
fatalError("Unable to send message: \(bot.lastError.unwrapOptional)")
}
```Do not use synchronous methods in real apps because they're slow. Use them when debugging or for experimenting in `REPL`. More details: [Using Swift REPL for calling API methods](https://github.com/zmeyc/telegram-bot-swift/wiki/Using-Swift-REPL-for-calling-API-methods)
* Asynchronous methods accept an optional completion handler which will be called when operation is completed.
Completion handler is called on main thread by default.
```swift
bot.sendMessageAsync(fromId, "Hello!") { result, error in
// message sent!
bot.sendMessageAsync(fromId, "Bye.")
}
// execution continues immediately
```In completion handler `result` contains the server response or `nil` in case of error. Details can be obtained by querying `error`.
For simplicity, it's possible to synchronously process messages, but respond asynchronously to avoid blocking the processing of the next message. So, a typical bot's main loop can look like this:
```swift
while let update = bot.nextUpdateSync() {
// process the message and call Async methods
}
```**Request parameters**
Parameter names should be specified explicitly in most cases:
```swift
bot.sendLocationAsync(chat_id: chatId, latitude: 50.4501, longitude: 30.5234)
```Exception to this are `sendMessageSync/Async` and `respondSync/Async` functions which are used very often. Parameter names can be omitted in them:
```swift
bot.sendMessageAsync(chatId: chatId, text: "Text")
bot.sendMessageAsync(chatId, "Text") // will also work
````Optional` parameters can also be passed:
```swift
let markup = ForceReply()
bot.sendMessageAsync(chatId: chatId, text: "Force reply",
reply_markup: markup, disable_notification: true)
```If you ever encounter a situation when parameter hasn't been added to method signature yet, you can pass a dictionary with any parameters at the end of parameter list:
```swift
let markup = ForceReply()
bot.sendMessageAsync(chatId: chatId, text: "Force reply",
["reply_markup": markup, "disable_notification": true])
```It's also possible to set default parameter values for a request:
```swift
bot.defaultParameters["sendMessage"] = ["disable_notification": true]
```In dictionaries `nil` values will be treated as `no value` and won't be sent to Telegram server.
**Available requests**
Check `TelegramBot/Requests` subdirectory for a list of available requests.
If you find a missing request, please create a ticket and it will be added. Until then, an arbitrary unsupported endpoint can be called like this:
```swift
let user: User? = requestSync("sendMessage", ["chat_id": chatId, "text": text])
```Or async version:
```swift
requestAsync("sendMessage", ["chat_id": chatId, "text": text]) { (result: User?, error: DataTaskError?) -> () in
...
}
```These methods automatically deserialize the json response.
Explicitly specifying result type is important. Result type should conform to `JsonConvertible` protocol. `Bool` and `Int` already conform to `JsonConvertible`.
JSON class itself also conforms to `JsonConvertible`, so you can request a raw json if needed:
```swift
let user: JSON? = requestSync("sendMessage", ["chat_id": chatId, "text": text])
```### Routing
Router maps text commands and other events to their handler functions and helps parsing command arguments.
```swift
let router = Router(bot)
router["command1"] = handler1
router["command2"] = handler2
router[.event] = handler3
...
router.process(update: update)
```Multiple commands can be specified in a single rule:
```swift
router["Full Command Name", "command"] = handler
```Multiword commands are also supported:
```swift
router["list add"] = onListAdd
router["list remove"] = onListRemove
```Routers can be chained. This helps creating a context-sensitive routers with fallback to a global router.
```swift
router1.unmatched = router2.handler
```**Handlers**
Handlers take `Context` argument and return `Bool`.
* If handler returns `true`, command matching stops.
* If handler returns `false`, other paths will be matched.So, in handler check preconditions and return false if they aren't satisfied:
```swift
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
}
```Handler functions can be marked as `throws` and throw exceptions. Router won't process them and will simply pass the exceptions to caller.
`Context` is a request context, it contains:
* `bot` - a reference to the bot.
* `update` - current `Update` structure.
* `message` - convenience method for accessing `update.message`. If `update.message` is nil, fallbacks to `update.edited_message`, then to `update.callback_query?.message`.
* `command` - command without slash.
* `slash` - true, if command was prefixed with a slash. Useful if you want to skip commands not starting with slash in group chats.
* `args` - command arguments scanner.
* `properties` - context sensitive properties. Pass them to `process` method:```swift
var properties = [String: AnyObject]()
properties["myField"] = myValue
try router.process(update: update, properties: properties)
```And use them in handlers:
```swift
func myHandler(context: Context) -> Bool {
let myValue = context.properties["myField"] as? MyValueType
// ...
}
```Or make a `Context` category for easier access to your properties, for example:
```swift
extension Context {
var session: Session { return properties["session"] as! Session }
}
````Context` also contains a few helper methods and variables:
* `privateChat` - true, if this is a private chat with bot, false for all group chat types.
* `chatId` - shortcut for message?.chat.id. If message is nil, tries to retrieve chatId from other `Update` fields.
* `fromId` - shortcut for message?.from?.id. If message is nil, tries to retrieve fromId from other `Update` fields.
* `respondAsync`, `respondSync` - works as `sendMessage(chatId, ...)`
* `respondPrivatelyAsync/Sync("text", groupText: "text")` - respond to user privately, sending a short message to the group if this was a group chat. For example:```swift
context.respondPrivatelyAsync("Command list: ...",
groupText: "Please find a list of commands in a private message.")
```* `reportErrorAsync/Sync(text: "User text", errorDescription: "Detailed error description for administrator")` - sends a short message to user and prints detailed error description to a console. `text` parameter can be omitted, in which case user will receive a generic error message.
**Text commands**
Router can match text commands:
```swift
router["start"] = onStart
```Command name is processed differently in private and group chats:
* In private chats slash is optional. `start` matches `/start` as well as `start`.
* It group chats 'start' only matches `/start`.This can be overridden. The following line will require slash even in private chats:
```swift
router["start", .slashRequired] = onStart
```Router is case-insensitive by default. To make it case-sensitive, pass `.caseSensitive` option:
```swift
router["command", .caseSensitive] = handler
```Multiple options can be passed:
```swift
router["command", [.slashRequired, .caseSensitive]] = handler
```In Telegram group chats, user can append bot name to a command, for example: `/greet@hello_bot`. Router takes care of removing the `@hello_bot` part from command name automatically.
**Text commands with arguments**
Words can be captured and then processed by using `scanWord` method.
```swift
router["two_words"] = { context in
let word1 = context.args.scanWord()
let word2 = context.args.scanWord()
}
```Array of words can be captured using `scanWords`:
```swift
router["words"] = { context in
let words = context.args.scanWords() // returns [String] array
}
```Numbers can be captured using `scanInt`, `scanInt64` and `scanDouble`. `restOfString` captures the remainder as a single string.
```swift
router["command"] = { context in
let value1 = context.args.scanInt()
let value2 = context.args.scanDouble()
let text = context.args.scanRestOfString()
}
```It's also possible to directly access `NSScanner` used for scanning arguments: `context.args.scanner`.
Handler is expected to read all the arguments, otherwise user will see a warning: _Part of your input was ignored: text_
So, for example, if there's a command `swap` which expects two arguments but user types: `/swap aaa bbb ccc`, he will see:
```
bbb aaa
Part of your input was ignored: ccc
```A possible way to avoid the warning is to skip unneeded arguments by calling `context.args.skipRestOfString()`.
Also, the warning can be overridden:
```swift
router.partialMatch = { context in
context.respondAsync("Part of your input was ignored: \(context.args.scanRestOfString())")
return true
}
```**Other events**
Router can handle other event types as well. For example, when new user joins the chat, `.new_chat_member` path will be triggered:
```swift
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
}
```Check `TelegramBot/Router/ContentType.swift` file for a complete list of events supported by Router.
**Handling unmatched paths**
If no paths were matched, router will call it's `unmatched` handler, which will print "Command not found" by default.
This can be overridden by setting an explicit handler:```swift
router.unmatched = { context in
// Do something else with context.args
return true
}
```### Debugging notes
In debugger you may want to dump the contents of a json structure, but `debugDescription` loses it's formatting.
`prettyPrint` helper function allows printing any `JsonConvertible` with indentation:
```swift
let user: User
user.prettyPrint()bot.sendMessageSync(fromId, "Hello!")?.prettyPrint()
```## Examples
There are 3 example projects available:
* `Examples/hello-bot` - a trivial bot which responds to `/greet` command and greets users who join the chat.
* `Examples/word-reverse-bot` - demonstrates how to handle start and stop requests, keep session state and parse command arguments. Behaves differently in private and group chats. Uses a router and a controller.
* `Examples/shopster-bot` - maintains a shopping list using sqlite3 database. Allows creating shared shopping lists in group chats. [GRDB library](https://github.com/groue/GRDB.swift) is used for working with database.
Details on compiling and running the bots are available on Wiki: [Building and running the example projects](https://github.com/zmeyc/telegram-bot-swift/wiki).
## Documentation
Additional documentation is available on [Telegram Bot Swift SDK Wiki](https://github.com/zmeyc/telegram-bot-swift/wiki).
Check `Examples/` for sample bot projects.
This SDK is a work in progress, expect the API to change very often.
## Need help?
Please [submit an issue](https://github.com/zmeyc/telegram-bot-swift/issues) on Github.
If you miss a specific feature, please create an issue and it will be prioritized. Pull Requests are also welcome.
Talk with other developers in our Telegram chat: [swiftsdkchat](https://telegram.me/swiftsdkchat).
Happy coding!
## License
Apache License Version 2.0 with Runtime Library Exception. Please see LICENSE.txt for more information.