From 2e230e40a492705ab1f22d6f303cd8d8bf9e717e Mon Sep 17 00:00:00 2001 From: Mauricio Cardozo Date: Thu, 9 Nov 2023 15:22:35 -0300 Subject: [PATCH 1/3] refactors how commands are added to bots --- .gitignore | 1 + .../AdminLib/Sources/AdminLib/Events.swift | 14 ++-- .../xcschemes/LandinhoBot.xcscheme | 84 ------------------- .../Sources/LandinhoBot/SwiftyBot/Bot.swift | 12 +++ .../LandinhoBot/SwiftyBot/Command.swift | 25 +----- .../LandinhoBot/SwiftyBot/SwiftyBot.swift | 24 ++++-- .../Telegram/TelegramBot+Bot.swift | 20 +++++ .../TelegramBot+ChatUpdate.swift} | 15 +--- .../Telegram/TelegramBot+Command.swift | 18 ++++ .../Telegram/TelegramBot+Reply.swift | 4 - .../LandinhoBot/Telegram/TelegramError.swift | 12 +++ .../VroomBot/DefaultVroomBot.swift | 27 ++++++ .../Services/CategoryListCommand.swift | 7 ++ .../VroomBot/Services/HelpCommand.swift | 14 ++++ .../Services/NextRaceCommand.swift} | 27 ++---- 15 files changed, 149 insertions(+), 155 deletions(-) delete mode 100644 telegram/.swiftpm/xcode/xcshareddata/xcschemes/LandinhoBot.xcscheme create mode 100644 telegram/Sources/LandinhoBot/SwiftyBot/Bot.swift create mode 100644 telegram/Sources/LandinhoBot/Telegram/TelegramBot+Bot.swift rename telegram/Sources/LandinhoBot/{SwiftyBot/SwiftyBot+Telegram.swift => Telegram/TelegramBot+ChatUpdate.swift} (81%) create mode 100644 telegram/Sources/LandinhoBot/Telegram/TelegramBot+Command.swift create mode 100644 telegram/Sources/LandinhoBot/Telegram/TelegramError.swift create mode 100644 telegram/Sources/LandinhoBot/VroomBot/DefaultVroomBot.swift create mode 100644 telegram/Sources/LandinhoBot/VroomBot/Services/CategoryListCommand.swift create mode 100644 telegram/Sources/LandinhoBot/VroomBot/Services/HelpCommand.swift rename telegram/Sources/LandinhoBot/{DefaultVroomBot.swift => VroomBot/Services/NextRaceCommand.swift} (81%) diff --git a/.gitignore b/.gitignore index 0879010..f399847 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ DerivedData/ .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata .netrc telegram/.swiftpm/xcode/xcshareddata/xcschemes/LandinhoBot.xcscheme +telegram/.swiftpm/xcode/xcshareddata/xcschemes/LandinhoBot.xcscheme diff --git a/SuperLandoAdmin/AdminLib/Sources/AdminLib/Events.swift b/SuperLandoAdmin/AdminLib/Sources/AdminLib/Events.swift index 706a630..68be128 100644 --- a/SuperLandoAdmin/AdminLib/Sources/AdminLib/Events.swift +++ b/SuperLandoAdmin/AdminLib/Sources/AdminLib/Events.swift @@ -37,19 +37,19 @@ public enum UploadRaceEventBundle: Equatable { switch self { case .F1Sprint: [ - .init(title: "Practice 1", date: Date()), + .init(title: "Treino Livre", date: Date()), .init(title: "Qualifying", date: Date()), - .init(title: "Practice 2", date: Date()), + .init(title: "Sprint Shootout", date: Date()), .init(title: "Sprint", date: Date()), - .init(title: "Race", date: Date()), + .init(title: "Corrida", date: Date()), ] case .F1Regular: [ - .init(title: "Practice 1", date: Date()), - .init(title: "Practice 2", date: Date()), - .init(title: "Practice 3", date: Date()), + .init(title: "Treino Livre 1", date: Date()), + .init(title: "Treino Livre 2", date: Date()), + .init(title: "Treino Livre 3", date: Date()), .init(title: "Qualifying", date: Date()), - .init(title: "Race", date: Date()), + .init(title: "Corrida", date: Date()), ] } } diff --git a/telegram/.swiftpm/xcode/xcshareddata/xcschemes/LandinhoBot.xcscheme b/telegram/.swiftpm/xcode/xcshareddata/xcschemes/LandinhoBot.xcscheme deleted file mode 100644 index 274c054..0000000 --- a/telegram/.swiftpm/xcode/xcshareddata/xcschemes/LandinhoBot.xcscheme +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/telegram/Sources/LandinhoBot/SwiftyBot/Bot.swift b/telegram/Sources/LandinhoBot/SwiftyBot/Bot.swift new file mode 100644 index 0000000..3832718 --- /dev/null +++ b/telegram/Sources/LandinhoBot/SwiftyBot/Bot.swift @@ -0,0 +1,12 @@ +// +// Bot.swift +// +// +// Created by Mauricio Cardozo on 09/11/23. +// + +import Foundation + +public protocol Bot { + func reply(_ update: ChatUpdate, text: String) async throws +} diff --git a/telegram/Sources/LandinhoBot/SwiftyBot/Command.swift b/telegram/Sources/LandinhoBot/SwiftyBot/Command.swift index b8509eb..3fc9496 100644 --- a/telegram/Sources/LandinhoBot/SwiftyBot/Command.swift +++ b/telegram/Sources/LandinhoBot/SwiftyBot/Command.swift @@ -5,25 +5,8 @@ // Created by Mauricio Cardozo on 23/10/23. // -public struct Command { - let command: String - let description: String - let handler: (ChatUpdate) async throws -> Void - - public init( - command: String, - description: String, - handler: @escaping (ChatUpdate) async throws -> Void) - { - /// A bot command **must** be lowercased - let sanitizedCommand = command.lowercased() - - if description.isEmpty { - fatalError("Command description must be non-empty") - } - - self.command = sanitizedCommand - self.description = description - self.handler = handler - } +public protocol Command { + var command: String { get } + var description: String { get } + func handle(update: ChatUpdate, bot: Bot, debugMessage: (String) -> Void) async throws } diff --git a/telegram/Sources/LandinhoBot/SwiftyBot/SwiftyBot.swift b/telegram/Sources/LandinhoBot/SwiftyBot/SwiftyBot.swift index a3bb088..8df8635 100644 --- a/telegram/Sources/LandinhoBot/SwiftyBot/SwiftyBot.swift +++ b/telegram/Sources/LandinhoBot/SwiftyBot/SwiftyBot.swift @@ -7,11 +7,15 @@ import TelegramBotSDK open class SwiftyBot { public init() { - self.bot = TelegramBot(token: Environment.telegramToken) + self._bot = TelegramBot(token: Environment.telegramToken) + self.bot = _bot + registerCommands() } - public let bot: TelegramBot + public let bot: Bot + // TODO: Make the internal bot more generic so we can support other platforms in the future + let _bot: TelegramBot open var commands: [Command] { fatalError("Please subclass SwiftyBot") @@ -25,7 +29,7 @@ open class SwiftyBot { return } - bot.setMyCommandsAsync(commands: cmds) { [unowned self] result, error in + _bot.setMyCommandsAsync(commands: cmds) { [unowned self] result, error in debugMessage("SET COMMANDS ASYNC") if let error { @@ -35,11 +39,11 @@ open class SwiftyBot { } public func update() { - while let update = bot.nextUpdateSync() { + while let update = _bot.nextUpdateSync() { guard let chatUpdate = ChatUpdate(update), let cmd = commands.first(where: { - chatUpdate.command == "/\($0.command)" + chatUpdate.command == "/\($0.command.lowercased())" }) else { continue @@ -47,7 +51,13 @@ open class SwiftyBot { firehose(chatUpdate) Task { - try await cmd.handler(chatUpdate) + try await cmd.handle( + update: chatUpdate, + bot: bot, + debugMessage: { [weak self] str in + self?.debugMessage(str) + } + ) } } } @@ -74,7 +84,7 @@ open class SwiftyBot { return } - try await bot.sendMessageAsync(chatId: .chat(chatID), text: message) + try await _bot.sendMessageAsync(chatId: .chat(chatID), text: message) } } } diff --git a/telegram/Sources/LandinhoBot/Telegram/TelegramBot+Bot.swift b/telegram/Sources/LandinhoBot/Telegram/TelegramBot+Bot.swift new file mode 100644 index 0000000..1fe0c9f --- /dev/null +++ b/telegram/Sources/LandinhoBot/Telegram/TelegramBot+Bot.swift @@ -0,0 +1,20 @@ +// +// Telegram+Bot.swift +// +// +// Created by Mauricio Cardozo on 09/11/23. +// + +import Foundation +import TelegramBotSDK + +extension TelegramBot: Bot { + public func reply(_ update: ChatUpdate, text: String) async throws { + guard let chatID = Int64(update.chatID) else { + throw TelegramError(message: "Couldn't convert chatID String to Int64") + } + try await sendMessageAsync( + chatId: .chat(chatID), + text: text) + } +} diff --git a/telegram/Sources/LandinhoBot/SwiftyBot/SwiftyBot+Telegram.swift b/telegram/Sources/LandinhoBot/Telegram/TelegramBot+ChatUpdate.swift similarity index 81% rename from telegram/Sources/LandinhoBot/SwiftyBot/SwiftyBot+Telegram.swift rename to telegram/Sources/LandinhoBot/Telegram/TelegramBot+ChatUpdate.swift index d3a1f03..c47ada9 100644 --- a/telegram/Sources/LandinhoBot/SwiftyBot/SwiftyBot+Telegram.swift +++ b/telegram/Sources/LandinhoBot/Telegram/TelegramBot+ChatUpdate.swift @@ -1,10 +1,11 @@ // -// SwiftyBot+Telegram.swift +// File.swift +// // -// -// Created by Mauricio Cardozo on 23/10/23. +// Created by Mauricio Cardozo on 09/11/23. // +import Foundation import TelegramBotSDK extension ChatUpdate { @@ -29,11 +30,3 @@ extension ChatUpdate { self.chatName = message.chat.username ?? message.chat.title ?? "" } } - -extension Command { - var botCommand: BotCommand { - .init( - command: command, - description: description) - } -} diff --git a/telegram/Sources/LandinhoBot/Telegram/TelegramBot+Command.swift b/telegram/Sources/LandinhoBot/Telegram/TelegramBot+Command.swift new file mode 100644 index 0000000..fbe2b4e --- /dev/null +++ b/telegram/Sources/LandinhoBot/Telegram/TelegramBot+Command.swift @@ -0,0 +1,18 @@ +// +// SwiftyBot+Telegram.swift +// +// +// Created by Mauricio Cardozo on 23/10/23. +// + +import TelegramBotSDK + +extension Command { + var botCommand: BotCommand { + if description.isEmpty { fatalError("Command description should not be empty") } + + return .init( + command: command.lowercased(), + description: description) + } +} diff --git a/telegram/Sources/LandinhoBot/Telegram/TelegramBot+Reply.swift b/telegram/Sources/LandinhoBot/Telegram/TelegramBot+Reply.swift index d72834f..86e3885 100644 --- a/telegram/Sources/LandinhoBot/Telegram/TelegramBot+Reply.swift +++ b/telegram/Sources/LandinhoBot/Telegram/TelegramBot+Reply.swift @@ -8,10 +8,6 @@ import Foundation import TelegramBotSDK -struct TelegramError: Error { - let message: String -} - extension TelegramBot { @discardableResult func reply(_ update: ChatUpdate, text: String) async throws -> Message { diff --git a/telegram/Sources/LandinhoBot/Telegram/TelegramError.swift b/telegram/Sources/LandinhoBot/Telegram/TelegramError.swift new file mode 100644 index 0000000..2873ca0 --- /dev/null +++ b/telegram/Sources/LandinhoBot/Telegram/TelegramError.swift @@ -0,0 +1,12 @@ +// +// TelegramError.swift +// +// +// Created by Mauricio Cardozo on 09/11/23. +// + +import Foundation + +struct TelegramError: Error { + let message: String +} diff --git a/telegram/Sources/LandinhoBot/VroomBot/DefaultVroomBot.swift b/telegram/Sources/LandinhoBot/VroomBot/DefaultVroomBot.swift new file mode 100644 index 0000000..8332aea --- /dev/null +++ b/telegram/Sources/LandinhoBot/VroomBot/DefaultVroomBot.swift @@ -0,0 +1,27 @@ +// +// +// DefaultVroomBot.swift +// +// +// Created by Mauricio Cardozo on 23/10/23. +// + +import Foundation +#if os(Linux) +import FoundationNetworking +#endif +import TelegramBotSDK + +final class DefaultVroomBot: SwiftyBot { + + override init() { + super.init() + update() + } + + override var commands: [Command] { + [ + NextRaceCommand() + ] + } +} diff --git a/telegram/Sources/LandinhoBot/VroomBot/Services/CategoryListCommand.swift b/telegram/Sources/LandinhoBot/VroomBot/Services/CategoryListCommand.swift new file mode 100644 index 0000000..bc314bc --- /dev/null +++ b/telegram/Sources/LandinhoBot/VroomBot/Services/CategoryListCommand.swift @@ -0,0 +1,7 @@ +// +// CategoryListService.swift +// +// +// Created by Mauricio Cardozo on 09/11/23. +// + diff --git a/telegram/Sources/LandinhoBot/VroomBot/Services/HelpCommand.swift b/telegram/Sources/LandinhoBot/VroomBot/Services/HelpCommand.swift new file mode 100644 index 0000000..c89681e --- /dev/null +++ b/telegram/Sources/LandinhoBot/VroomBot/Services/HelpCommand.swift @@ -0,0 +1,14 @@ +// +// HelpCommand.swift +// +// +// Created by Mauricio Cardozo on 09/11/23. +// + +import Foundation + +// TODO: Um comando que apenas explica os outros + +/* \n + Uso: /nextrace [categoria] + Deixe a categoria vazia para a próxima corrida ou passe uma categoria para saber qual é a próxima corrida da categoria escolhida.*/ diff --git a/telegram/Sources/LandinhoBot/DefaultVroomBot.swift b/telegram/Sources/LandinhoBot/VroomBot/Services/NextRaceCommand.swift similarity index 81% rename from telegram/Sources/LandinhoBot/DefaultVroomBot.swift rename to telegram/Sources/LandinhoBot/VroomBot/Services/NextRaceCommand.swift index 458ef0f..a53f34c 100644 --- a/telegram/Sources/LandinhoBot/DefaultVroomBot.swift +++ b/telegram/Sources/LandinhoBot/VroomBot/Services/NextRaceCommand.swift @@ -1,41 +1,26 @@ // +// NextRaceService.swift // -// DefaultVroomBot.swift // -// -// Created by Mauricio Cardozo on 23/10/23. +// Created by Mauricio Cardozo on 09/11/23. // import Foundation #if os(Linux) import FoundationNetworking #endif -import TelegramBotSDK - -final class DefaultVroomBot: SwiftyBot { - override init() { - super.init() - update() - } +struct NextRaceCommand: Command { struct NextRaceResponse: Codable, Equatable { let nextRace: Race? let categoryComment: String } - override var commands: [Command] { - [ - .init( - command: "nextrace", - description: "Shows when the next race will happen", - handler: { [weak self] update in - try await self?.handleNextRace(update: update) - }) - ] - } + let command: String = "nextrace" + let description: String = "Mostra a próxima corrida que irá acontecer" - func handleNextRace(update: ChatUpdate) async throws { + func handle(update: ChatUpdate, bot: Bot, debugMessage: (String) -> Void) async throws { let categoryTag = update.arguments.first ?? "" guard From d73c1e9ab25f5700edf3b601e0548f34d18d76cb Mon Sep 17 00:00:00 2001 From: Mauricio Cardozo Date: Thu, 9 Nov 2023 16:57:34 -0300 Subject: [PATCH 2/3] refactors how the API is accessed, adds some error handling and a help command --- .../LandinhoBot/APIClient/APIClient.swift | 51 +++++++++++++++++++ .../LandinhoBot/SwiftyBot/SwiftyBot.swift | 21 ++++---- .../VroomBot/DefaultVroomBot.swift | 8 ++- .../Services/CategoryListCommand.swift | 15 +++++- .../VroomBot/Services/HelpCommand.swift | 23 +++++++-- .../VroomBot/Services/NextRaceCommand.swift | 47 ++++------------- 6 files changed, 108 insertions(+), 57 deletions(-) create mode 100644 telegram/Sources/LandinhoBot/APIClient/APIClient.swift diff --git a/telegram/Sources/LandinhoBot/APIClient/APIClient.swift b/telegram/Sources/LandinhoBot/APIClient/APIClient.swift new file mode 100644 index 0000000..82bee0a --- /dev/null +++ b/telegram/Sources/LandinhoBot/APIClient/APIClient.swift @@ -0,0 +1,51 @@ +// +// APIClient.swift +// +// +// Created by Mauricio Cardozo on 09/11/23. +// + +import Foundation +#if os(Linux) +import FoundationNetworking +#endif + +struct APIClientError: Error { + let message: String +} + +struct APIClient { + + init(endpoint: String) { + self.endpoint = endpoint + } + + let endpoint: String + + func fetch(arguments: [String: String] = [:]) async throws -> T { + guard let url = buildURL(path: endpoint, args: arguments) else { + throw APIClientError(message: "Couldn't build URL") + } + + let data = try await URLSession.shared.data(from: url) + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + + do { + let response = try decoder.decode(T.self, from: data.0) + return response + } catch (let error) { + throw APIClientError(message: "\(error)") + } + } + + func buildURL(path: String, args: [String: String]) -> URL? { + var components = URLComponents() + components.scheme = "http" + components.host = "localhost" + components.path = "/\(path)" + components.port = 8080 + components.queryItems = args.map { URLQueryItem(name: $0.key, value: $0.value) } + return components.url + } +} diff --git a/telegram/Sources/LandinhoBot/SwiftyBot/SwiftyBot.swift b/telegram/Sources/LandinhoBot/SwiftyBot/SwiftyBot.swift index 8df8635..04d9893 100644 --- a/telegram/Sources/LandinhoBot/SwiftyBot/SwiftyBot.swift +++ b/telegram/Sources/LandinhoBot/SwiftyBot/SwiftyBot.swift @@ -1,7 +1,4 @@ import Foundation -#if os(Linux) -import FoundationNetworking -#endif import TelegramBotSDK open class SwiftyBot { @@ -51,13 +48,17 @@ open class SwiftyBot { firehose(chatUpdate) Task { - try await cmd.handle( - update: chatUpdate, - bot: bot, - debugMessage: { [weak self] str in - self?.debugMessage(str) - } - ) + do { + try await cmd.handle( + update: chatUpdate, + bot: bot, + debugMessage: { [weak self] str in + self?.debugMessage(str) + } + ) + } catch(let error) { + debugMessage("ERROR: \(error)") + } } } } diff --git a/telegram/Sources/LandinhoBot/VroomBot/DefaultVroomBot.swift b/telegram/Sources/LandinhoBot/VroomBot/DefaultVroomBot.swift index 8332aea..75008fa 100644 --- a/telegram/Sources/LandinhoBot/VroomBot/DefaultVroomBot.swift +++ b/telegram/Sources/LandinhoBot/VroomBot/DefaultVroomBot.swift @@ -7,10 +7,6 @@ // import Foundation -#if os(Linux) -import FoundationNetworking -#endif -import TelegramBotSDK final class DefaultVroomBot: SwiftyBot { @@ -21,7 +17,9 @@ final class DefaultVroomBot: SwiftyBot { override var commands: [Command] { [ - NextRaceCommand() + HelpCommand(), + NextRaceCommand(), + CategoryListCommand() ] } } diff --git a/telegram/Sources/LandinhoBot/VroomBot/Services/CategoryListCommand.swift b/telegram/Sources/LandinhoBot/VroomBot/Services/CategoryListCommand.swift index bc314bc..dba8613 100644 --- a/telegram/Sources/LandinhoBot/VroomBot/Services/CategoryListCommand.swift +++ b/telegram/Sources/LandinhoBot/VroomBot/Services/CategoryListCommand.swift @@ -1,7 +1,20 @@ // -// CategoryListService.swift +// CategoryListCommand.swift // // // Created by Mauricio Cardozo on 09/11/23. // +import Foundation + +struct CategoryListCommand: Command { + + let command: String = "categories" + let description: String = "Lista todas as categorias disponíveis" + + func handle(update: ChatUpdate, bot: Bot, debugMessage: (String) -> Void) async throws { + // TODO: bater na API e pegar uma lista de todas as categorias disponíveis + + try await bot.reply(update, text: "TODO") + } +} diff --git a/telegram/Sources/LandinhoBot/VroomBot/Services/HelpCommand.swift b/telegram/Sources/LandinhoBot/VroomBot/Services/HelpCommand.swift index c89681e..f53d266 100644 --- a/telegram/Sources/LandinhoBot/VroomBot/Services/HelpCommand.swift +++ b/telegram/Sources/LandinhoBot/VroomBot/Services/HelpCommand.swift @@ -7,8 +7,23 @@ import Foundation -// TODO: Um comando que apenas explica os outros +struct HelpCommand: Command { + let command: String = "help" + let description: String = "Ajuda para todos os comandos" + func handle(update: ChatUpdate, bot: Bot, debugMessage: (String) -> Void) async throws { + try await bot.reply(update, text: helpText) + } -/* \n - Uso: /nextrace [categoria] - Deixe a categoria vazia para a próxima corrida ou passe uma categoria para saber qual é a próxima corrida da categoria escolhida.*/ + let helpText = """ +Os comandos disponíveis são: + +/help +Lista os comandos de ajuda + +/nextrace [categoria] +Lista a próxima corrida que vai acontecer. Passe uma categoria para que ele liste a próxima corrida de uma categoria específica. + +/categories +Lista as categorias disponíveis +""" +} diff --git a/telegram/Sources/LandinhoBot/VroomBot/Services/NextRaceCommand.swift b/telegram/Sources/LandinhoBot/VroomBot/Services/NextRaceCommand.swift index a53f34c..6fa0fee 100644 --- a/telegram/Sources/LandinhoBot/VroomBot/Services/NextRaceCommand.swift +++ b/telegram/Sources/LandinhoBot/VroomBot/Services/NextRaceCommand.swift @@ -6,53 +6,21 @@ // import Foundation -#if os(Linux) -import FoundationNetworking -#endif struct NextRaceCommand: Command { - struct NextRaceResponse: Codable, Equatable { - let nextRace: Race? - let categoryComment: String - } - let command: String = "nextrace" let description: String = "Mostra a próxima corrida que irá acontecer" + let api = APIClient(endpoint: "next-race") func handle(update: ChatUpdate, bot: Bot, debugMessage: (String) -> Void) async throws { let categoryTag = update.arguments.first ?? "" - - guard - let url = buildURL(path: "next-race", args: ["argument": categoryTag]) - else { - debugMessage("Couldn't build `next-race` URL") + let response = try await api.fetch(arguments: ["argument": categoryTag]) + guard let formattedResponse = formatResponse(response) else { + try await bot.reply(update, text: "Couldn't find next race") return } - - let data = try await URLSession.shared.data(url: url) - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - do { - let response = try decoder.decode(NextRaceResponse.self, from: data) - guard let formattedRace = formatResponse(response) else { - try await bot.reply(update, text: "Couldn't find next race") - return - } - try await bot.reply(update, text: formattedRace) - } catch (let error) { - try await bot.reply(update, text: "\(error)") - } - } - - func buildURL(path: String, args: [String: String]) -> URL? { - var components = URLComponents() - components.scheme = "http" - components.host = "localhost" - components.path = "/\(path)" - components.port = 8080 - components.queryItems = args.map { URLQueryItem(name: $0.key, value: $0.value) } - return components.url + try await bot.reply(update, text: formattedResponse) } func formatResponse(_ response: NextRaceResponse) -> String? { @@ -99,4 +67,9 @@ struct NextRaceCommand: Command { f.dateFormat = "dd/MM 'as' HH:mm" return f }() + + struct NextRaceResponse: Codable, Equatable { + let nextRace: Race? + let categoryComment: String + } } From aa86036272d96a5ab4c52d1f879ff99768fb15e9 Mon Sep 17 00:00:00 2001 From: Mauricio Cardozo Date: Thu, 9 Nov 2023 17:25:56 -0300 Subject: [PATCH 3/3] localizes messages, adds command to list all available categories --- .../LandinhoBot/APIClient/APIClient.swift | 16 ++++++++++++-- .../Sources/LandinhoBot/Models/Race.swift | 2 +- .../LandinhoBot/SwiftyBot/SwiftyBot.swift | 4 ++-- .../Telegram/TelegramBot+Bot.swift | 5 +++-- .../LandinhoBot/Telegram/TelegramError.swift | 4 +++- .../Services/CategoryListCommand.swift | 21 +++++++++++++++++-- .../VroomBot/Services/NextRaceCommand.swift | 2 +- 7 files changed, 43 insertions(+), 11 deletions(-) diff --git a/telegram/Sources/LandinhoBot/APIClient/APIClient.swift b/telegram/Sources/LandinhoBot/APIClient/APIClient.swift index 82bee0a..93bf9a0 100644 --- a/telegram/Sources/LandinhoBot/APIClient/APIClient.swift +++ b/telegram/Sources/LandinhoBot/APIClient/APIClient.swift @@ -10,8 +10,12 @@ import Foundation import FoundationNetworking #endif -struct APIClientError: Error { +struct APIClientError: LocalizedError { let message: String + + var errorDescription: String? { + message + } } struct APIClient { @@ -35,7 +39,15 @@ struct APIClient { let response = try decoder.decode(T.self, from: data.0) return response } catch (let error) { - throw APIClientError(message: "\(error)") + let result = String(data: data.0, encoding: .utf8) ?? "Couldn't decode JSON" + throw APIClientError(message: """ + + \(error) + + –––– + + \(result) + """) } } diff --git a/telegram/Sources/LandinhoBot/Models/Race.swift b/telegram/Sources/LandinhoBot/Models/Race.swift index f6f5544..d4dc207 100644 --- a/telegram/Sources/LandinhoBot/Models/Race.swift +++ b/telegram/Sources/LandinhoBot/Models/Race.swift @@ -11,7 +11,7 @@ struct Category: Codable { let title: String let tag: String let comment: String - let races: [Race] + let races: [Race]? } struct Race: Codable, Equatable { diff --git a/telegram/Sources/LandinhoBot/SwiftyBot/SwiftyBot.swift b/telegram/Sources/LandinhoBot/SwiftyBot/SwiftyBot.swift index 04d9893..49e4629 100644 --- a/telegram/Sources/LandinhoBot/SwiftyBot/SwiftyBot.swift +++ b/telegram/Sources/LandinhoBot/SwiftyBot/SwiftyBot.swift @@ -57,7 +57,7 @@ open class SwiftyBot { } ) } catch(let error) { - debugMessage("ERROR: \(error)") + debugMessage(error.localizedDescription) } } } @@ -66,7 +66,7 @@ open class SwiftyBot { private func firehose(_ update: ChatUpdate) { debugMessage( """ - \(update.command) called by \(update.chatID) - \(update.chatName) + \(update.command) called by \(update.chatID) – \(update.chatName) """, currentUpdate: update) } diff --git a/telegram/Sources/LandinhoBot/Telegram/TelegramBot+Bot.swift b/telegram/Sources/LandinhoBot/Telegram/TelegramBot+Bot.swift index 1fe0c9f..b1cfd80 100644 --- a/telegram/Sources/LandinhoBot/Telegram/TelegramBot+Bot.swift +++ b/telegram/Sources/LandinhoBot/Telegram/TelegramBot+Bot.swift @@ -1,6 +1,6 @@ // // Telegram+Bot.swift -// +// // // Created by Mauricio Cardozo on 09/11/23. // @@ -15,6 +15,7 @@ extension TelegramBot: Bot { } try await sendMessageAsync( chatId: .chat(chatID), - text: text) + text: text, + parseMode: .markdownv2) } } diff --git a/telegram/Sources/LandinhoBot/Telegram/TelegramError.swift b/telegram/Sources/LandinhoBot/Telegram/TelegramError.swift index 2873ca0..fdb58c7 100644 --- a/telegram/Sources/LandinhoBot/Telegram/TelegramError.swift +++ b/telegram/Sources/LandinhoBot/Telegram/TelegramError.swift @@ -7,6 +7,8 @@ import Foundation -struct TelegramError: Error { +struct TelegramError: LocalizedError { let message: String + + var errorDescription: String? { message } } diff --git a/telegram/Sources/LandinhoBot/VroomBot/Services/CategoryListCommand.swift b/telegram/Sources/LandinhoBot/VroomBot/Services/CategoryListCommand.swift index dba8613..d0165a0 100644 --- a/telegram/Sources/LandinhoBot/VroomBot/Services/CategoryListCommand.swift +++ b/telegram/Sources/LandinhoBot/VroomBot/Services/CategoryListCommand.swift @@ -11,10 +11,27 @@ struct CategoryListCommand: Command { let command: String = "categories" let description: String = "Lista todas as categorias disponíveis" + let api = APIClient<[Category]>(endpoint: "category") func handle(update: ChatUpdate, bot: Bot, debugMessage: (String) -> Void) async throws { - // TODO: bater na API e pegar uma lista de todas as categorias disponíveis + let result = try await api.fetch() + let formattedResult = formatCategories(result) + try await bot.reply(update, text: formattedResult) + } + + func formatCategories(_ categories: [Category]) -> String { + guard !categories.isEmpty else { + return "Não encontrei nenhuma categoria" + } + + let categoryList = categories + .map { "`\($0.tag)` – \($0.title)" } + .joined(separator: "\n") + + return """ + As categorias disponíveis são: - try await bot.reply(update, text: "TODO") + \(categoryList) + """ } } diff --git a/telegram/Sources/LandinhoBot/VroomBot/Services/NextRaceCommand.swift b/telegram/Sources/LandinhoBot/VroomBot/Services/NextRaceCommand.swift index 6fa0fee..b091491 100644 --- a/telegram/Sources/LandinhoBot/VroomBot/Services/NextRaceCommand.swift +++ b/telegram/Sources/LandinhoBot/VroomBot/Services/NextRaceCommand.swift @@ -17,7 +17,7 @@ struct NextRaceCommand: Command { let categoryTag = update.arguments.first ?? "" let response = try await api.fetch(arguments: ["argument": categoryTag]) guard let formattedResponse = formatResponse(response) else { - try await bot.reply(update, text: "Couldn't find next race") + try await bot.reply(update, text: "Não encontrei a próxima corrida") return } try await bot.reply(update, text: formattedResponse)