feat: wolfram alpha

This commit is contained in:
Andrew Glaze
2026-02-28 12:10:37 -05:00
commit 1435890218
7 changed files with 563 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
import Foundation
import DiscordBM
import SwiftDotenv
@main
struct Mathsumoto {
nonisolated(unsafe) static private(set) var ownID: UserSnowflake? = nil
static func main() async throws {
let tmp = Result { try Dotenv.configure() }
switch (tmp) {
case .success:
print("Loaded .env file")
case .failure:
break
}
let token = ProcessInfo.processInfo.environment["DISCORD_TOKEN"]!
let bot = await BotGatewayManager(token: token, intents: [.guildMessages, .messageContent])
await withTaskGroup(of: Void.self) { taskGroup in
taskGroup.addTask {
await bot.connect()
let tmp = try! await bot.client.getOwnUser()
ownID = try! tmp.decode().id
}
for await event in await bot.events {
taskGroup.addTask {
await EventHandler(event: event, client: bot.client).handleAsync()
}
}
}
}
}
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()
}
}

View File

@@ -0,0 +1,111 @@
import Foundation
import FoundationNetworking
import DiscordBM
struct MessageHandler {
let ctx: Gateway.MessageCreate
let client: any DiscordClient
static let prefix = ":"
static let zundaGifData = try? Data(contentsOf: URL(filePath: "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)
default: break
}
} else if ctx.mentions.contains(where: { $0.id == Mathsumoto.ownID }) {
if ctx.content
.replacingOccurrences(of: "<@\(Mathsumoto.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

@@ -0,0 +1,105 @@
import Foundation
import FoundationNetworking
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]
}