init: start bot write of swift

This commit is contained in:
Konrad Geletey 2024-10-19 17:39:04 +03:00
commit 77672b5c2a
Signed by: kglt
GPG key ID: 386DEE24B60BD996
11 changed files with 465 additions and 0 deletions

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc

7
LICENSE Normal file
View file

@ -0,0 +1,7 @@
ISC License
Copyright 2024 Oleh Nersh and Konrad Geletey
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

231
Package.resolved Normal file
View file

@ -0,0 +1,231 @@
{
"originHash" : "5b140fcd6a36165fb4c1302609ef5353bacb59ce0f82996aa0cf66ee021cbb17",
"pins" : [
{
"identity" : "async-http-client",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swift-server/async-http-client.git",
"state" : {
"revision" : "0a9b72369b9d87ab155ef585ef50700a34abf070",
"version" : "1.23.1"
}
},
{
"identity" : "async-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/async-kit.git",
"state" : {
"revision" : "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31",
"version" : "1.20.0"
}
},
{
"identity" : "console-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/console-kit.git",
"state" : {
"revision" : "78c0dd739df8cb9ee14a8bbbf770facc4fc3402a",
"version" : "4.15.0"
}
},
{
"identity" : "multipart-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/multipart-kit.git",
"state" : {
"revision" : "a31236f24bfd2ea2f520a74575881f6731d7ae68",
"version" : "4.7.0"
}
},
{
"identity" : "routing-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/routing-kit.git",
"state" : {
"revision" : "8c9a227476555c55837e569be71944e02a056b72",
"version" : "4.9.1"
}
},
{
"identity" : "swift-algorithms",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-algorithms.git",
"state" : {
"revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42",
"version" : "1.2.0"
}
},
{
"identity" : "swift-asn1",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-asn1.git",
"state" : {
"revision" : "7faebca1ea4f9aaf0cda1cef7c43aecd2311ddf6",
"version" : "1.3.0"
}
},
{
"identity" : "swift-atomics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-atomics.git",
"state" : {
"revision" : "cd142fd2f64be2100422d658e7411e39489da985",
"version" : "1.2.0"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "671108c96644956dddcd89dd59c203dcdb36cec7",
"version" : "1.1.4"
}
},
{
"identity" : "swift-crypto",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-crypto.git",
"state" : {
"revision" : "21f7878f2b39d46fd8ba2b06459ccb431cdf876c",
"version" : "3.8.1"
}
},
{
"identity" : "swift-custom-logger",
"kind" : "remoteSourceControl",
"location" : "https://github.com/nerzh/swift-custom-logger",
"state" : {
"revision" : "b76cd97634c3d23348f041ca161187dac8bf6fe3",
"version" : "1.1.0"
}
},
{
"identity" : "swift-http-types",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-http-types",
"state" : {
"revision" : "ae67c8178eb46944fd85e4dc6dd970e1f3ed6ccd",
"version" : "1.3.0"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log",
"state" : {
"revision" : "9cb486020ebf03bfa5b5df985387a14a98744537",
"version" : "1.6.1"
}
},
{
"identity" : "swift-metrics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-metrics.git",
"state" : {
"revision" : "e0165b53d49b413dd987526b641e05e246782685",
"version" : "2.5.0"
}
},
{
"identity" : "swift-nio",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
"revision" : "f7dc3f527576c398709b017584392fb58592e7f5",
"version" : "2.75.0"
}
},
{
"identity" : "swift-nio-extras",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-extras.git",
"state" : {
"revision" : "2e9746cfc57554f70b650b021b6ae4738abef3e6",
"version" : "1.24.1"
}
},
{
"identity" : "swift-nio-http2",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-http2.git",
"state" : {
"revision" : "eaa71bb6ae082eee5a07407b1ad0cbd8f48f9dca",
"version" : "1.34.1"
}
},
{
"identity" : "swift-nio-ssl",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-ssl.git",
"state" : {
"revision" : "d7ceaf0e4d8001cd35cdc12e42cdd281e9e564e8",
"version" : "2.28.0"
}
},
{
"identity" : "swift-nio-transport-services",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-transport-services.git",
"state" : {
"revision" : "dbace16f126fdcd80d58dc54526c561ca17327d7",
"version" : "1.22.0"
}
},
{
"identity" : "swift-numerics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-numerics.git",
"state" : {
"revision" : "0a5bc04095a675662cf24757cc0640aa2204253b",
"version" : "1.0.2"
}
},
{
"identity" : "swift-regular-expression",
"kind" : "remoteSourceControl",
"location" : "https://github.com/nerzh/swift-regular-expression",
"state" : {
"revision" : "19c3e569a8e81da6f7f4ee3b73028c25737d3706",
"version" : "0.2.4"
}
},
{
"identity" : "swift-system",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-system.git",
"state" : {
"revision" : "c8a44d836fe7913603e246acab7c528c2e780168",
"version" : "1.4.0"
}
},
{
"identity" : "swift-telegram-sdk",
"kind" : "remoteSourceControl",
"location" : "https://github.com/nerzh/swift-telegram-sdk",
"state" : {
"revision" : "1be977abc50e9e6aed5a60f1d1d27e25e635da9b",
"version" : "3.6.0"
}
},
{
"identity" : "vapor",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/vapor.git",
"state" : {
"revision" : "1466c50e4ad39072143e2fcdf13b4ba11be375a0",
"version" : "4.106.0"
}
},
{
"identity" : "websocket-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/websocket-kit.git",
"state" : {
"revision" : "4232d34efa49f633ba61afde365d3896fc7f8740",
"version" : "2.15.0"
}
}
],
"version" : 3
}

