Image attach

This commit is contained in:
Andrew Glaze
2026-03-20 19:13:45 -04:00
parent 4d39ed8053
commit 6a48936f6c
7 changed files with 289 additions and 241 deletions

View File

@@ -7,6 +7,7 @@ public struct DiscordClient: Sendable {
static let apiUrl: URL = URL(string: "https://discord.com/api/v10")! static let apiUrl: URL = URL(string: "https://discord.com/api/v10")!
let token: String let token: String
@discardableResult
private func authReq(_ request: consuming URLRequest) async throws -> (Data, URLResponse) { private func authReq(_ request: consuming URLRequest) async throws -> (Data, URLResponse) {
request.setValue("Bot \(token)", forHTTPHeaderField: "Authorization") request.setValue("Bot \(token)", forHTTPHeaderField: "Authorization")
request.setValue("DiscordKit (https:\\candy123.moe, v0.0.1)", forHTTPHeaderField: "User-Agent") request.setValue("DiscordKit (https:\\candy123.moe, v0.0.1)", forHTTPHeaderField: "User-Agent")
@@ -44,8 +45,7 @@ public struct DiscordClient: Sendable {
let json = JSONEncoder() let json = JSONEncoder()
req.httpBody = try json.encode(payload) req.httpBody = try json.encode(payload)
let (data, res) = try await authReq(req) try await authReq(req)
} }
} }

View File

@@ -32,8 +32,6 @@ actor GatewayClient {
} }
try await sendIdentify(intents: intents) try await sendIdentify(intents: intents)
//dump(try await getMessage())
//print("got here")
_ = await heartbeatTask.result _ = await heartbeatTask.result
} }

View File

@@ -96,13 +96,57 @@ public struct GatewayHello: Codable, Sendable {
} }
public struct CreateMessageReq: Codable, Sendable { public struct CreateMessageReq: Codable, Sendable {
public init(content: String? = nil, message_reference: MessageRefrence? = nil) { public init(content: String? = nil, message_reference: MessageRefrence? = nil, embeds: [Embed]? = nil, files: [RawFile]? = nil, attachments: [Attachment]? = nil) {
self.content = content self.content = content
self.message_reference = message_reference self.message_reference = message_reference
self.embeds = embeds
self.files = files
self.attachments = attachments
} }
public let content: String? public let content: String?
public let message_reference: MessageRefrence? public let message_reference: MessageRefrence?
public let embeds: [Embed]?
public let files: [RawFile]?
public let attachments: [Attachment]?
}
public struct Attachment: Codable, Sendable {
public init(index: Int, filename: String? = nil) {
self.index = index
self.filename = filename
}
public let index: Int
public let filename: String?
}
public struct RawFile: Codable, Sendable {
public init(data: Data, filename: String? = nil) {
self.data = data
self.filename = filename
}
public let data: Data
public let filename: String?
}
public struct Embed: Codable, Sendable {
public init(description: String? = nil, image: EmbedImage? = nil) {
self.description = description
self.image = image
}
public let description: String?
public let image: EmbedImage?
}
public struct EmbedImage: Codable, Sendable {
public init(url: String) {
self.url = url
}
public let url: String
} }
public struct MessageRefrence: Codable, Sendable { public struct MessageRefrence: Codable, Sendable {
@@ -125,12 +169,28 @@ public struct MessageCreate: Codable, Sendable {
public let guild_id: String? public let guild_id: String?
public let author: User? public let author: User?
public let content: String public let content: String
public let mentions: [User] public let mentions: [MentionUser]
public let member: GuildMember?
} }
public struct User: Codable, Sendable { public struct User: Codable, Sendable {
public let id: String? public let id: String?
public let bot: Bool? public let bot: Bool?
public let global_name: String?
public let username: String
}
public struct GuildMember: Codable, Sendable {
public let user: User?
public let nick: String?
}
public struct MentionUser: Codable, Sendable {
public let id: String?
public let bot: Bool?
public let global_name: String?
public let username: String
public let member: GuildMember?
} }

View File

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

View File

@@ -20,32 +20,30 @@ struct MessageHandler {
switch command { switch command {
case "wow": try await handleWow(args) case "wow": try await handleWow(args)
// case "domath": try await Wolfram.handleMath(args, client: client, ctx: ctx) case "domath": try await Wolfram.handleMath(args, client: client, ctx: ctx)
// case "hug": try await Actions.hug(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 "pat": try await Actions.pat(args, client: client, ctx: ctx)
// case "pet": try await Actions.pat(args, client: client, ctx: ctx) case "pet": try await Actions.pat(args, client: client, ctx: ctx)
default: break default: break
} }
} else if ctx.mentions.contains(where: { $0.id == Zundamon.ownID }) { } else if ctx.mentions.contains(where: { $0.id == Zundamon.ownID }) {
if ctx.content if ctx.content
.replacingOccurrences(of: "<@\(Zundamon.ownID!)>", with: "") .replacingOccurrences(of: "<@\(Zundamon.ownID!)>", with: "")
.trimmingCharacters(in: .whitespacesAndNewlines) .trimmingCharacters(in: .whitespacesAndNewlines)
.count == 0, .count == 0
let zundaGif = MessageHandler.zundaGifData
{ {
// try await client.createMessage( try await client.createMessage(
// channelId: ctx.channel_id, channelId: ctx.channel_id,
// payload: .init( payload: .init(
// message_reference: .init( message_reference: .init(
// type: .default, type: 0,
// message_id: ctx.id, message_id: ctx.id,
// channel_id: ctx.channel_id, channel_id: ctx.channel_id,
// guild_id: ctx.guild_id, guild_id: ctx.guild_id,
// ), ),
// files: [.init(data: .init(data: zundaGif), filename: "zundamone.gif")], embeds: [.init(image: .init(url: "https://candy123.moe/tetrio/zundamone.gif"))],
// attachments: [.init(index: 0, filename: "zundamone.gif")], )
// ) )
// ).guardSuccess()
} else { } else {
try await handle8Ball() try await handle8Ball()
} }

View File

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

View File

@@ -21,8 +21,6 @@ struct Zundamon {
ownID = try await bot.client.getOwnUser().id ownID = try await bot.client.getOwnUser().id
guard ownID != nil else { fatalError("Failed to get own User ID") } guard ownID != nil else { fatalError("Failed to get own User ID") }
// let bot = await BotGatewayManager(token: token, intents: [.guildMessages, .messageContent])
await withThrowingTaskGroup(of: Void.self) { taskGroup in await withThrowingTaskGroup(of: Void.self) { taskGroup in
taskGroup.addTask { taskGroup.addTask {
try await bot.connect() try await bot.connect()