Swiftgram / TDLibKit

Native Swift wrapper for Telegram's TDLib. Available on iOS, macOS, watchOS, tvOS and visionOS.
MIT License
106 stars 23 forks source link
ios library macos swift swift-async tdlib telegram tvos visionos watchos

TDLibKit

CI

TDLibKit is a native Swift wrapper for TDLib with support for iOS, macOS, watchOS, tvOS and visionOS.

Powered by pre-built multi-platform TDLibFramework implementation of TDLib and generated sources with tl2swift

Installation

Xcode

  1. Install Latest Xcode
  2. Add https://github.com/Swiftgram/TDLibKit as SPM dependency in Project > Swift Packages. This could take a while cause it downloads ~300mb zip file with binary from TDLibFramework dependency
  3. Add TDLibKit as your target dependency.
  4. Code!

Cocoapods

Integration requires similar to TDLibFramework Cocoapods & Flutter guide adaptation.

Usage

Library provides multiple API interfaces based on different approaches

Create client Manager

import TDLibKit
let manager = TDLibClientManager()

Make sure to create only one TDLibClientManager, since td_receive can be only called from a single thread.

Manager automatically polls for new updates, we will handle them per-client below.

Create Client & Handle updates

let client = manager.createClient(updateHandler: { /* data: Data, client: TDLibCLient */
    do {
        let update = try $1.decoder.decode(Update.self, from: $0)
        switch update {
            case .updateNewMessage(let newMsg):
                switch newMsg.message.content {
                    case .messageText(let text):
                        print("Text Message: \(text.text.text)")
                    default:
                        break
                }
            case .updateMessageEdited:
                break

            // ... etc

            default:
                print("Unhandled Update \(update)")
                break
        }
    } catch {
        print("Error in update handler \(error.localizedDescription)")
    }
})

Synchronious requests

Only for methods with "Can be called synchronously" in docs

let query = SetLogVerbosityLevel(newVerbosityLevel: 5)
do {
    let result = try client.execute(query: DTO(query))
    if let resultDict = result {
        print("Response: \(resultDict)")
    } else {
        print("Empty result")
    }
} catch {
    print("Error in SetLogVerbosityLevel request \(error.localizedDescription)")
}

Async requests

Async/Await

do {
    let chatHistory = try await client.getChatHistory(
        chatId: chatId,
        fromMessageId: 0,
        limit: 50,
        offset: 0,
        onlyLocal: false // Request remote messages from server
    )

    for message in chatHistory.messages {
    switch message.content {
        case .messageText(let text):
            print(text.text.text)

        case .messageAnimation:
            print("<Animation>")

        case .messagePhoto(let photo):
            print("<Photo>\n\(photo.caption.text)")

        case .messageSticker(let sticker):
            print(sticker.sticker.emoji)

        case .messageVideo(let video):
            print("<Video>\n\(video.caption.text)")

            // ...

        default:
            print("Unknown message content \(message.content)")
        }
    }
} catch {
    print("Error in getChatHistory \(error)")
}

Completion Handlers

try? client.getChatHistory(
    chatId: chatId,
    fromMessageId: 0,
    limit: 50,
    offset: 0,
    onlyLocal: false, // Request remote messages from server
    completion: { result in
        // Handle Errors
        if case .failure(let error) = result {
            print("Error in getChatHistory request \(error.localizedDescription)")
        } else if let messages = try? result.get().messages {
            // Handle messages
            for message in messages {
                switch message.content {
                case .messageText(let text):
                    print(text.text.text)

                case .messageAnimation:
                    print("<Animation>")

                case .messagePhoto(let photo):
                    print("<Photo>\n\(photo.caption.text)")

                case .messageSticker(let sticker):
                    print(sticker.sticker.emoji)

                case .messageVideo(let video):
                    print("<Video>\n\(video.caption.text)")

                    // ...

                default:
                    print("Unknown message content \(message.content)")
                }
            }
        }
    }
)

Logging

You can pass additional parameter with Logger type to log "send, receive, execute" and custom entries.

import TDLibKit
public final class StdOutLogger: TDLibLogger {

    let queue: DispatchQueue

    public init() {
        queue = DispatchQueue(label: "Logger", qos: .userInitiated)
    }

    public func log(_ message: String, type: LoggerMessageType?) {
        queue.async {
            var fisrtLine = "---------------------------"
            if let type = type {
                fisrtLine = ">> \(type.description): ---------------"
            }
            print("""
                \(fisrtLine)
                \(message)
                ---------------------------
                """)
        }
    }
}

let manager = TDLibClientManager(logger: StdOutLogger())

Close client

To ensure data integrity, you must properly close all the clients on app termination, either with

let client = manager.createClient()
try? client.close(completion: { _ in })

or use a blocking function

manager.closeClients()

Build

You can find more about build process in Github Actions file.

Credits

License

MIT