29
Package.swift Normal file
View file

@ -0,0 +1,29 @@
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
var packageDependencies: [Package.Dependency] = [
.package(url: "https://github.com/vapor/vapor.git", .upToNextMajor(from: "4.57.0")),
]
var targetDependencies: [PackageDescription.Target.Dependency] = [
.product(name: "Vapor", package: "vapor"),
]
packageDependencies.append(.package(url: "https://github.com/nerzh/swift-telegram-sdk", .upToNextMajor(from: "3.0.3")))
targetDependencies.append(.product(name: "SwiftTelegramSdk", package: "swift-telegram-sdk"))
let package = Package(
name: "TelegramModeratorBot",
dependencies: packageDependencies,
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.executableTarget(
name: "TelegramModeratorBot",
dependencies: targetDependencies
),
]
)

View file

@ -0,0 +1,18 @@
import Vapor
import SwiftTelegramSdk
final class DefaultBotHandlers {
static func addHandlers() async {
await defaultBaseHandler()
}
private static func defaultBaseHandler() async {
await botActor.bot.dispatcher.add(TGBaseHandler({ update in
guard let message = update.message else { return }
let params: TGSendMessageParams = .init(chatId: .chat(message.chat.id), text: """
Help message
""")
try await botActor.bot.sendMessage(params: params)
}))
}
}

View file

@ -0,0 +1,13 @@
import SwiftTelegramSdk
actor TGBotActor {
private var _bot: TGBot!
var bot: TGBot {
self._bot
}
func setBot(_ bot: TGBot) {
self._bot = bot
}
}

View file

