Skip to content

Commit

Permalink
Add first send api methods and PromiseKit extension
Browse files Browse the repository at this point in the history
  • Loading branch information
Koray Koska committed Jan 29, 2019
1 parent 5326db7 commit 21bfd58
Show file tree
Hide file tree
Showing 9 changed files with 460 additions and 4 deletions.
7 changes: 7 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ let package = Package(
.library(
name: "TelegramBotVapor",
targets: ["TelegramBotVapor"]),
.library(
name: "TelegramBotPromiseKit",
targets: ["TelegramBotPromiseKit"]),
],
dependencies: [
// Test dependencies
Expand All @@ -18,10 +21,14 @@ let package = Package(

// Vapor for vapor related requests, optional.
.package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),

// PromiseKit dependency, optional.
.package(url: "https://github.com/mxcl/PromiseKit.git", from: "6.0.0"),
],
targets: [
.target(name: "TelegramBot", dependencies: []),
.target(name: "TelegramBotVapor", dependencies: ["TelegramBot", "Vapor"]),
.target(name: "TelegramBotPromiseKit", dependencies: ["TelegramBot", "PromiseKit"]),
.testTarget(name: "TelegramBotTests", dependencies: ["TelegramBot", "Quick", "Nimble"])
]
)
38 changes: 38 additions & 0 deletions Sources/TelegramBot/Json/SendApi/TelegramSendForwardMessage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// TelegramSendForwardMessage.swift
// TelegramBot
//
// Created by Koray Koska on 29.01.19.
//

import Foundation

public final class TelegramSendForwardMessage: Codable {

// MARK: - Primitive types

/// Unique identifier for the target chat or
/// username of the target channel (in the
/// format @channelusername)
public var chatId: TelegramSendChatIdentifier

/// Unique identifier for the chat where the original message was sent (or
/// channel username in the format @channelusername)
public var fromChatId: TelegramSendChatIdentifier

/// Sends the message silently. Users will receive a notification with no
/// sound.
public var disableNotification: Bool?

/// Message identifier in the chat specified in fromChatId
public var messageId: Int

// MARK: - Initialization

public init(chatId: TelegramSendChatIdentifier, fromChatId: TelegramSendChatIdentifier, disableNotification: Bool? = nil, messageId: Int) {
self.chatId = chatId
self.fromChatId = fromChatId
self.disableNotification = disableNotification
self.messageId = messageId
}
}
66 changes: 66 additions & 0 deletions Sources/TelegramBot/Json/SendApi/TelegramSendPhoto.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// TelegramSendPhoto.swift
// TelegramBot
//
// Created by Koray Koska on 29.01.19.
//

import Foundation

public final class TelegramSendPhoto: Codable {

// MARK: - Primitive types

/// Unique identifier for the target chat or
/// username of the target channel (in the
/// format @channelusername)
public var chatId: TelegramSendChatIdentifier

/// Photo to send. Pass a file_id as String to send a photo
/// that exists on the Telegram servers (recommended),
/// pass an HTTP URL as a String for Telegram to get a
/// photo from the Internet
public var photo: String

/// Photo caption (may also be used when resending
/// photos by file_id), 0-1024 characters
public var caption: String?

/// Send Markdown or HTML, if you want Telegram apps
/// to show bold, italic, fixed-width text or inline URLs in
/// the media caption.
public var parseMode: TelegramSendMessage.ParseMode?

/// Sends the message silently. Users will receive a
/// notification with no sound.
public var disableNotification: Bool?

/// If the message is a reply, ID of the original message
public var replyToMessageId: Int?

/// Additional interface options. A JSON-serialized object
/// for an inline keyboard, custom reply keyboard,
/// instructions to remove reply keyboard or to force a
/// reply from the user.
public var replyMarkup: TelegramSendMessage.ReplyMarkup?

// MARK: - Initialization

public init(
chatId: TelegramSendChatIdentifier,
photo: String,
caption: String? = nil,
parseMode: TelegramSendMessage.ParseMode? = nil,
disableNotification: Bool? = nil,
replyToMessageId: Int? = nil,
replyMarkup: TelegramSendMessage.ReplyMarkup? = nil
) {
self.chatId = chatId
self.photo = photo
self.caption = caption
self.parseMode = parseMode
self.disableNotification = disableNotification
self.replyToMessageId = replyToMessageId
self.replyMarkup = replyMarkup
}
}
32 changes: 28 additions & 4 deletions Sources/TelegramBot/TelegramSendApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,37 @@ import Foundation

