diff --git a/Package.swift b/Package.swift index e7898ec..8ba32ed 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:4.0 +// swift-tools-version:5.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -16,12 +16,12 @@ let package = Package( .package(url: "https://github.com/vapor/http.git", from: "3.0.0"), .package(url: "https://github.com/vapor/crypto.git", from: "3.1.0"), .package(url: "https://github.com/vapor/multipart.git", from: "3.0.0"), - .package(url: "https://github.com/IBM-Swift/HeliumLogger.git", from: "1.8.0"), + .package(url: "https://github.com/apple/swift-log.git", .branch("master")) ], targets: [ .target( name: "Telegrammer", - dependencies: ["HTTP", "Multipart", "Crypto", "HeliumLogger"]), + dependencies: ["HTTP", "Multipart", "Crypto", "Logging"]), .target(name: "EchoBot", dependencies: ["Telegrammer"]), .target(name: "HelloBot", dependencies: ["Telegrammer"]), .target(name: "SchedulerBot", dependencies: ["Telegrammer"]), diff --git a/README.md b/README.md index 4a0ba5d..462ccf5 100644 --- a/README.md +++ b/README.md @@ -128,22 +128,38 @@ $ vapor xcode Demo bots --------- -#### EchoBot Sample +#### All sample bots 1. Add Telegram Token in [Environment Variables](http://nshipster.com/launch-arguments-and-environment-variables/), so, either create an environment variable: ``` $ export TELEGRAM_BOT_TOKEN='000000000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' ``` -2. Run EchoBot executable scheme or +2. Run Bot executable scheme or ``` $ swift run ``` -3. Send _**/echo**_ command to bot +[EchoBot sources](https://github.com/givip/Telegrammer/tree/master/Sources/EchoBot) +Starts/stops with command "/echo", then simply responds with your message + +[HelloBot sources](https://github.com/givip/Telegrammer/tree/master/Sources/HelloBot) +Says "Hello" to new users in group. Responds with "hello" message on command "/greet" + +[SchedulerBot sources](https://github.com/givip/Telegrammer/tree/master/Sources/SchedulerBot) +Demonstrate Jobs Queue scheduling mechanism. +Command "/start X" starts repeatable job, wich will send you a message each X seconds. +Command "/once X" will send you message once after timeout of X seconds. +Command "/stop" stops JobsQueue only for you. Other users continues to receive scheduled messages. + +[SpellCheckerBot sources](https://github.com/givip/Telegrammer/tree/master/Sources/SpellCheckerBot) +Demonstrate how works InlineMenus and Callback handlers. +Command "/start" will start bot. +Send any english text to bot and it will be checked for mistakes. Bot will propose you some fixes in case of found mistake. + Requirements --------------- -- Ubuntu 14.04 or later with [Swift 4.2 or later](https://swift.org/getting-started/) / macOS with [Xcode 9.3 or later](https://swift.org/download/) +- Ubuntu 14.04 or later with [Swift 5.0 or later](https://swift.org/getting-started/) / macOS with [Xcode 10.2 beta 4 or later](https://swift.org/download/) - Telegram account and a Telegram App for any platform - [Swift Package Manager (SPM)](https://github.com/apple/swift-package-manager/blob/master/Documentation/Usage.md) for dependencies - [Vapor 3](https://vapor.codes) (optionally, for bots with database and other server side stuff) diff --git a/Sources/EchoBot/main.swift b/Sources/EchoBot/main.swift index 86cce59..6e75e45 100644 --- a/Sources/EchoBot/main.swift +++ b/Sources/EchoBot/main.swift @@ -33,7 +33,7 @@ func echoModeSwitch(_ update: Update, _ context: BotContext?) throws { onText = "ON" userEchoModes[user.id] = true } - + let params = Bot.SendMessageParams(chatId: .chat(message.chat.id), text: "Echo mode turned \(onText)") try bot.sendMessage(params: params) } @@ -59,7 +59,7 @@ do { ///Creating and adding handler for ordinary text messages let echoHandler = MessageHandler(filters: Filters.text, callback: echoResponse) dispatcher.add(handler: echoHandler) - + ///Longpolling updates _ = try Updater(bot: bot, dispatcher: dispatcher).startLongpolling().wait() diff --git a/Sources/HelloBot/Handlers/NewMemberHandler.swift b/Sources/HelloBot/Handlers/NewMemberHandler.swift index 84f772a..988d84b 100644 --- a/Sources/HelloBot/Handlers/NewMemberHandler.swift +++ b/Sources/HelloBot/Handlers/NewMemberHandler.swift @@ -9,16 +9,16 @@ import Foundation import Telegrammer class NewMemberHandler: Handler { - + typealias NewMemberCallback = (_ update: Update) throws -> Void - - var name: String + + var name: String let filters = StatusUpdateFilters.newChatMembers var callback: NewMemberCallback init(callback: @escaping NewMemberCallback, name: String = String(describing: CallbackQueryHandler.self)) { self.callback = callback - self.name = name + self.name = name } func check(update: Update) -> Bool { diff --git a/Sources/HelloBot/main.swift b/Sources/HelloBot/main.swift index f219619..ac20abb 100644 --- a/Sources/HelloBot/main.swift +++ b/Sources/HelloBot/main.swift @@ -57,11 +57,11 @@ do { let dispatcher = Dispatcher(bot: bot) ///Creating and adding New chat member handler - let newMemberHandler = NewMemberHandler(callback: greetNewMember) + let newMemberHandler = NewMemberHandler(callback: greetNewMember) dispatcher.add(handler: newMemberHandler) ///Creating and adding Command handler for '/greet' - let commandHandler = CommandHandler(commands: ["/greet"], callback: greeting) + let commandHandler = CommandHandler(commands: ["/greet"], callback: greeting) dispatcher.add(handler: commandHandler) ///Longpolling updates diff --git a/Sources/SpellCheckerBot/Controllers/SpellCheckerController.swift b/Sources/SpellCheckerBot/Controllers/SpellCheckerController.swift index 684a41f..a363eb0 100644 --- a/Sources/SpellCheckerBot/Controllers/SpellCheckerController.swift +++ b/Sources/SpellCheckerBot/Controllers/SpellCheckerController.swift @@ -39,17 +39,17 @@ class SpellCheckerController { return InlineKeyboardMarkup(inlineKeyboard: menuButtons) } - + func start(_ update: Update, _ context: BotContext?) throws { guard let message = update.message else { return } try respond(to: message, text: "Send text to bot, you want to spellcheck ✅\n\n\(attentionText)") } - + func spellCheck(_ update: Update, _ context: BotContext?) throws { guard let message = update.message, let text = message.text, let user = message.from else { return } - + spellChecker.check(text, lang: .en, format: .plain) { [unowned self] (checks) in guard !checks.isEmpty else { try self.congrat(message: message) @@ -60,7 +60,7 @@ class SpellCheckerController { flow.start(text, checks: checks) self.sessions[user.id] = flow - + try self.begin(flow, to: message) } } @@ -126,7 +126,7 @@ private extension SpellCheckerController { } func congrat(message: Message) throws { - try message.reply(text: "👏 Congratulate! Your text is without any mistakes!", from: bot) + try message.reply(text: "👏 Congratulate! Your text is without any mistakes!", from: bot) } func cancel(message: Message) throws { diff --git a/Sources/SpellCheckerBot/main.swift b/Sources/SpellCheckerBot/main.swift index 29f5f79..beab26f 100644 --- a/Sources/SpellCheckerBot/main.swift +++ b/Sources/SpellCheckerBot/main.swift @@ -6,23 +6,23 @@ import Telegrammer guard let token = Enviroment.get("TELEGRAM_BOT_TOKEN") else { exit(1) } do { - let bot = try Bot(token: token) - - let dispatcher = Dispatcher(bot: bot) - let controller = SpellCheckerController(bot: bot) - - let commandHandler = CommandHandler(commands: ["/start"], callback: controller.start) - dispatcher.add(handler: commandHandler) - - let textHandler = MessageHandler(filters: .private, callback: controller.spellCheck) - dispatcher.add(handler: textHandler) - - let inlineHandler = CallbackQueryHandler(pattern: "\\w+", callback: controller.inline) - dispatcher.add(handler: inlineHandler) - - ///Longpolling updates - _ = try Updater(bot: bot, dispatcher: dispatcher).startLongpolling().wait() - + let bot = try Bot(token: token) + + let dispatcher = Dispatcher(bot: bot) + let controller = SpellCheckerController(bot: bot) + + let commandHandler = CommandHandler(commands: ["/start"], callback: controller.start) + dispatcher.add(handler: commandHandler) + + let textHandler = MessageHandler(filters: .private, callback: controller.spellCheck) + dispatcher.add(handler: textHandler) + + let inlineHandler = CallbackQueryHandler(pattern: "\\w+", callback: controller.inline) + dispatcher.add(handler: inlineHandler) + + ///Longpolling updates + _ = try Updater(bot: bot, dispatcher: dispatcher).startLongpolling().wait() + } catch { - print(error.localizedDescription) + print(error.localizedDescription) } diff --git a/Sources/Telegrammer/Bot/Telegram/Bot.swift b/Sources/Telegrammer/Bot/Telegram/Bot.swift index 701b65c..e4bad6c 100644 --- a/Sources/Telegrammer/Bot/Telegram/Bot.swift +++ b/Sources/Telegrammer/Bot/Telegram/Bot.swift @@ -7,8 +7,7 @@ import Foundation import HTTP -import HeliumLogger -import LoggerAPI +import Logging public final class Bot: BotProtocol { @@ -43,8 +42,6 @@ public final class Bot: BotProtocol { } public init(settings: Settings, numThreads: Int = System.coreCount) throws { - Log.logger = settings.debugMode ? HeliumLogger(.verbose) : HeliumLogger(.error) - self.settings = settings self.requestWorker = MultiThreadedEventLoopGroup(numberOfThreads: numThreads) self.client = try BotClient(host: settings.serverHost, @@ -56,14 +53,12 @@ public final class Bot: BotProtocol { func wrap(_ container: TelegramContainer) throws -> Future { - Log.verbose(logMessage(container)) + log.info(logMessage(container)) if let result = container.result { return Future.map(on: self.requestWorker, { result }) } else { - let error = logError(container) - Log.error(error.localizedDescription) - throw error + throw logError(container) } } @@ -96,7 +91,7 @@ public final class Bot: BotProtocol { return HTTPHeaders() } - func logMessage(_ container: TelegramContainer) -> String { + func logMessage(_ container: TelegramContainer) -> Logger.Message { var resultString = "[]" if let result = container.result { @@ -111,12 +106,12 @@ public final class Bot: BotProtocol { if let value = container.result as? Bool { resultString = value.description } else { - Log.error(error.localizedDescription) + log.error(error.logMessage) } } } - return """ + let logString = """ Received response Code: \(container.errorCode ?? 0) @@ -125,10 +120,16 @@ public final class Bot: BotProtocol { Result: \(resultString) """ + + return Logger.Message(stringLiteral: logString) } func logError(_ container: TelegramContainer) -> Error { - return CoreError(identifier: "DecodingErrors", - reason: container.description ?? "Response error") + let error = CoreError( + identifier: "DecodingErrors", + reason: container.description ?? "Response error" + ) + log.error(error.logMessage) + return error } } diff --git a/Sources/Telegrammer/Bot/Telegram/Jobs/BasicJobQueue.swift b/Sources/Telegrammer/Bot/Telegram/Jobs/BasicJobQueue.swift index bace63a..7c5d36f 100644 --- a/Sources/Telegrammer/Bot/Telegram/Jobs/BasicJobQueue.swift +++ b/Sources/Telegrammer/Bot/Telegram/Jobs/BasicJobQueue.swift @@ -6,8 +6,6 @@ // import Foundation -import HeliumLogger -import LoggerAPI import HTTP public class BasicJobQueue: JobQueue { @@ -25,7 +23,7 @@ public class BasicJobQueue: JobQueue { public func shutdownQueue() { worker.shutdownGracefully { (error) in if let error = error { - Log.error(error.localizedDescription) + log.error(error.logMessage) } } } diff --git a/Sources/Telegrammer/Bot/Telegram/TelegramBot.swift b/Sources/Telegrammer/Bot/Telegram/TelegramBot.swift index 7c8bc3e..982aff3 100644 --- a/Sources/Telegrammer/Bot/Telegram/TelegramBot.swift +++ b/Sources/Telegrammer/Bot/Telegram/TelegramBot.swift @@ -6,3 +6,6 @@ // import Foundation +import Logging + +let log = Logger(label: "com.gp-apps.telegrammer") diff --git a/Sources/Telegrammer/Dispatcher/Dispatcher.swift b/Sources/Telegrammer/Dispatcher/Dispatcher.swift index 1e33430..7e08369 100644 --- a/Sources/Telegrammer/Dispatcher/Dispatcher.swift +++ b/Sources/Telegrammer/Dispatcher/Dispatcher.swift @@ -6,8 +6,6 @@ // import NIO -import HeliumLogger -import LoggerAPI import HTTP /** @@ -15,7 +13,7 @@ import HTTP It supports handlers for different kinds of data: Updates from Telegram, basic text commands and even arbitrary types. */ public class Dispatcher { - + /// Telegram bot instance public let bot: Bot @@ -26,33 +24,33 @@ public class Dispatcher { public let worker: Worker /// Queue which keep all added handlers and gives appropriates - public var handlersQueue: HandlersQueue + public var handlersQueue: HandlersQueue public init(bot: Bot, worker: Worker = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)) { self.bot = bot - self.worker = worker + self.worker = worker self.updateQueue = DispatchQueue(label: "TLGRM-UPDATES-QUEUE", - qos: .default, - attributes: .concurrent, - autoreleaseFrequency: .inherit, - target: nil) - self.handlersQueue = HandlersQueue() + qos: .default, + attributes: .concurrent, + autoreleaseFrequency: .inherit, + target: nil) + self.handlersQueue = HandlersQueue() } - + /** Enqueue new updates array to Updates queue - - - Parameters: - - updates: Array of Telegram updates - */ - public func enqueue(updates: [Update]) { - updates.forEach { (update) in - updateQueue.async { - self.submit(update: update) - } - } - } + + - Parameters: + - updates: Array of Telegram updates + */ + public func enqueue(updates: [Update]) { + updates.forEach { (update) in + updateQueue.async { + self.submit(update: update) + } + } + } } public extension Dispatcher { @@ -60,45 +58,45 @@ public extension Dispatcher { Add new handler to group - Parameters: - - handler: Handler to add in `Dispatcher`'s handlers queue - - group: Group of `Dispatcher`'s handler queue, `zero` group by default + - handler: Handler to add in `Dispatcher`'s handlers queue + - group: Group of `Dispatcher`'s handler queue, `zero` group by default */ func add(handler: Handler, to group: HandlerGroup = .zero) { - self.handlersQueue.add(handler, to: group) - } - + self.handlersQueue.add(handler, to: group) + } + /** Add new error handler to group - Parameters: - - handler: Error Handler to add in `Dispatcher`'s handlers queue + - handler: Error Handler to add in `Dispatcher`'s handlers queue */ func add(errorHandler: ErrorHandler) { - self.handlersQueue.add(errorHandler) - } - + self.handlersQueue.add(errorHandler) + } + /** Remove handler from specific group of `Dispatchers`'s queue Note: If in one group present more then one handlers with the same name, they both will be deleted - Parameters: - - handler: Handler to be removed - - group: Group from which handlers will be removed + - handler: Handler to be removed + - group: Group from which handlers will be removed */ func remove(handler: Handler, from group: HandlerGroup) { - self.handlersQueue.remove(handler, from: group) - } - + self.handlersQueue.remove(handler, from: group) + } + /** Removes error handler - Parameters: - - handler: Handler to be removed + - handler: Handler to be removed */ func remove(errorHandler: ErrorHandler) { - self.handlersQueue.remove(errorHandler) - } + self.handlersQueue.remove(errorHandler) + } } private extension Dispatcher { @@ -113,7 +111,7 @@ private extension Dispatcher { extension Dispatcher: HTTPServerResponder { public func respond(to request: HTTPRequest, on worker: Worker) -> Future { - Log.info(""" + log.info(""" \nReceived telegram webhook request \(request.description) @@ -124,18 +122,18 @@ extension Dispatcher: HTTPServerResponder { return Future.map(on: worker, { try JSONDecoder().decode(Update.self, from: data) }) } .do { (update) in - Log.info("Update \(update.updateId) enqueued to handlers") + log.info("Update \(update.updateId) enqueued to handlers") self.enqueue(updates: [update]) } .flatMap { (update) -> EventLoopFuture in let ok = HTTPResponse(status: .ok) - Log.info("\nResponding: \(ok.description)") + log.info("\nResponding: \(ok.description)") return Future.map(on: worker, { ok }) } .catchFlatMap { (error) -> (EventLoopFuture) in let bad = HTTPResponse(status: .badRequest) - Log.info("\nResponding: \(bad.description)") - Log.error(error.localizedDescription) + log.info("\nResponding: \(bad.description)") + log.error(error.logMessage) return Future.map(on: worker, { bad }) } } diff --git a/Sources/Telegrammer/Dispatcher/HandlersQueue.swift b/Sources/Telegrammer/Dispatcher/HandlersQueue.swift index 410c1f6..125644b 100644 --- a/Sources/Telegrammer/Dispatcher/HandlersQueue.swift +++ b/Sources/Telegrammer/Dispatcher/HandlersQueue.swift @@ -14,75 +14,75 @@ import Foundation will perform after all pending read operations finished */ public final class HandlersQueue { - - public var handlers: [HandlerGroup: [Handler]] { - var handlers: [HandlerGroup: [Handler]] = [:] - concurrentQueue.sync { - handlers = _handlers - } - return handlers - } - public var errorHandlers: [ErrorHandler] { - var errorHandlers: [ErrorHandler] = [] - concurrentQueue.sync { - errorHandlers = _errorHandlers - } - return errorHandlers - } - - private var _handlers: [HandlerGroup: [Handler]] = [:] - private var _handlersGroup: [[Handler]] = [] - private var _errorHandlers: [ErrorHandler] = [] - - private let concurrentQueue = DispatchQueue(label: "TLGRM-HANDLERS-QUEUE", attributes: .concurrent) - - public func add(_ handler: Handler, to group: HandlerGroup) { - concurrentQueue.async(flags: .barrier) { - if var groupHandlers = self._handlers[group] { - groupHandlers.append(handler) - self._handlers[group] = groupHandlers - } else { - self._handlers[group] = [handler] - } - self.sortGroups() - } - } - - public func remove(_ handler: Handler, from group: HandlerGroup) { - concurrentQueue.async(flags: .barrier) { - guard var groupHandlers = self._handlers[group] else { return } - groupHandlers = groupHandlers.filter( { $0.name != handler.name } ) - - if groupHandlers.isEmpty { - self._handlers.removeValue(forKey: group) - self.sortGroups() - } else { - self._handlers[group] = groupHandlers - } - } - } - - public func add(_ errorHandler: ErrorHandler) { - _errorHandlers.append(errorHandler) - } - - public func remove(_ errorHandler: ErrorHandler) { - _errorHandlers = _errorHandlers.filter( { $0.name != errorHandler.name } ) - } - - private func sortGroups() { - _handlersGroup = self._handlers.keys.sorted { $0.id < $1.id }.compactMap { _handlers[$0] } - } - - public func next(for update: Update) -> [Handler] { - var handlers: [Handler] = [] - for group in _handlersGroup { - concurrentQueue.sync { - guard let handler = group.first(where: { $0.check(update: update) }) else { return } - handlers.append(handler) - } - } - return handlers - } + public var handlers: [HandlerGroup: [Handler]] { + var handlers: [HandlerGroup: [Handler]] = [:] + concurrentQueue.sync { + handlers = _handlers + } + return handlers + } + + public var errorHandlers: [ErrorHandler] { + var errorHandlers: [ErrorHandler] = [] + concurrentQueue.sync { + errorHandlers = _errorHandlers + } + return errorHandlers + } + + private var _handlers: [HandlerGroup: [Handler]] = [:] + private var _handlersGroup: [[Handler]] = [] + private var _errorHandlers: [ErrorHandler] = [] + + private let concurrentQueue = DispatchQueue(label: "TLGRM-HANDLERS-QUEUE", attributes: .concurrent) + + public func add(_ handler: Handler, to group: HandlerGroup) { + concurrentQueue.async(flags: .barrier) { + if var groupHandlers = self._handlers[group] { + groupHandlers.append(handler) + self._handlers[group] = groupHandlers + } else { + self._handlers[group] = [handler] + } + self.sortGroups() + } + } + + public func remove(_ handler: Handler, from group: HandlerGroup) { + concurrentQueue.async(flags: .barrier) { + guard var groupHandlers = self._handlers[group] else { return } + groupHandlers = groupHandlers.filter( { $0.name != handler.name } ) + + if groupHandlers.isEmpty { + self._handlers.removeValue(forKey: group) + self.sortGroups() + } else { + self._handlers[group] = groupHandlers + } + } + } + + public func add(_ errorHandler: ErrorHandler) { + _errorHandlers.append(errorHandler) + } + + public func remove(_ errorHandler: ErrorHandler) { + _errorHandlers = _errorHandlers.filter( { $0.name != errorHandler.name } ) + } + + private func sortGroups() { + _handlersGroup = self._handlers.keys.sorted { $0.id < $1.id }.compactMap { _handlers[$0] } + } + + public func next(for update: Update) -> [Handler] { + var handlers: [Handler] = [] + for group in _handlersGroup { + concurrentQueue.sync { + guard let handler = group.first(where: { $0.check(update: update) }) else { return } + handlers.append(handler) + } + } + return handlers + } } diff --git a/Sources/Telegrammer/Handlers/CallbackQueryHandler.swift b/Sources/Telegrammer/Handlers/CallbackQueryHandler.swift index 2373236..e38286a 100644 --- a/Sources/Telegrammer/Handlers/CallbackQueryHandler.swift +++ b/Sources/Telegrammer/Handlers/CallbackQueryHandler.swift @@ -9,20 +9,20 @@ import Foundation /// Handler for CallbackQuery updates public class CallbackQueryHandler: Handler { - - public var name: String - + + public var name: String + let pattern: String let callback: HandlerCallback public init( - pattern: String, - callback: @escaping HandlerCallback, - name: String = String(describing: CallbackQueryHandler.self) - ) { + pattern: String, + callback: @escaping HandlerCallback, + name: String = String(describing: CallbackQueryHandler.self) + ) { self.pattern = pattern self.callback = callback - self.name = name + self.name = name } public func check(update: Update) -> Bool { diff --git a/Sources/Telegrammer/Handlers/CommandHandler.swift b/Sources/Telegrammer/Handlers/CommandHandler.swift index 78733bb..bd904d1 100644 --- a/Sources/Telegrammer/Handlers/CommandHandler.swift +++ b/Sources/Telegrammer/Handlers/CommandHandler.swift @@ -14,12 +14,12 @@ import HTTP and/or some additional text. - Options of this handler - - `editedUpdates` Determines whether the handler should also accept edited messages. + - `editedUpdates` Determines whether the handler should also accept edited messages. */ public class CommandHandler: Handler { - public var name: String - + public var name: String + public struct Options: OptionSet { public let rawValue: Int @@ -36,13 +36,13 @@ public class CommandHandler: Handler { let filters: Filters let options: Options - public init( + public init( name: String = String(describing: CommandHandler.self), - commands: [String], - filters: Filters = .all, - options: Options = [], + commands: [String], + filters: Filters = .all, + options: Options = [], callback: @escaping HandlerCallback - ) { + ) { self.name = name self.commands = Set(commands) self.filters = filters @@ -56,7 +56,7 @@ public class CommandHandler: Handler { update.editedChannelPost != nil { return true } - + guard let message = update.message, filters.check(message), let text = message.text, diff --git a/Sources/Telegrammer/Handlers/Handler.swift b/Sources/Telegrammer/Handlers/Handler.swift index 5da7cf8..a9cfe2a 100644 --- a/Sources/Telegrammer/Handlers/Handler.swift +++ b/Sources/Telegrammer/Handlers/Handler.swift @@ -17,8 +17,8 @@ public typealias HandlerCallback = (_ update: Update, _ context: BotContext?) th Every handler must implement `check` and `handle` methods */ public protocol Handler { - var name: String { get } - + var name: String { get } + func check(update: Update) -> Bool func handle(update: Update, dispatcher: Dispatcher) throws } diff --git a/Sources/Telegrammer/Handlers/MessageHandler.swift b/Sources/Telegrammer/Handlers/MessageHandler.swift index 7399841..d964c18 100644 --- a/Sources/Telegrammer/Handlers/MessageHandler.swift +++ b/Sources/Telegrammer/Handlers/MessageHandler.swift @@ -9,10 +9,10 @@ import HTTP /// Handler for bot messages, can handle normal messages, channel posts, edited messages public class MessageHandler: Handler { - + /// Name of particular MessageHandler, needed for determine handlers instances of one class in groups - public var name: String - + public var name: String + /// Option Set for `MessageHandler` public struct Options: OptionSet { public let rawValue: Int @@ -32,17 +32,17 @@ public class MessageHandler: Handler { let filters: Filters let callback: HandlerCallback let options: Options - - public init( + + public init( name: String = String(describing: MessageHandler.self), - filters: Filters = .all, + filters: Filters = .all, options: Options = [.messageUpdates, .channelPostUpdates], callback: @escaping HandlerCallback - ) { + ) { self.filters = filters self.callback = callback self.options = options - self.name = name + self.name = name } public func check(update: Update) -> Bool { @@ -52,7 +52,7 @@ public class MessageHandler: Handler { } if options.contains(.editedUpdates), update.editedChannelPost != nil || - update.editedMessage != nil { + update.editedMessage != nil { return true } } diff --git a/Sources/Telegrammer/Helpers/Enviroment+Helper.swift b/Sources/Telegrammer/Helpers/Enviroment+Helper.swift index 8a4bce7..4183209 100644 --- a/Sources/Telegrammer/Helpers/Enviroment+Helper.swift +++ b/Sources/Telegrammer/Helpers/Enviroment+Helper.swift @@ -6,7 +6,7 @@ // import Foundation -import LoggerAPI +import Logging public struct Enviroment { /** @@ -17,7 +17,7 @@ public struct Enviroment { - Returns: Optional String */ public static func get(_ key: String) -> String? { - Log.info("Searching Telegram bot TOKEN in enviroment variable \(key)") + log.info("Searching Telegram bot TOKEN in enviroment variable \(key)") return ProcessInfo.processInfo.environment[key] } } diff --git a/Sources/Telegrammer/Helpers/Error+Helpers.swift b/Sources/Telegrammer/Helpers/Error+Helpers.swift new file mode 100644 index 0000000..66c4415 --- /dev/null +++ b/Sources/Telegrammer/Helpers/Error+Helpers.swift @@ -0,0 +1,15 @@ +// +// Error+Helpers.swift +// Telegrammer +// +// Created by Givi on 19/03/2019. +// + +import Foundation +import Logging + +public extension Error { + var logMessage: Logger.Message { + return Logger.Message(stringLiteral: self.localizedDescription) + } +} diff --git a/Sources/Telegrammer/Helpers/HTTPHeader+Helper.swift b/Sources/Telegrammer/Helpers/HTTPHeader+Helper.swift index 37273e7..4ffe1a6 100644 --- a/Sources/Telegrammer/Helpers/HTTPHeader+Helper.swift +++ b/Sources/Telegrammer/Helpers/HTTPHeader+Helper.swift @@ -8,31 +8,31 @@ import HTTP extension HTTPHeaders { - static var contentJson: HTTPHeaders { - return HTTPHeaders([("Content-Type", "application/json")]) - } - - static func typeFormData(boundary: String, length: Int? = nil) -> HTTPHeaders { - return HTTPHeaders([("Content-Type", "multipart/form-data; boundary=\(boundary)")]) - } - - static func dispositionFormData(name: String) -> HTTPHeaders { - return HTTPHeaders([("Content-Disposition", "form-data; name=\"\(name)\"")]) - } - - static func dispositionFormDataFile(name: String, fileName: String? = nil, mime: String? = nil) -> HTTPHeaders { - - var fileNameStr = "" - if let fileName = fileName { - fileNameStr = "; filename=\"\(fileName)\"" - } - - var headers = [("Content-Disposition", "form-data; name=\"\(name)\"\(fileNameStr)")] - - if let mime = mime { - headers.append(("Content-Type", "\(mime)")) - } - - return HTTPHeaders(headers) - } + static var contentJson: HTTPHeaders { + return HTTPHeaders([("Content-Type", "application/json")]) + } + + static func typeFormData(boundary: String, length: Int? = nil) -> HTTPHeaders { + return HTTPHeaders([("Content-Type", "multipart/form-data; boundary=\(boundary)")]) + } + + static func dispositionFormData(name: String) -> HTTPHeaders { + return HTTPHeaders([("Content-Disposition", "form-data; name=\"\(name)\"")]) + } + + static func dispositionFormDataFile(name: String, fileName: String? = nil, mime: String? = nil) -> HTTPHeaders { + + var fileNameStr = "" + if let fileName = fileName { + fileNameStr = "; filename=\"\(fileName)\"" + } + + var headers = [("Content-Disposition", "form-data; name=\"\(name)\"\(fileNameStr)")] + + if let mime = mime { + headers.append(("Content-Type", "\(mime)")) + } + + return HTTPHeaders(headers) + } } diff --git a/Sources/Telegrammer/Helpers/String+Helper.swift b/Sources/Telegrammer/Helpers/String+Helper.swift index f6ceb38..bdb4dc7 100644 --- a/Sources/Telegrammer/Helpers/String+Helper.swift +++ b/Sources/Telegrammer/Helpers/String+Helper.swift @@ -74,3 +74,11 @@ public extension String { return regexp.numberOfMatches(in: self, options: [], range: range) != 0 } } + +import Logging + +public extension String { + var logMessage: Logger.Message { + return Logger.Message(stringLiteral: self) + } +} diff --git a/Sources/Telegrammer/Network/BotClient.swift b/Sources/Telegrammer/Network/BotClient.swift index 41c50ac..fd4b95e 100644 --- a/Sources/Telegrammer/Network/BotClient.swift +++ b/Sources/Telegrammer/Network/BotClient.swift @@ -7,7 +7,6 @@ import Foundation import HTTP -import LoggerAPI public class BotClient { @@ -44,7 +43,7 @@ public class BotClient { let promise = worker.eventLoop.newPromise(TelegramContainer.self) - Log.info("Sending request:\n\(httpRequest.description)") + log.info("Sending request:\n\(httpRequest.description)") worker.eventLoop.execute { self.send(request: httpRequest).whenSuccess({ (container) in @@ -78,31 +77,31 @@ public class BotClient { /* var futureClient: Future if let existingClient = client { - Log.info("Using existing HTTP client") + log.info("Using existing HTTP client") futureClient = Future.map(on: worker, { existingClient }) } else { futureClient = HTTPClient .connect(scheme: .https, hostname: host, port: port, on: worker, onError: { (error) in - Log.info("HTTP Client was down with error: \n\(error.localizedDescription)") - Log.error(error.localizedDescription) + log.info("HTTP Client was down with error: \n\(error.logMessage)") + log.error(error.logMessage) self.client = nil }) .do({ (freshClient) in - Log.info("Creating new HTTP Client") + log.info("Creating new HTTP Client") self.client = freshClient }) } return futureClient .catch { (error) in - Log.info("HTTP Client was down with error: \n\(error.localizedDescription)") - Log.error(error.localizedDescription) + log.info("HTTP Client was down with error: \n\(error.logMessage)") + log.error(error.logMessage) } .then { (client) -> Future in - Log.info("Sending request to vapor HTTPClient") + log.info("Sending request to vapor HTTPClient") return client.send(request) } .map(to: TelegramContainer.self) { (response) -> TelegramContainer in - Log.info("Decoding response from HTTPClient") + log.info("Decoding response from HTTPClient") return try self.decode(response: response) } */ diff --git a/Sources/Telegrammer/Network/Longpolling.swift b/Sources/Telegrammer/Network/Longpolling.swift index 5ef9de4..70ba09f 100644 --- a/Sources/Telegrammer/Network/Longpolling.swift +++ b/Sources/Telegrammer/Network/Longpolling.swift @@ -6,17 +6,15 @@ // import Foundation -import LoggerAPI -import HeliumLogger import HTTP public class Longpolling: Connection { - - public var bot: Bot - public var dispatcher: Dispatcher - public var worker: Worker - public var running: Bool - + + public var bot: Bot + public var dispatcher: Dispatcher + public var worker: Worker + public var running: Bool + public var allowedUpdates: [String]? = nil public var limit: Int? = nil public var bootstrapRetries: Int? = nil @@ -31,19 +29,19 @@ public class Longpolling: Connection { private var pollingPromise: Promise? public init(bot: Bot, dispatcher: Dispatcher, worker: Worker = MultiThreadedEventLoopGroup(numberOfThreads: 1)) { - self.bot = bot - self.dispatcher = dispatcher - self.worker = worker - self.running = false - } - - public func start() throws -> Future { - self.running = true - - let promise = worker.eventLoop.newPromise(Void.self) + self.bot = bot + self.dispatcher = dispatcher + self.worker = worker + self.running = false + } + + public func start() throws -> Future { + self.running = true + + let promise = worker.eventLoop.newPromise(Void.self) pollingPromise = promise - + let params = Bot.GetUpdatesParams(offset: nil, limit: limit, timeout: pollingTimeout, allowedUpdates: allowedUpdates) _ = worker.eventLoop.submit { @@ -53,17 +51,17 @@ public class Longpolling: Connection { }) } - return promise.futureResult - } + return promise.futureResult + } + + public func stop() { + running = false + worker.eventLoop.execute { + self.pollingPromise?.succeed() + } + } - public func stop() { - running = false - worker.eventLoop.execute { - self.pollingPromise?.succeed() - } - } - - private func longpolling(with params: Bot.GetUpdatesParams) { + private func longpolling(with params: Bot.GetUpdatesParams) { var requestBody = params do { try self.bot.getUpdates(params: requestBody) @@ -71,39 +69,39 @@ public class Longpolling: Connection { self.retryRequest(with: params, after: error) } .whenSuccess({ (updates) in - if !updates.isEmpty { - if !self.cleanStart || !(self.cleanStart && self.isFirstRequest) { - self.dispatcher.enqueue(updates: updates) + if !updates.isEmpty { + if !self.cleanStart || !(self.cleanStart && self.isFirstRequest) { + self.dispatcher.enqueue(updates: updates) + } + if let last = updates.last { + requestBody.offset = last.updateId + 1 + } } - if let last = updates.last { - requestBody.offset = last.updateId + 1 - } - } - self.isFirstRequest = false - self.scheduleLongpolling(with: requestBody) - }) + self.isFirstRequest = false + self.scheduleLongpolling(with: requestBody) + }) } catch { - Log.error(error.localizedDescription) + log.error(error.logMessage) retryRequest(with: params, after: error) - } - } - - private func scheduleLongpolling(with params: Bot.GetUpdatesParams) { - _ = worker.eventLoop.scheduleTask(in: pollingInterval) { () -> Void in - self.longpolling(with: params) - } - } + } + } + + private func scheduleLongpolling(with params: Bot.GetUpdatesParams) { + _ = worker.eventLoop.scheduleTask(in: pollingInterval) { () -> Void in + self.longpolling(with: params) + } + } private func retryRequest(with params: Bot.GetUpdatesParams, after error: Error) { guard let maxRetries = bootstrapRetries, connectionRetries < maxRetries else { running = false - Log.error("Failed connection after \(connectionRetries) retries") + log.error("Failed connection after \(connectionRetries) retries") pollingPromise?.fail(error: error) return } connectionRetries += 1 - Log.info("Retry \(connectionRetries) after failed request") + log.warning("Retry \(connectionRetries) after failed request") _ = worker.eventLoop.scheduleTask(in: pollingInterval, { () -> Void in self.longpolling(with: params) diff --git a/Sources/Telegrammer/Network/Network.swift b/Sources/Telegrammer/Network/Network.swift index eb16a48..2f7d370 100644 --- a/Sources/Telegrammer/Network/Network.swift +++ b/Sources/Telegrammer/Network/Network.swift @@ -15,8 +15,8 @@ public typealias Future = EventLoopFuture public typealias Promise = EventLoopPromise public protocol Connection { - var bot: Bot { get } - var dispatcher: Dispatcher { get } - var worker: Worker { get } - var running: Bool { get set } + var bot: Bot { get } + var dispatcher: Dispatcher { get } + var worker: Worker { get } + var running: Bool { get set } } diff --git a/Sources/Telegrammer/Network/Webhooks.swift b/Sources/Telegrammer/Network/Webhooks.swift index 5ec2353..053be06 100644 --- a/Sources/Telegrammer/Network/Webhooks.swift +++ b/Sources/Telegrammer/Network/Webhooks.swift @@ -6,31 +6,29 @@ // import Foundation -import HeliumLogger -import LoggerAPI import HTTP import NIO class Webhooks: Connection { - public var bot: Bot - public var dispatcher: Dispatcher - public var worker: Worker - public var running: Bool + public var bot: Bot + public var dispatcher: Dispatcher + public var worker: Worker + public var running: Bool public var readLatency: TimeAmount = .seconds(2) public var clean: Bool = false public var maxConnections: Int = 40 - - private var server: HTTPServer? - + + private var server: HTTPServer? + public init(bot: Bot, dispatcher: Dispatcher, worker: Worker = MultiThreadedEventLoopGroup(numberOfThreads: 4)) { - self.bot = bot - self.dispatcher = dispatcher - self.worker = worker - self.running = false - } - + self.bot = bot + self.dispatcher = dispatcher + self.worker = worker + self.running = false + } + public func start() throws -> Future { guard let ip = bot.settings.webhooksIp, let url = bot.settings.webhooksUrl, @@ -38,13 +36,13 @@ class Webhooks: Connection { throw CoreError(identifier: "Webhooks", reason: "Initialization parameters wasn't found in enviroment variables") } - + var cert: InputFile? = nil - + if let publicCert = bot.settings.webhooksPublicCert { guard let fileHandle = FileHandle(forReadingAtPath: publicCert) else { let errorDescription = "Public key '\(publicCert)' was specified for HTTPS server, but wasn't found" - Log.error(errorDescription) + log.error(errorDescription.logMessage) throw CoreError(identifier: "FileIO", reason: errorDescription) } cert = InputFile(data: fileHandle.readDataToEndOfFile(), filename: publicCert) @@ -52,22 +50,22 @@ class Webhooks: Connection { let params = Bot.SetWebhookParams(url: url, certificate: cert, maxConnections: maxConnections, allowedUpdates: nil) return try bot.setWebhook(params: params).flatMap { (success) -> Future in - Log.info("setWebhook request result: \(success)") + log.info("setWebhook request result: \(success)") return try self.listenWebhooks(on: ip, port: port).then { $0.onClose } } } - + private func listenWebhooks(on host: String, port: Int) throws -> Future { return HTTPServer.start(hostname: host, port: port, responder: dispatcher, on: worker) .do { (server) in - Log.info("HTTP server started on: \(host):\(port)") + log.info("HTTP server started on: \(host):\(port)") self.server = server self.running = true - } + } + } + + public func stop() { + self.running = false + _ = self.server?.close() } - - public func stop() { - self.running = false - _ = self.server?.close() - } } diff --git a/Sources/Telegrammer/Types/FileInfo.swift b/Sources/Telegrammer/Types/FileInfo.swift index a8a014e..d0dab53 100644 --- a/Sources/Telegrammer/Types/FileInfo.swift +++ b/Sources/Telegrammer/Types/FileInfo.swift @@ -19,22 +19,22 @@ import Foundation SeeAlso Telegram Bot API Reference: [Sending Files](https://core.telegram.org/bots/api#sending-files) */ - public enum FileInfo: Encodable { - - case fileId(String) - case url(String) - case file(InputFile) - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - switch self { - case .fileId(let string): - try container.encode(string) - case .url(let string): - try container.encode(string) - case .file(let file): - try container.encode(file) - } - } +public enum FileInfo: Encodable { + + case fileId(String) + case url(String) + case file(InputFile) + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .fileId(let string): + try container.encode(string) + case .url(let string): + try container.encode(string) + case .file(let file): + try container.encode(file) + } + } } diff --git a/Sources/Telegrammer/Types/InlineQueryResult.swift b/Sources/Telegrammer/Types/InlineQueryResult.swift index dc07438..951f5a6 100644 --- a/Sources/Telegrammer/Types/InlineQueryResult.swift +++ b/Sources/Telegrammer/Types/InlineQueryResult.swift @@ -8,158 +8,158 @@ /// This enum represents one result of an inline query. Telegram clients currently support results of the following 20 types public enum InlineQueryResult: Codable { - case cachedAudio(InlineQueryResultAudio) - case cachedDocument(InlineQueryResultCachedDocument) - case cachedGif(InlineQueryResultCachedGif) - case cachedMpeg4Gif(InlineQueryResultCachedMpeg4Gif) - case cachedPhoto(InlineQueryResultCachedPhoto) - case cachedSticker(InlineQueryResultCachedSticker) - case cachedVideo(InlineQueryResultCachedVideo) - case cachedVoice(InlineQueryResultCachedVoice) - case article(InlineQueryResultArticle) - case audio(InlineQueryResultAudio) - case contact(InlineQueryResultContact) - case game(InlineQueryResultGame) - case document(InlineQueryResultDocument) - case gif(InlineQueryResultGif) - case location(InlineQueryResultLocation) - case mpeg4Gif(InlineQueryResultMpeg4Gif) - case photo(InlineQueryResultPhoto) - case venue(InlineQueryResultVenue) - case video(InlineQueryResultVideo) - case voice(InlineQueryResultVoice) - case undefined - - - public init(from decoder: Decoder) throws { - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultAudio.self) { - self = .cachedAudio(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultCachedDocument.self) { - self = .cachedDocument(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultCachedGif.self) { - self = .cachedGif(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultCachedMpeg4Gif.self) { - self = .cachedMpeg4Gif(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultCachedPhoto.self) { - self = .cachedPhoto(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultCachedSticker.self) { - self = .cachedSticker(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultCachedVideo.self) { - self = .cachedVideo(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultCachedVoice.self) { - self = .cachedVoice(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultArticle.self) { - self = .article(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultAudio.self) { - self = .audio(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultContact.self) { - self = .contact(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultGame.self) { - self = .game(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultDocument.self) { - self = .document(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultGif.self) { - self = .gif(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultLocation.self) { - self = .location(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultMpeg4Gif.self) { - self = .mpeg4Gif(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultPhoto.self) { - self = .photo(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultVenue.self) { - self = .venue(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultVideo.self) { - self = .video(value) - return - } - if let value = try? decoder.singleValueContainer().decode(InlineQueryResultVoice.self) { - self = .voice(value) - return - } - self = .undefined - } - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - switch self { - case .cachedAudio(let value): - try container.encode(value) - case .cachedDocument(let value): - try container.encode(value) - case .cachedGif(let value): - try container.encode(value) - case .cachedMpeg4Gif(let value): - try container.encode(value) - case .cachedPhoto(let value): - try container.encode(value) - case .cachedSticker(let value): - try container.encode(value) - case .cachedVideo(let value): - try container.encode(value) - case .cachedVoice(let value): - try container.encode(value) - case .article(let value): - try container.encode(value) - case .audio(let value): - try container.encode(value) - case .contact(let value): - try container.encode(value) - case .game(let value): - try container.encode(value) - case .document(let value): - try container.encode(value) - case .gif(let value): - try container.encode(value) - case .location(let value): - try container.encode(value) - case .mpeg4Gif(let value): - try container.encode(value) - case .photo(let value): - try container.encode(value) - case .venue(let value): - try container.encode(value) - case .video(let value): - try container.encode(value) - case .voice(let value): - try container.encode(value) - case .undefined: - try container.encodeNil() - } - } + case cachedAudio(InlineQueryResultAudio) + case cachedDocument(InlineQueryResultCachedDocument) + case cachedGif(InlineQueryResultCachedGif) + case cachedMpeg4Gif(InlineQueryResultCachedMpeg4Gif) + case cachedPhoto(InlineQueryResultCachedPhoto) + case cachedSticker(InlineQueryResultCachedSticker) + case cachedVideo(InlineQueryResultCachedVideo) + case cachedVoice(InlineQueryResultCachedVoice) + case article(InlineQueryResultArticle) + case audio(InlineQueryResultAudio) + case contact(InlineQueryResultContact) + case game(InlineQueryResultGame) + case document(InlineQueryResultDocument) + case gif(InlineQueryResultGif) + case location(InlineQueryResultLocation) + case mpeg4Gif(InlineQueryResultMpeg4Gif) + case photo(InlineQueryResultPhoto) + case venue(InlineQueryResultVenue) + case video(InlineQueryResultVideo) + case voice(InlineQueryResultVoice) + case undefined + + + public init(from decoder: Decoder) throws { + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultAudio.self) { + self = .cachedAudio(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultCachedDocument.self) { + self = .cachedDocument(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultCachedGif.self) { + self = .cachedGif(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultCachedMpeg4Gif.self) { + self = .cachedMpeg4Gif(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultCachedPhoto.self) { + self = .cachedPhoto(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultCachedSticker.self) { + self = .cachedSticker(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultCachedVideo.self) { + self = .cachedVideo(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultCachedVoice.self) { + self = .cachedVoice(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultArticle.self) { + self = .article(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultAudio.self) { + self = .audio(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultContact.self) { + self = .contact(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultGame.self) { + self = .game(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultDocument.self) { + self = .document(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultGif.self) { + self = .gif(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultLocation.self) { + self = .location(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultMpeg4Gif.self) { + self = .mpeg4Gif(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultPhoto.self) { + self = .photo(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultVenue.self) { + self = .venue(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultVideo.self) { + self = .video(value) + return + } + if let value = try? decoder.singleValueContainer().decode(InlineQueryResultVoice.self) { + self = .voice(value) + return + } + self = .undefined + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .cachedAudio(let value): + try container.encode(value) + case .cachedDocument(let value): + try container.encode(value) + case .cachedGif(let value): + try container.encode(value) + case .cachedMpeg4Gif(let value): + try container.encode(value) + case .cachedPhoto(let value): + try container.encode(value) + case .cachedSticker(let value): + try container.encode(value) + case .cachedVideo(let value): + try container.encode(value) + case .cachedVoice(let value): + try container.encode(value) + case .article(let value): + try container.encode(value) + case .audio(let value): + try container.encode(value) + case .contact(let value): + try container.encode(value) + case .game(let value): + try container.encode(value) + case .document(let value): + try container.encode(value) + case .gif(let value): + try container.encode(value) + case .location(let value): + try container.encode(value) + case .mpeg4Gif(let value): + try container.encode(value) + case .photo(let value): + try container.encode(value) + case .venue(let value): + try container.encode(value) + case .video(let value): + try container.encode(value) + case .voice(let value): + try container.encode(value) + case .undefined: + try container.encodeNil() + } + } } diff --git a/Sources/Telegrammer/Types/InputFile.swift b/Sources/Telegrammer/Types/InputFile.swift index 2bb2b72..426e0ae 100644 --- a/Sources/Telegrammer/Types/InputFile.swift +++ b/Sources/Telegrammer/Types/InputFile.swift @@ -41,16 +41,16 @@ public struct InputFile: Encodable, MultipartPartConvertible { } var name: String? - var filename: String - var data: Data - var mimeType: String? - + var filename: String + var data: Data + var mimeType: String? + public init(data: Data, name: String? = nil, filename: String, mimeType: String? = nil) { self.data = data self.name = name - self.filename = filename - self.mimeType = mimeType - } + self.filename = filename + self.mimeType = mimeType + } public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() diff --git a/Sources/Telegrammer/Types/MessageOrBool.swift b/Sources/Telegrammer/Types/MessageOrBool.swift index 31c6108..a39e996 100644 --- a/Sources/Telegrammer/Types/MessageOrBool.swift +++ b/Sources/Telegrammer/Types/MessageOrBool.swift @@ -7,25 +7,25 @@ /// Sometimes bot methods returns objects On success, otherwise returns False public enum MessageOrBool: Codable { - case message(Message) - case bool(Bool) - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - switch self { - case .bool(let value): - try container.encode(value) - case .message(let message): - try container.encode(message) - } - } - - public init(from decoder: Decoder) throws { - if let value = try? decoder.singleValueContainer().decode(Bool.self) { - self = .bool(value) - } else { - let message = try decoder.singleValueContainer().decode(Message.self) - self = .message(message) - } - } + case message(Message) + case bool(Bool) + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .bool(let value): + try container.encode(value) + case .message(let message): + try container.encode(message) + } + } + + public init(from decoder: Decoder) throws { + if let value = try? decoder.singleValueContainer().decode(Bool.self) { + self = .bool(value) + } else { + let message = try decoder.singleValueContainer().decode(Message.self) + self = .message(message) + } + } } diff --git a/Sources/Telegrammer/Types/ReplyMarkup.swift b/Sources/Telegrammer/Types/ReplyMarkup.swift index 5bd1fe6..e85decb 100644 --- a/Sources/Telegrammer/Types/ReplyMarkup.swift +++ b/Sources/Telegrammer/Types/ReplyMarkup.swift @@ -11,45 +11,45 @@ [Reply Markups](https://core.telegram.org/bots/2-0-intro#new-inline-keyboards) */ public enum ReplyMarkup: Codable, MultipartPartNestedConvertible { - case inlineKeyboardMarkup(InlineKeyboardMarkup) - case replyKeyboardMarkup(ReplyKeyboardMarkup) - case replyKeyboardRemove(ReplyKeyboardRemove) - case forceReply(ForceReply) - case undefined - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - switch self { - case .inlineKeyboardMarkup(let value): - try container.encode(value) - case .replyKeyboardMarkup(let value): - try container.encode(value) - case .replyKeyboardRemove(let value): - try container.encode(value) - case .forceReply(let value): - try container.encode(value) - case .undefined: - try container.encodeNil() - } - } - - public init(from decoder: Decoder) throws { - if let value = try? decoder.singleValueContainer().decode(InlineKeyboardMarkup.self) { - self = .inlineKeyboardMarkup(value) - return - } - if let value = try? decoder.singleValueContainer().decode(ReplyKeyboardMarkup.self) { - self = .replyKeyboardMarkup(value) - return - } - if let value = try? decoder.singleValueContainer().decode(ReplyKeyboardRemove.self) { - self = .replyKeyboardRemove(value) - return - } - if let value = try? decoder.singleValueContainer().decode(ForceReply.self) { - self = .forceReply(value) - return - } - self = .undefined - } + case inlineKeyboardMarkup(InlineKeyboardMarkup) + case replyKeyboardMarkup(ReplyKeyboardMarkup) + case replyKeyboardRemove(ReplyKeyboardRemove) + case forceReply(ForceReply) + case undefined + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .inlineKeyboardMarkup(let value): + try container.encode(value) + case .replyKeyboardMarkup(let value): + try container.encode(value) + case .replyKeyboardRemove(let value): + try container.encode(value) + case .forceReply(let value): + try container.encode(value) + case .undefined: + try container.encodeNil() + } + } + + public init(from decoder: Decoder) throws { + if let value = try? decoder.singleValueContainer().decode(InlineKeyboardMarkup.self) { + self = .inlineKeyboardMarkup(value) + return + } + if let value = try? decoder.singleValueContainer().decode(ReplyKeyboardMarkup.self) { + self = .replyKeyboardMarkup(value) + return + } + if let value = try? decoder.singleValueContainer().decode(ReplyKeyboardRemove.self) { + self = .replyKeyboardRemove(value) + return + } + if let value = try? decoder.singleValueContainer().decode(ForceReply.self) { + self = .forceReply(value) + return + } + self = .undefined + } } diff --git a/Sources/Telegrammer/Types/TelegramContainer.swift b/Sources/Telegrammer/Types/TelegramContainer.swift index ade05d4..9bdc43f 100644 --- a/Sources/Telegrammer/Types/TelegramContainer.swift +++ b/Sources/Telegrammer/Types/TelegramContainer.swift @@ -7,28 +7,28 @@ /// This object represents a Telegram server response container. public struct TelegramContainer: Codable { - - enum CodingKeys: String, CodingKey { - case ok = "ok" - case result = "result" - case description = "description" - case errorCode = "error_code" + + enum CodingKeys: String, CodingKey { + case ok = "ok" + case result = "result" + case description = "description" + case errorCode = "error_code" case parameters = "parameters" - } - - public var ok: Bool - public var result: T? - public var description: String? - public var errorCode: Int? + } + + public var ok: Bool + public var result: T? + public var description: String? + public var errorCode: Int? public var parameters: ResponseParameters? - + public init (ok: Bool, result: T?, description: String?, errorCode: Int?, parameters: ResponseParameters?) { - self.ok = ok - self.result = result - self.description = description - self.errorCode = errorCode + self.ok = ok + self.result = result + self.description = description + self.errorCode = errorCode self.parameters = parameters - } + } } diff --git a/Sources/Telegrammer/Types/TelegramEncodables.swift b/Sources/Telegrammer/Types/TelegramEncodables.swift index 09b7b21..9b11f20 100644 --- a/Sources/Telegrammer/Types/TelegramEncodables.swift +++ b/Sources/Telegrammer/Types/TelegramEncodables.swift @@ -12,16 +12,16 @@ import Multipart protocol JSONEncodable: Encodable {} extension JSONEncodable { - func encodeBody() throws -> Data { - return try JSONEncoder().encode(self) - } + func encodeBody() throws -> Data { + return try JSONEncoder().encode(self) + } } /// Represent Telegram type, which will be encoded as multipart/form-data on sending to server protocol MultipartEncodable: Encodable {} extension MultipartEncodable { - func encodeBody(boundary: [UInt8]) throws -> Data { - return try FormDataEncoder().encode(self, boundary: boundary) - } + func encodeBody(boundary: [UInt8]) throws -> Data { + return try FormDataEncoder().encode(self, boundary: boundary) + } } diff --git a/Sources/Telegrammer/Updater/Updater.swift b/Sources/Telegrammer/Updater/Updater.swift index 082dad5..216bc8e 100644 --- a/Sources/Telegrammer/Updater/Updater.swift +++ b/Sources/Telegrammer/Updater/Updater.swift @@ -15,7 +15,7 @@ import NIO This is achieved using the Webhooks and Longpolling classes. */ public final class Updater { - + /// Bot instance which perform requests and establish http server public let bot: Bot @@ -24,11 +24,11 @@ public final class Updater { /// EventLoopGroup for networking stuff public let worker: Worker - - private var longpollingConnection: Longpolling! + + private var longpollingConnection: Longpolling! private var webhooksListener: Webhooks! - - @discardableResult + + @discardableResult public init(bot: Bot, dispatcher: Dispatcher, worker: Worker = MultiThreadedEventLoopGroup(numberOfThreads: 1)) { self.bot = bot self.dispatcher = dispatcher @@ -47,7 +47,7 @@ public final class Updater { webhooksListener = Webhooks(bot: bot, dispatcher: dispatcher, worker: worker) return try webhooksListener.start() } - + /** Call this method to start receiving updates from Telegram by longpolling. @@ -56,11 +56,11 @@ public final class Updater { - Throws: Throws on errors - Returns: Future of `Void` type */ - public func startLongpolling() throws -> Future { - longpollingConnection = Longpolling(bot: bot, dispatcher: dispatcher, worker: worker) - return try longpollingConnection.start() - } - + public func startLongpolling() throws -> Future { + longpollingConnection = Longpolling(bot: bot, dispatcher: dispatcher, worker: worker) + return try longpollingConnection.start() + } + /** Call this method to stop receiving updates from Telegram. */