@ -0,0 +1,110 @@
//
// VaporTGClient.swift
// Vapor-telegram-bot-example
//
// Created by Oleh Hudeichuk on 01.07.2024.
//
import Foundation
import Vapor
import SwiftTelegramSdk
public enum TGHTTPMediaType: String, Equatable {
case formData
case json
}
private struct TGEmptyParams: Encodable {}
public final class VaporTGClient: TGClientPrtcl {
public typealias HTTPMediaType = SwiftTelegramSdk.HTTPMediaType
public var log: Logging.Logger = .init(label: "VaporTGClient")
private let client: Vapor.Client
public init(client: Vapor.Client) {
self.client = client
}
@discardableResult
public func post<Params: Encodable, Response: Decodable>
(
_ url: URL,
params: Params? = nil,
as mediaType: HTTPMediaType? = nil
) async throws -> Response {
let clientResponse: ClientResponse = try await client.post(URI(string: url.absoluteString), headers: HTTPHeaders()) { clientRequest in
if mediaType == .formData || mediaType == nil {
#warning("THIS CODE FOR FAST FIX, BECAUSE https://github.com/vapor/multipart-kit/issues/63 not accepted yet")
var rawMultipart: (body: NSMutableData, boundary: String)!
do {
/// Content-Disposition: form-data; name="nested_object"
///
/// { json string }
if let currentParams: Params = params {
rawMultipart = try currentParams.toMultiPartFormData(log: log)
} else {
rawMultipart = try TGEmptyParams().toMultiPartFormData(log: log)
}
} catch {
log.critical("Post request error: \(error.logMessage)")
}
clientRequest.headers.add(name: "Content-Type", value: "multipart/form-data; boundary=\(rawMultipart.boundary)")
let buffer = ByteBuffer.init(data: rawMultipart.body as Data)
clientRequest.body = buffer
/// Debug
// TGBot.log.critical("url: \(url)\n\(String(decoding: rawMultipart.body, as: UTF8.self))")
} else {
let mediaType: Vapor.HTTPMediaType = if let mediaType {
.init(type: mediaType.type, subType: mediaType.subType, parameters: mediaType.parameters)
} else {
.json
}
try clientRequest.content.encode(params ?? (TGEmptyParams() as! Params), as: mediaType)
}
}
let telegramContainer: TGTelegramContainer = try clientResponse.content.decode(TGTelegramContainer<Response>.self)
return try processContainer(telegramContainer)
}
@discardableResult
public func post<Response: Decodable>(_ url: URL) async throws -> Response {
try await post(url, params: TGEmptyParams(), as: nil)
}
private func processContainer<T: Decodable>(_ container: TGTelegramContainer<T>) throws -> T {
guard container.ok else {
let desc = """
Response marked as `not Ok`, it seems something wrong with request
Code: \(container.errorCode ?? -1)
\(container.description ?? "Empty")
"""
let error = BotError(
type: .server,
description: desc
)
log.error(error.logMessage)
throw error
}
guard let result = container.result else {
let error = BotError(
type: .server,
reason: "Response marked as `Ok`, but doesn't contain `result` field."
)
log.error(error.logMessage)
throw error
}
let logString = """
Response:
Code: \(container.errorCode ?? 0)
Status OK: \(container.ok)
Description: \(container.description ?? "Empty")
"""
log.trace(logString.logMessage)
return result
}
}

View file

@ -0,0 +1,20 @@
import Foundation
import Vapor
import SwiftTelegramSdk
public func configure(_ app: Application) async throws {
guard let tgApi: String = Environment.get("TG_BOT_API") else { throw Errors.notVariable("Telegram key is not defined")}
app.logger.logLevel = .debug
let bot: TGBot = try await .init(connectionType: .longpolling(limit: nil,
timeout: nil,
allowedUpdates: nil),
dispatcher: nil,
tgClient: VaporTGClient(client: app.client),
tgURI: TGBot.standardTGURL,
botId: tgApi,
log: app.logger)
await botActor.setBot(bot)
await DefaultBotHandlers.addHandlers()
try await botActor.bot.start()
// try routes(app)
}

View file

@ -0,0 +1,3 @@
enum Errors: Error {
case notVariable(String)
}

View file

@ -0,0 +1,18 @@
// The Swift Programming Language
// https://docs.swift.org/swift-book
import Vapor
import SwiftTelegramSdk
var env = try Environment.detect()
try LoggingSystem.bootstrap(from: &env)
let eventLoop: EventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount * 4)
let app: Application = try await Application.make(env, Application.EventLoopGroupProvider.shared(eventLoop))
let botActor: TGBotActor = .init()
defer { app.shutdown() }
try await configure(app)
try await app.execute()

View file

@ -0,0 +1,8 @@
/*
import Vapor
func routes(_ app: Application) throws {
try app.register(collection: TelegramController())
}
*/