public final class TelegramSendApi {

public var baseUrl: String {
return "https://api.telegram.org/bot\(token)"
}
public typealias TelegramResponseCompletion<Result: Codable> = (_ resp: TelegramResponse<Result>) -> Void

public let token: String

public init(token: String) {
public let provider: TelegramApiProvider

public init(token: String, provider: TelegramApiProvider) {
self.token = token
self.provider = provider
}

// MARK: - Methods

public func getMe(response: @escaping TelegramResponseCompletion<TelegramUser>) {
let req = EmptyRequest()

provider.send(method: "getMe", request: req, response: response)
}

public func sendMessage(message: TelegramSendMessage, response: @escaping TelegramResponseCompletion<TelegramMessage>) {
provider.send(method: "sendMessage", request: message, response: response)
}

public func forwardMessage(message: TelegramSendForwardMessage, response: @escaping TelegramResponseCompletion<TelegramMessage>) {
provider.send(method: "forwardMessage", request: message, response: response)
}

public func sendPhoto(photo: TelegramSendPhoto, response: @escaping TelegramResponseCompletion<TelegramMessage>) {
provider.send(method: "sendPhoto", request: photo, response: response)
}
}

private struct EmptyRequest: Codable {
}
96 changes: 96 additions & 0 deletions Sources/TelegramBot/Toolbox/TelegramApiHttpProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//
// TelegramApiHttpProvider.swift
// TelegramBot
//
// Created by Koray Koska on 29.01.19.
//

import Foundation
import Dispatch

public struct TelegramApiHttpProvider: TelegramApiProvider {

let encoder: JSONEncoder
let decoder: JSONDecoder

let queue: DispatchQueue

let session: URLSession

static let headers = [
"Accept": "application/json",
"Content-Type": "application/json"
]

public let url: String

public init(url: String, session: URLSession = URLSession(configuration: .default)) {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
self.encoder = encoder
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
self.decoder = decoder

self.url = url
self.session = session

// Concurrent queue for faster concurrent requests
self.queue = DispatchQueue(label: "TelegramApiHttpProvider", attributes: .concurrent)
}

public func send<Params: Codable, Result>(method: String, request: Params, response: @escaping TelegramApiResponseCompletion<Result>) {
queue.async {

let body: Data
do {
body = try self.encoder.encode(request)
} catch {
let err = TelegramResponse<Result>(error: .requestFailed(error))
response(err)
return
}

guard let url = URL(string: "\(self.url)/\(method)") else {
let err = TelegramResponse<Result>(error: .requestFailed(nil))
response(err)
return
}

var req = URLRequest(url: url)
req.httpMethod = "POST"
req.httpBody = body
for (k, v) in type(of: self).headers {
req.addValue(v, forHTTPHeaderField: k)
}

let task = self.session.dataTask(with: req) { data, urlResponse, error in
guard let urlResponse = urlResponse as? HTTPURLResponse, let data = data, error == nil else {
let err = TelegramResponse<Result>(error: .serverError(error))
response(err)
return
}

let status = urlResponse.statusCode
guard status >= 200 && status < 300 else {
// This is a non typical error response and should be considered a server error.
let err = TelegramResponse<Result>(error: .serverError(nil))
response(err)
return
}

do {
let decoded = try self.decoder.decode(TelegramApiResponse<Result>.self, from: data)
// We got the Result object
let res = TelegramResponse(response: decoded)
response(res)
} catch {
// We don't have the response we expected...
let err = TelegramResponse<Result>(error: .decodingError(error))
response(err)
}
}
task.resume()
}
}
}
124 changes: 124 additions & 0 deletions Sources/TelegramBot/Toolbox/TelegramApiProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//
// TelegramApiProvider.swift
// TelegramBot
//
// Created by Koray Koska on 29.01.19.
//

import Foundation

public protocol TelegramApiProvider {

typealias TelegramApiResponseCompletion<Result: Codable> = (_ resp: TelegramResponse<Result>) -> Void

func send<Params: Codable, Result: Codable>(method: String, request: Params, response: @escaping TelegramApiResponseCompletion<Result>)
}

public struct TelegramResponse<Result: Codable> {

public enum Error: Swift.Error {
case emptyResponse
case requestFailed(Swift.Error?)
case connectionFailed(Swift.Error?)
case serverError(Swift.Error?)
case decodingError(Swift.Error?)
}

public enum Status<Result> {
case success(Result)
case failure(Swift.Error)
}

public let status: Status<Result>

public var result: Result? {
return status.result
}

public var error: Swift.Error? {
return status.error
}

// MARK: - Initialization

public init(status: Status<Result>) {
self.status = status
}

/// Initialize with any Error object
public init(error: Swift.Error) {
self.status = .failure(error)
}

/// Initialize with a response
public init(response: TelegramApiResponse<Result>) {
if let result = response.result {
self.status = .success(result)
} else if let error = response.error {
self.status = .failure(error)
} else {
self.status = .failure(Error.emptyResponse)
}
}

/// For convenience, initialize with one of the common errors
public init(error: Error) {
self.status = .failure(error)
}
}

/// Convenience properties
extension TelegramResponse.Status {
public var isSuccess: Bool {
switch self {
case .success:
return true
case .failure:
return false
}
}

public var isFailure: Bool {
return !isSuccess
}

public var result: Result? {
switch self {
case .success(let value):
return value
case .failure:
return nil
}
}

public var error: Error? {
switch self {
case .failure(let error):
return error
case .success:
return nil
}
}
}

extension TelegramResponse.Status: CustomStringConvertible {
public var description: String {
switch self {
case .success:
return "SUCCESS"
case .failure:
return "FAILURE"
}
}
}

extension TelegramResponse.Status: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
case .success(let value):
return "SUCCESS: \(value)"
case .failure(let error):
return "FAILURE: \(error)"
}
}
}
Loading

0 comments on commit 21bfd58

Please sign in to comment.