remove DiscordBM dependency

This commit is contained in:
2026-03-17 06:33:26 -04:00
parent c6b92d968a
commit 29a2b0370b
9 changed files with 361 additions and 370 deletions

View File

@@ -1,5 +1,5 @@
{
"originHash" : "8fae71593cfeba2ef52421241b82a32253b5dfcaf81f7533c3f5b0f981822d3a",
"originHash" : "219f2158581681a4b5edbd171ed8aebcd120092f145806d33067fb0cfbb93848",
"pins" : [
{
"identity" : "async-http-client",

View File

@@ -17,7 +17,6 @@ let package = Package(
.executableTarget(
name: "zundamon",
dependencies: [
.product(name: "DiscordBM", package: "discordbm"),
.product(name: "SwiftDotenv", package: "swift-dotenv"),
.product(name: "XMLCoder", package: "xmlcoder"),
.target(name: "DiscordKit")

View File

@@ -11,9 +11,7 @@ public struct Bot {
let gatewayURL = try await client.getGatewayURL()
print(gatewayURL.absoluteURL)
let gateway = GatewayClient(gatewayURL: gatewayURL)
try await gateway.openConnection()
}
}

View File

@@ -3,40 +3,30 @@ import Foundation
import FoundationNetworking
#endif
struct GatewayClient {
let gatewayURL: URL
class GatewayClient {
let ws: URLSessionWebSocketTask
var sequenceNum: Int? = nil
init(gatewayURL: URL) {
self.gatewayURL = gatewayURL
ws = URLSession.shared.webSocketTask(with: gatewayURL.appending(component: "?v=10&encoding=json"))
}
func openConnection() {
listen()
func openConnection() async throws {
ws.resume()
RunLoop.current.run()
guard case .hello(let helloMessage) = try await getMessage().d else { throw GatewayError.mismatchedOpcode }
dump(helloMessage)
Timer.scheduledTimer(withTimeInterval: (helloMessage.heartbeat_interval / 1000.0), repeats: true) {
}
}
func listen() {
ws.receive { result in
defer { listen() }
do {
if case .failure(let err) = result { throw err }
guard case .success(let message) = result else { return }
guard case .string(let str) = message else { throw GatewayError.invalidMessage }
let json = JSONDecoder()
let gwMessage = try json.decode(GatewayMessage.self, from: Data(str.utf8))
switch gwMessage.d {
case .hello(let hello):
dump(hello)
case .none:
return
}
} catch {
print(error)
}
}
func getMessage() async throws -> GatewayMessage {
let wsMessage = try await ws.receive()
guard case .string(let str) = wsMessage else { throw GatewayError.invalidMessage }
let json = JSONDecoder()
let gwMessage = try json.decode(GatewayMessage.self, from: Data(str.utf8))
sequenceNum = gwMessage.s ?? sequenceNum
return gwMessage
}
}
@@ -44,4 +34,5 @@ struct GatewayClient {
public enum GatewayError: Error {
case invalidMessage
case invalidOpcode
case mismatchedOpcode
}

View File

@@ -23,6 +23,8 @@ public enum GatewayPayload: Decodable {
public struct GatewayMessage: Decodable {
let op: Int
let d: GatewayPayload?
let s: Int?
let t: String?
enum CodingKeys: String, CodingKey {
case t
@@ -34,6 +36,8 @@ public struct GatewayMessage: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
op = try container.decode(Int.self, forKey: .op)
s = try container.decode(Int?.self, forKey: .s)
t = try container.decode(String?.self, forKey: .t)
switch op {
case 10:
let hello = try container.decode(HelloPayload.self, forKey: .d)

View File

@@ -1,107 +1,107 @@
import DiscordBM
struct Actions {
static func getUserFromMention(_ mention: MentionUser) -> String {
mention.member?.nick ?? mention.global_name ?? mention.username
}
static func performAction(ctx: Gateway.MessageCreate, client: DiscordClient, resOpts: [String]) async throws {
let author = "**\(ctx.member?.nick ?? ctx.author?.global_name ?? ctx.author?.username ?? "Zundamon")**"
let dests = ctx.mentions.map(getUserFromMention).map({ "**\($0)**" })
let orig: String
let dest: String
if let firstDest = dests.first {
orig = "\(author)"
dest = "\(firstDest)"
} else {
orig = "**Zundamon**"
dest = "\(author)"
}
guard let res = resOpts.randomElement()?
.replacingOccurrences(of: "{orig}", with: orig)
.replacingOccurrences(of: "{dest}", with: dest)
else { print("retOptions empty"); return }
let retMsg = res
try await client.createMessage(
channelId: ctx.channel_id,
payload: .init(
embeds: [.init(description: retMsg)],
)
).guardSuccess()
}
static let hugRes = (try? String(contentsOfFile: "resources/choices/hug.txt", encoding: .utf8))?
.split(separator: "\n")
.map({ $0.trimmingCharacters(in: .whitespacesAndNewlines) })
static func hug(
_ args: ArraySlice<String.SubSequence>,
client: DiscordClient,
ctx: Gateway.MessageCreate
) async throws {
guard let hugRes = hugRes else { print("hug.txt not loaded"); return }
let author = "**\(ctx.member?.nick ?? ctx.author?.global_name ?? ctx.author?.username ?? "Zundamon")**"
let dests = ctx.mentions.map(getUserFromMention).map({ "**\($0)**" })
let retMsg: String
if dests.count > 1 {
let groupHugs = [
"{subjects} all huddled together.",
"{subjects} hugged each other pairwise, generating a total of **{total}** hugs.",
"{subjects} hugged each other at the same time in the same place (although I'm not sure how that works with the current understanding of spacetime).",
]
let group = [author] + dests
let total = String(group.count)
let subjects = String(group.joined(by: ", "))
guard let res = groupHugs.randomElement()?
.replacingOccurrences(of: "{subjects}", with: subjects)
.replacingOccurrences(of: "{total}", with: total)
else { print("groupHugs.randomElement() returned null"); return }
retMsg = res
} else {
let orig: String
let dest: String
if let firstDest = dests.first {
orig = "\(author)"
dest = "\(firstDest)"
} else {
orig = "**Zundamon**"
dest = "\(author)"
}
guard let res = hugRes.randomElement()?
.replacingOccurrences(of: "{orig}", with: orig)
.replacingOccurrences(of: "{dest}", with: dest)
else { print("hug.txt empty"); return }
retMsg = res
}
try await client.createMessage(
channelId: ctx.channel_id,
payload: .init(
embeds: [.init(description: retMsg)],
)
).guardSuccess()
}
static let patRes = (try? String(contentsOfFile: "resources/choices/pat.txt", encoding: .utf8))?
.split(separator: "\n")
.map({ $0.trimmingCharacters(in: .whitespacesAndNewlines) })
static func pat(
_ args: ArraySlice<String.SubSequence>,
client: DiscordClient,
ctx: Gateway.MessageCreate
) async throws {
guard let patRes = patRes else { print("pat.txt not loaded"); return }
try await performAction(ctx: ctx, client: client, resOpts: patRes)
}
}
// import DiscordBM
//
// struct Actions {
// static func getUserFromMention(_ mention: MentionUser) -> String {
// mention.member?.nick ?? mention.global_name ?? mention.username
// }
//
// static func performAction(ctx: Gateway.MessageCreate, client: DiscordClient, resOpts: [String]) async throws {
// let author = "**\(ctx.member?.nick ?? ctx.author?.global_name ?? ctx.author?.username ?? "Zundamon")**"
// let dests = ctx.mentions.map(getUserFromMention).map({ "**\($0)**" })
// let orig: String
// let dest: String
// if let firstDest = dests.first {
// orig = "\(author)"
// dest = "\(firstDest)"
// } else {
// orig = "**Zundamon**"
// dest = "\(author)"
// }
//
// guard let res = resOpts.randomElement()?
// .replacingOccurrences(of: "{orig}", with: orig)
// .replacingOccurrences(of: "{dest}", with: dest)
// else { print("retOptions empty"); return }
//
// let retMsg = res
//
// try await client.createMessage(
// channelId: ctx.channel_id,
// payload: .init(
// embeds: [.init(description: retMsg)],
// )
// ).guardSuccess()
// }
//
// static let hugRes = (try? String(contentsOfFile: "resources/choices/hug.txt", encoding: .utf8))?
// .split(separator: "\n")
// .map({ $0.trimmingCharacters(in: .whitespacesAndNewlines) })
//
// static func hug(
// _ args: ArraySlice<String.SubSequence>,
// client: DiscordClient,
// ctx: Gateway.MessageCreate
// ) async throws {
// guard let hugRes = hugRes else { print("hug.txt not loaded"); return }
// let author = "**\(ctx.member?.nick ?? ctx.author?.global_name ?? ctx.author?.username ?? "Zundamon")**"
// let dests = ctx.mentions.map(getUserFromMention).map({ "**\($0)**" })
//
// let retMsg: String
//
// if dests.count > 1 {
// let groupHugs = [
// "{subjects} all huddled together.",
// "{subjects} hugged each other pairwise, generating a total of **{total}** hugs.",
// "{subjects} hugged each other at the same time in the same place (although I'm not sure how that works with the current understanding of spacetime).",
// ]
//
// let group = [author] + dests
// let total = String(group.count)
// let subjects = String(group.joined(by: ", "))
//
// guard let res = groupHugs.randomElement()?
// .replacingOccurrences(of: "{subjects}", with: subjects)
// .replacingOccurrences(of: "{total}", with: total)
// else { print("groupHugs.randomElement() returned null"); return }
//
// retMsg = res
// } else {
// let orig: String
// let dest: String
// if let firstDest = dests.first {
// orig = "\(author)"
// dest = "\(firstDest)"
// } else {
// orig = "**Zundamon**"
// dest = "\(author)"
// }
//
// guard let res = hugRes.randomElement()?
// .replacingOccurrences(of: "{orig}", with: orig)
// .replacingOccurrences(of: "{dest}", with: dest)
// else { print("hug.txt empty"); return }
//
// retMsg = res
// }
//
// try await client.createMessage(
// channelId: ctx.channel_id,
// payload: .init(
// embeds: [.init(description: retMsg)],
// )
// ).guardSuccess()
// }
//
// static let patRes = (try? String(contentsOfFile: "resources/choices/pat.txt", encoding: .utf8))?
// .split(separator: "\n")
// .map({ $0.trimmingCharacters(in: .whitespacesAndNewlines) })
//
// static func pat(
// _ args: ArraySlice<String.SubSequence>,
// client: DiscordClient,
// ctx: Gateway.MessageCreate
// ) async throws {
// guard let patRes = patRes else { print("pat.txt not loaded"); return }
// try await performAction(ctx: ctx, client: client, resOpts: patRes)
// }
// }

View File

@@ -1,116 +1,116 @@
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
import DiscordBM
struct MessageHandler {
let ctx: Gateway.MessageCreate
let client: any DiscordClient
static let prefix = ":"
static let zundaGifData = try? Data(contentsOf: URL(filePath: "resources/media/zundamone.gif"))
func handle() async throws {
guard !(ctx.author?.bot ?? false) else { return }
if (ctx.content.hasPrefix(MessageHandler.prefix)) {
let split = ctx.content.split(separator: " ")
let command = split.first?.trimmingPrefix(MessageHandler.prefix)
let args = split[1...]
switch command {
case "wow": try await handleWow(args)
case "domath": try await Wolfram.handleMath(args, client: client, ctx: ctx)
case "hug": try await Actions.hug(args, client: client, ctx: ctx)
case "pat": try await Actions.pat(args, client: client, ctx: ctx)
case "pet": try await Actions.pat(args, client: client, ctx: ctx)
default: break
}
} else if ctx.mentions.contains(where: { $0.id == Zundamon.ownID }) {
if ctx.content
.replacingOccurrences(of: "<@\(Zundamon.ownID!.rawValue)>", with: "")
.trimmingCharacters(in: .whitespacesAndNewlines)
.count == 0,
let zundaGif = MessageHandler.zundaGifData
{
try await client.createMessage(
channelId: ctx.channel_id,
payload: .init(
message_reference: .init(
type: .default,
message_id: ctx.id,
channel_id: ctx.channel_id,
guild_id: ctx.guild_id,
),
files: [.init(data: .init(data: zundaGif), filename: "zundamone.gif")],
attachments: [.init(index: 0, filename: "zundamone.gif")],
)
).guardSuccess()
} else {
try await handle8Ball()
}
}
}
static let ballResponses = [
"It is certain.",
"It is decidedly so.",
"Without a doubt.",
"Yes definitely.",
"You may rely on it.",
"As I see it, yes.",
"Most likely.",
"Outlook good.",
"Yes.",
"Signs point to yes.",
"Reply hazy, try again.",
"Ask again later.",
"Better not tell you now.",
"Cannot predict now.",
"Concentrate and ask again.",
"Dont count on it.",
"My reply is no.",
"My sources say no.",
"Outlook not so good.",
"Very doubtful.",
"Ui beam",
"We are Shigure Ui",
"We are Shigure Ux",
]
func handle8Ball() async throws {
try await client.createMessage(
channelId: ctx.channel_id,
payload: .init(
content: MessageHandler.ballResponses.randomElement(),
message_reference: .init(
type: .default,
message_id: ctx.id,
channel_id: ctx.channel_id,
guild_id: ctx.guild_id,
),
)
).guardSuccess()
}
static let wows = [
"<:wow:1477062414913634334>",
"<:wow2:1477062432357875948>",
"<:wow4:1477062471746588713>",
"<:wow5:1477062452804849845>"
]
func handleWow(_ args: ArraySlice<String.SubSequence>) async throws {
try await client.createMessage(
channelId: ctx.channel_id,
payload: .init(
content: MessageHandler.wows.randomElement(),
message_reference: .init(
type: .default,
message_id: ctx.id,
channel_id: ctx.channel_id,
guild_id: ctx.guild_id,
),
)
).guardSuccess()
}
}
// import Foundation
// #if canImport(FoundationNetworking)
// import FoundationNetworking
// #endif
// import DiscordBM
//
// struct MessageHandler {
// let ctx: Gateway.MessageCreate
// let client: any DiscordClient
//
// static let prefix = ":"
// static let zundaGifData = try? Data(contentsOf: URL(filePath: "resources/media/zundamone.gif"))
//
// func handle() async throws {
// guard !(ctx.author?.bot ?? false) else { return }
// if (ctx.content.hasPrefix(MessageHandler.prefix)) {
// let split = ctx.content.split(separator: " ")
// let command = split.first?.trimmingPrefix(MessageHandler.prefix)
// let args = split[1...]
//
// switch command {
// case "wow": try await handleWow(args)
// case "domath": try await Wolfram.handleMath(args, client: client, ctx: ctx)
// case "hug": try await Actions.hug(args, client: client, ctx: ctx)
// case "pat": try await Actions.pat(args, client: client, ctx: ctx)
// case "pet": try await Actions.pat(args, client: client, ctx: ctx)
// default: break
// }
// } else if ctx.mentions.contains(where: { $0.id == Zundamon.ownID }) {
// if ctx.content
// .replacingOccurrences(of: "<@\(Zundamon.ownID!.rawValue)>", with: "")
// .trimmingCharacters(in: .whitespacesAndNewlines)
// .count == 0,
// let zundaGif = MessageHandler.zundaGifData
// {
// try await client.createMessage(
// channelId: ctx.channel_id,
// payload: .init(
// message_reference: .init(
// type: .default,
// message_id: ctx.id,
// channel_id: ctx.channel_id,
// guild_id: ctx.guild_id,
// ),
// files: [.init(data: .init(data: zundaGif), filename: "zundamone.gif")],
// attachments: [.init(index: 0, filename: "zundamone.gif")],
// )
// ).guardSuccess()
// } else {
// try await handle8Ball()
// }
// }
// }
//
// static let ballResponses = [
// "It is certain.",
// "It is decidedly so.",
// "Without a doubt.",
// "Yes definitely.",
// "You may rely on it.",
// "As I see it, yes.",
// "Most likely.",
// "Outlook good.",
// "Yes.",
// "Signs point to yes.",
// "Reply hazy, try again.",
// "Ask again later.",
// "Better not tell you now.",
// "Cannot predict now.",
// "Concentrate and ask again.",
// "Dont count on it.",
// "My reply is no.",
// "My sources say no.",
// "Outlook not so good.",
// "Very doubtful.",
// "Ui beam",
// "We are Shigure Ui",
// "We are Shigure Ux",
// ]
// func handle8Ball() async throws {
// try await client.createMessage(
// channelId: ctx.channel_id,
// payload: .init(
// content: MessageHandler.ballResponses.randomElement(),
// message_reference: .init(
// type: .default,
// message_id: ctx.id,
// channel_id: ctx.channel_id,
// guild_id: ctx.guild_id,
// ),
// )
// ).guardSuccess()
// }
//
// static let wows = [
// "<:wow:1477062414913634334>",
// "<:wow2:1477062432357875948>",
// "<:wow4:1477062471746588713>",
// "<:wow5:1477062452804849845>"
// ]
// func handleWow(_ args: ArraySlice<String.SubSequence>) async throws {
// try await client.createMessage(
// channelId: ctx.channel_id,
// payload: .init(
// content: MessageHandler.wows.randomElement(),
// message_reference: .init(
// type: .default,
// message_id: ctx.id,
// channel_id: ctx.channel_id,
// guild_id: ctx.guild_id,
// ),
// )
// ).guardSuccess()
// }
//
// }

View File

@@ -1,107 +1,107 @@
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
import XMLCoder
import DiscordBM
struct Wolfram {
static let token = ProcessInfo.processInfo.environment["WOLFRAM_APP_ID"]!
static let apiUrl = "http://api.wolframalpha.com/v2/query"
static func getWolfram(_ question: String) async throws -> (String, URL?) {
let encQuestion = question.addingPercentEncoding(withAllowedCharacters: .alphanumerics)
let url = URL(string: "\(apiUrl)?appid=\(token)&input=\(encQuestion!)")!
let (data, _) = try await URLSession.shared.data(from: url)
let wolframRes = try XMLDecoder().decode(WolframQueryResult.self, from: data)
let resultPod = wolframRes.pod.first(where: { $0.primary ?? false || $0.id == "Result" })
var ans: String?
var img: URL?
if let resultPod = resultPod {
ans = String(resultPod.subpod.compactMap(\.plaintext).joined(by: "\n"))
.replacingOccurrences(of: " | ", with: ": ")
}
if let imgPod = wolframRes.pod.first(where: {
["RootPlot", "NumberLine", "Plot", "ImplicitPlot", "3DPlot"].contains($0.id) ||
($0.id == "Example" && $0.scanner == "Dice")
}) {
ans = ans ?? "Plot:"
img = imgPod.subpod.first?.img.src
}
if ans == nil,
let maybePod = wolframRes.pod.first(where: { $0.title == "Input interpretation" }),
let maybeText = maybePod.subpod.first?.plaintext
.replacingOccurrences(of: " | ", with: ": ") {
ans = "<:smol_rise:852763040452575252> I don't know. Maybe you meant '\(maybeText)'"
}
return (ans ?? "<:smol_rise:852763040452575252> sorry, I have no idea (´._.`)", img)
}
static func handleMath(
_ args: ArraySlice<String.SubSequence>,
client: DiscordClient,
ctx: Gateway.MessageCreate
) async throws {
try await client.triggerTypingIndicator(channelId: ctx.channel_id).guardSuccess()
let question = String(args.joined(by: " "))
let (answer, img) = try await getWolfram(question)
var attachments: [Payloads.Attachment] = []
var files: [RawFile] = []
if let img = img {
let data = try? await URLSession.shared.data(from: img)
if let data = data?.0 {
attachments.append(.init(index: 0, filename: "img.gif"))
files.append(.init(data: .init(data: data), filename: "img.gif"))
}
}
try await client.createMessage(channelId: ctx.channel_id, payload: .init(
content: answer,
message_reference: .init(
type: .default,
message_id: ctx.id,
channel_id: ctx.channel_id,
guild_id: ctx.guild_id,
),
files: files,
attachments: attachments
)).guardSuccess()
}
}
struct WolframQueryResult: Codable {
struct Pod: Codable {
let title: String
let id: String
let scanner: String
let primary: Bool?
let subpod: [Subpod]
}
struct Subpod: Codable {
let title: String
let img: Image
let plaintext: String
}
struct Image: Codable {
let src: URL
}
let success: Bool
let numpods: Int
let pod: [Pod]
}
// import Foundation
// #if canImport(FoundationNetworking)
// import FoundationNetworking
// #endif
// import XMLCoder
// import DiscordBM
//
// struct Wolfram {
// static let token = ProcessInfo.processInfo.environment["WOLFRAM_APP_ID"]!
// static let apiUrl = "http://api.wolframalpha.com/v2/query"
//
// static func getWolfram(_ question: String) async throws -> (String, URL?) {
// let encQuestion = question.addingPercentEncoding(withAllowedCharacters: .alphanumerics)
// let url = URL(string: "\(apiUrl)?appid=\(token)&input=\(encQuestion!)")!
// let (data, _) = try await URLSession.shared.data(from: url)
//
// let wolframRes = try XMLDecoder().decode(WolframQueryResult.self, from: data)
//
// let resultPod = wolframRes.pod.first(where: { $0.primary ?? false || $0.id == "Result" })
//
// var ans: String?
// var img: URL?
//
// if let resultPod = resultPod {
// ans = String(resultPod.subpod.compactMap(\.plaintext).joined(by: "\n"))
// .replacingOccurrences(of: " | ", with: ": ")
// }
// if let imgPod = wolframRes.pod.first(where: {
// ["RootPlot", "NumberLine", "Plot", "ImplicitPlot", "3DPlot"].contains($0.id) ||
// ($0.id == "Example" && $0.scanner == "Dice")
// }) {
// ans = ans ?? "Plot:"
// img = imgPod.subpod.first?.img.src
// }
//
// if ans == nil,
// let maybePod = wolframRes.pod.first(where: { $0.title == "Input interpretation" }),
// let maybeText = maybePod.subpod.first?.plaintext
// .replacingOccurrences(of: " | ", with: ": ") {
// ans = "<:smol_rise:852763040452575252> I don't know. Maybe you meant '\(maybeText)'"
// }
//
// return (ans ?? "<:smol_rise:852763040452575252> sorry, I have no idea (´._.`)", img)
// }
//
// static func handleMath(
// _ args: ArraySlice<String.SubSequence>,
// client: DiscordClient,
// ctx: Gateway.MessageCreate
// ) async throws {
// try await client.triggerTypingIndicator(channelId: ctx.channel_id).guardSuccess()
//
// let question = String(args.joined(by: " "))
//
// let (answer, img) = try await getWolfram(question)
//
// var attachments: [Payloads.Attachment] = []
// var files: [RawFile] = []
//
// if let img = img {
// let data = try? await URLSession.shared.data(from: img)
// if let data = data?.0 {
// attachments.append(.init(index: 0, filename: "img.gif"))
// files.append(.init(data: .init(data: data), filename: "img.gif"))
// }
// }
//
// try await client.createMessage(channelId: ctx.channel_id, payload: .init(
// content: answer,
// message_reference: .init(
// type: .default,
// message_id: ctx.id,
// channel_id: ctx.channel_id,
// guild_id: ctx.guild_id,
// ),
// files: files,
// attachments: attachments
// )).guardSuccess()
// }
// }
//
// struct WolframQueryResult: Codable {
// struct Pod: Codable {
// let title: String
// let id: String
// let scanner: String
// let primary: Bool?
//
// let subpod: [Subpod]
// }
//
// struct Subpod: Codable {
// let title: String
// let img: Image
// let plaintext: String
// }
//
// struct Image: Codable {
// let src: URL
// }
//
// let success: Bool
// let numpods: Int
//
// let pod: [Pod]
// }
//

View File

@@ -1,11 +1,10 @@
import Foundation
import DiscordBM
import DiscordKit
import SwiftDotenv
@main
struct Zundamon {
nonisolated(unsafe) static private(set) var ownID: UserSnowflake? = nil
//nonisolated(unsafe) static private(set) var ownID: UserSnowflake? = nil
static func main() async throws {
let tmp = Result { try Dotenv.configure() }
@@ -41,12 +40,12 @@ struct Zundamon {
}
}
struct EventHandler: GatewayEventHandler {
let event: Gateway.Event
let client: any DiscordClient
func onMessageCreate(_ payload: Gateway.MessageCreate) async throws {
try await MessageHandler(ctx: payload, client: client).handle()
}
}
//struct EventHandler: GatewayEventHandler {
// let event: Gateway.Event
// let client: any DiscordClient
//
// func onMessageCreate(_ payload: Gateway.MessageCreate) async throws {
// try await MessageHandler(ctx: payload, client: client).handle()
// }
//
//}