Skip to content

Commit

Permalink
Merge pull request #162 from fwcd/command-on-methods-async
Browse files Browse the repository at this point in the history
Migrate command notification methods to async and clean up `Sink`
  • Loading branch information
fwcd authored Jul 3, 2024
2 parents 4999924 + d07e32c commit 40ff9d5
Show file tree
Hide file tree
Showing 30 changed files with 316 additions and 332 deletions.
20 changes: 10 additions & 10 deletions Sources/D2Commands/Command.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,34 +20,34 @@ public protocol Command: AnyObject {

/// Notifies the command that a message sent via CommandOutput has been
/// successfully transmitted.
func onSuccessfullySent(context: CommandContext)
func onSuccessfullySent(context: CommandContext) async

/// Notifies the command that a message on a subscribed channel has arrived.
func onSubscriptionMessage(with content: String, output: any CommandOutput, context: CommandContext)
func onSubscriptionMessage(with content: String, output: any CommandOutput, context: CommandContext) async

/// Notifies the command that a component interaction on a subscribed channel has arrived.
func onSubscriptionInteraction(with customId: String, by user: User, output: any CommandOutput, context: CommandContext)
func onSubscriptionInteraction(with customId: String, by user: User, output: any CommandOutput, context: CommandContext) async

/// Notifies the command that a reaction on a subscribed channel has arrived.
func onSubscriptionReaction(emoji: Emoji, by user: User, output: any CommandOutput, context: CommandContext)
func onSubscriptionReaction(emoji: Emoji, by user: User, output: any CommandOutput, context: CommandContext) async

/// Notifies the command that the bot's presence has updated.
func onReceivedUpdated(presence: Presence)
func onReceivedUpdated(presence: Presence) async
}

extension Command {
public var inputValueType: RichValueType { .unknown }
public var outputValueType: RichValueType { .unknown }

public func onSuccessfullySent(context: CommandContext) {}
public func onSuccessfullySent(context: CommandContext) async {}

public func onSubscriptionMessage(with content: String, output: any CommandOutput, context: CommandContext) {}
public func onSubscriptionMessage(with content: String, output: any CommandOutput, context: CommandContext) async {}

public func onSubscriptionInteraction(with customId: String, by user: User, output: any CommandOutput, context: CommandContext) {}
public func onSubscriptionInteraction(with customId: String, by user: User, output: any CommandOutput, context: CommandContext) async {}

public func onSubscriptionReaction(emoji: Emoji, by user: User, output: any CommandOutput, context: CommandContext) {}
public func onSubscriptionReaction(emoji: Emoji, by user: User, output: any CommandOutput, context: CommandContext) async {}

// TODO: Support reaction removal

public func onReceivedUpdated(presence: Presence) {}
public func onReceivedUpdated(presence: Presence) async {}
}
7 changes: 4 additions & 3 deletions Sources/D2Commands/Emoji/DeleteEmojiCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ public class DeleteEmojiCommand: StringCommand {
return
}

if (try? await sink.deleteEmoji(from: guild.id, emojiId: emojiId)) ?? false {
do {
try await sink.deleteEmoji(from: guild.id, emojiId: emojiId)
await output.append("Successfully deleted emoji!")
} else {
await output.append(errorText: "Could not delete emoji")
} catch {
await output.append(error, errorText: "Could not delete emoji")
}
}
}
35 changes: 16 additions & 19 deletions Sources/D2Commands/Fun/AkinatorCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public class AkinatorCommand: StringCommand {

}

public func onSubscriptionMessage(with content: String, output: any CommandOutput, context: CommandContext) {
public func onSubscriptionMessage(with content: String, output: any CommandOutput, context: CommandContext) async {
guard let channelId = context.channel?.id, let session = sessions[channelId] else {
context.unsubscribeFromChannel()
return
Expand All @@ -61,26 +61,23 @@ public class AkinatorCommand: StringCommand {
return
}

// TODO: Remove once onSubscriptionMessage is async
Task {
do {
let question = try await session.answer(with: answer)
if question.progression > progressionThreshold {
do {
guard let guess = try await session.guess().first else { throw AkinatorError.noGuesses }
await output.append(self.embed(of: guess))
} catch {
await output.append(error, errorText: "Error while guessing")
}

self.sessions[channelId] = nil
context.unsubscribeFromChannel()
} else {
await output.append(self.embed(of: question))
do {
let question = try await session.answer(with: answer)
if question.progression > progressionThreshold {
do {
guard let guess = try await session.guess().first else { throw AkinatorError.noGuesses }
await output.append(embed(of: guess))
} catch {
await output.append(error, errorText: "Error while guessing")
}
} catch {
await output.append(error, errorText: "Error while answering")

sessions[channelId] = nil
context.unsubscribeFromChannel()
} else {
await output.append(embed(of: question))
}
} catch {
await output.append(error, errorText: "Error while answering")
}
}

Expand Down
45 changes: 22 additions & 23 deletions Sources/D2Commands/Fun/DiscordinderCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,30 @@ public class DiscordinderCommand: StringCommand {
self.inventoryManager = inventoryManager
}

public func invoke(with input: String, output: any CommandOutput, context: CommandContext) {
public func invoke(with input: String, output: any CommandOutput, context: CommandContext) async {
if input == cancelSubcommand {
guard context.isSubscribed else {
output.append(errorText: "No session is running on this channel!")
await output.append(errorText: "No session is running on this channel!")
return
}

cancelSession(context: context)
output.append(":x: Cancelled Discordinder session on this channel!")
await output.append(":x: Cancelled Discordinder session on this channel!")
} else {
guard let authorId = context.author?.id else {
output.append(errorText: "Author has no user ID")
await output.append(errorText: "Author has no user ID")
return
}
guard !context.isSubscribed else {
output.append(errorText: "There is already an active session on this channel!")
await output.append(errorText: "There is already an active session on this channel!")
return
}

presentNextCandidate(for: authorId, output: output, context: context)
await presentNextCandidate(for: authorId, output: output, context: context)
}
}

public func onSubscriptionReaction(emoji: Emoji, by user: User, output: any CommandOutput, context: CommandContext) {
public func onSubscriptionReaction(emoji: Emoji, by user: User, output: any CommandOutput, context: CommandContext) async {
guard
let guild = context.guild,
let messageId = context.message.id,
Expand All @@ -57,7 +57,7 @@ public class DiscordinderCommand: StringCommand {
case acceptEmoji:
let state = accept(matchBetween: user.id, and: candidateId, on: guild)
if state == .accepted {
output.append(":partying_face: It's a match!")
await output.append(":partying_face: It's a match!")
cancelSession(context: context)
return
}
Expand All @@ -68,24 +68,24 @@ public class DiscordinderCommand: StringCommand {
}

activeMatches[messageId] = nil
let success = presentNextCandidate(for: user.id, output: output, context: context)
let success = await presentNextCandidate(for: user.id, output: output, context: context)
if !success {
context.unsubscribeFromChannel()
}
}

@discardableResult
private func presentNextCandidate(for authorId: UserID, output: any CommandOutput, context: CommandContext) -> Bool {
private func presentNextCandidate(for authorId: UserID, output: any CommandOutput, context: CommandContext) async -> Bool {
guard let guild = context.guild else {
output.append(errorText: "Not on a guild!")
await output.append(errorText: "Not on a guild!")
return false
}
guard let sink = context.sink else {
output.append(errorText: "No client")
await output.append(errorText: "No client")
return false
}
guard let channelId = context.channel?.id else {
output.append(errorText: "No channel ID")
await output.append(errorText: "No channel ID")
return false
}

Expand All @@ -101,21 +101,18 @@ public class DiscordinderCommand: StringCommand {
nonCandidateIds.insert(authorId)

guard let candidateId = waitingForAcceptor.randomElement() ?? guild.members.filter({ !nonCandidateIds.contains($0.0) && !$0.1.user.bot }).randomElement()?.0 else {
output.append(errorText: "Sorry, no candidates are left!")
await output.append(errorText: "Sorry, no candidates are left!")
return false
}
guard let candidate = guild.members[candidateId] else {
output.append(errorText: "Could not find candidate")
await output.append(errorText: "Could not find candidate")
return false
}
let candidatePresence = guild.presences[candidateId]

// TODO: Asyncify everything
Task {
do {
let sentMessage = try await sink.sendMessage(Message(embed: embedOf(member: candidate, presence: candidatePresence, sink: sink)), to: channelId)
guard let messageId = sentMessage?.id else { return }

do {
let sentMessage = try await sink.sendMessage(Message(embed: embedOf(member: candidate, presence: candidatePresence, sink: sink)), to: channelId)
if let messageId = sentMessage?.id {
context.subscribeToChannel()
self.accept(matchBetween: authorId, and: candidateId, on: guild)
self.activeMatches[messageId] = (channelId, candidateId)
Expand All @@ -124,9 +121,11 @@ public class DiscordinderCommand: StringCommand {
for reaction in reactions {
try await sink.createReaction(for: messageId, on: channelId, emoji: reaction)
}
} catch {
await output.append(error, errorText: "Could not present next candidate")
} else {
await output.append(errorText: "Sent message has no id")
}
} catch {
await output.append(error, errorText: "Could not send message/create reaction for next candidate")
}

return true
Expand Down
8 changes: 3 additions & 5 deletions Sources/D2Commands/Fun/NeverHaveIEverCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,11 @@ public class NeverHaveIEverCommand: StringCommand {
}
}

public func onSuccessfullySent(context: CommandContext) {
public func onSuccessfullySent(context: CommandContext) async {
guard let messageId = context.message.id, let channelId = context.message.channelId else { return }

Task {
for emoji in ["🍹", ""] {
_ = try? await context.sink?.createReaction(for: messageId, on: channelId, emoji: emoji)
}
for emoji in ["🍹", ""] {
_ = try? await context.sink?.createReaction(for: messageId, on: channelId, emoji: emoji)
}
}
}
7 changes: 3 additions & 4 deletions Sources/D2Commands/Game/Wordle/SolveWordleCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ public class SolveWordleCommand: StringCommand {
context.subscriptions.unsubscribe(from: channelId)
}

public func onSubscriptionMessage(with content: String, output: any CommandOutput, context: CommandContext) {
public func onSubscriptionMessage(with content: String, output: any CommandOutput, context: CommandContext) async {
guard let channelId = context.channel?.id else {
return
}
if content == "cancel" {
output.append("Cancelled wordle solver on this channel")
await output.append("Cancelled wordle solver on this channel")
unsubscribe(from: channelId, context: context)
return
}
Expand All @@ -66,7 +66,6 @@ public class SolveWordleCommand: StringCommand {
board.guesses.append(WordleBoard.Guess(word: word, clues: clues))
boards[channelId] = board

// TODO: Remove once we asyncify onSubscriptionMessage
Task {
_ = try? await context.channel?.triggerTyping()
}
Expand Down Expand Up @@ -104,7 +103,7 @@ public class SolveWordleCommand: StringCommand {
)
}

output.append(.compound([
await output.append(.compound([
board.asRichValue,
.embed(embed)
]))
Expand Down
27 changes: 16 additions & 11 deletions Sources/D2Commands/Imaging/Animation/AnimateCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ public class AnimateCommand<A>: Command where A: Animation {
self.delayTime = delayTime
}

public func invoke(with input: RichValue, output: any CommandOutput, context: CommandContext) {
public func invoke(with input: RichValue, output: any CommandOutput, context: CommandContext) async {
let args = input.asText ?? ""
let typingIndicator = context.channel.map { TypingIndicator(on: $0) }
typingIndicator?.startAsync()
await typingIndicator?.start()

// Parse user-specified args
let pos: Vec2<Int>? = (try? posPattern.firstMatch(in: args)).map { Vec2<Int>(x: Int($0.1)!, y: Int($0.2)!) }
Expand All @@ -62,7 +62,7 @@ public class AnimateCommand<A>: Command where A: Animation {
let frameCount = kvArgs[framesParameter].flatMap(Int.init) ?? defaultFrameCount

guard frameCount <= maxFrameCount else {
output.append(errorText: "Please specify less than \(maxFrameCount) frames!")
await output.append(errorText: "Please specify less than \(maxFrameCount) frames!")
return
}

Expand All @@ -72,7 +72,7 @@ public class AnimateCommand<A>: Command where A: Animation {
.filter({ (k, _) in k != framesParameter })
.sequenceMap({ (k, v) in A.Key(rawValue: k).map { ($0, v) } })
.map(Dictionary.init(uniqueKeysWithValues:)) else {
output.append(errorText: "Invalid keys. Try using only these: \(kvParameters.map { "`\($0)`" }.joined(separator: ", "))")
await output.append(errorText: "Invalid keys. Try using only these: \(kvParameters.map { "`\($0)`" }.joined(separator: ", "))")
return
}

Expand All @@ -95,29 +95,34 @@ public class AnimateCommand<A>: Command where A: Animation {
log.debug("Rendering frame \(i)")
try animation.renderFrame(from: image, to: frame, percent: percent)
gif.frames.append(.init(image: frame, delayTime: delayTime))

await Task.yield()
}

output.append(.gif(gif))
await output.append(.gif(gif))
} else if let sourceGif = input.asGif {
var gif = sourceGif
let frameCount = sourceGif.frames.count

gif.frames = try sourceGif.frames.enumerated().map { (i, f) in
gif.frames = []
for (i, f) in sourceGif.frames.enumerated() {
let frame = try CairoImage(width: f.image.width, height: f.image.height)
let percent = Double(i) / Double(frameCount)

try animation.renderFrame(from: f.image, to: frame, percent: percent)
return .init(image: frame, delayTime: f.delayTime)
gif.frames.append(.init(image: frame, delayTime: f.delayTime))

await Task.yield()
}

output.append(.gif(gif))
await output.append(.gif(gif))
} else {
output.append(errorText: "No image passed to AnimateCommand")
await output.append(errorText: "No image passed to AnimateCommand")
}
} catch {
output.append(error, errorText: "Error while generating animation")
await output.append(error, errorText: "Error while generating animation")
}

typingIndicator?.stop()
await typingIndicator?.stop()
}
}
17 changes: 7 additions & 10 deletions Sources/D2Commands/Math/AutoLatexCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,19 @@ public class AutoLatexCommand: StringCommand {
}
}

public func onSubscriptionMessage(with content: String, output: any CommandOutput, context: CommandContext) {
public func onSubscriptionMessage(with content: String, output: any CommandOutput, context: CommandContext) async {
if content == "cancel autolatex" {
output.append("This syntax has been deprecated, please use `\(context.commandPrefix)autolatex cancel` to cancel.")
await output.append("This syntax has been deprecated, please use `\(context.commandPrefix)autolatex cancel` to cancel.")
return
}

if !content.matches(of: formulaPattern).isEmpty {
let formula = escapeText(in: content)
// TODO: Remove this once onSubscriptionMessage is async
Task {
do {
let image = try await latexRenderer.renderImage(from: formula, scale: 1.5)
try await output.append(image)
} catch {
log.error("\(error)")
}
do {
let image = try await latexRenderer.renderImage(from: formula, scale: 1.5)
try await output.append(image)
} catch {
log.error("\(error)")
}
}
}
Expand Down
7 changes: 2 additions & 5 deletions Sources/D2Commands/Misc/PetitionCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,9 @@ public class PetitionCommand: StringCommand {
))
}

public func onSuccessfullySent(context: CommandContext) {
public func onSuccessfullySent(context: CommandContext) async {
guard let messageId = context.message.id, let channelId = context.message.channelId else { return }

// TODO: Remove once onSuccessfullySent is async
Task {
_ = try? await context.sink?.createReaction(for: messageId, on: channelId, emoji: "✍️")
}
_ = try? await context.sink?.createReaction(for: messageId, on: channelId, emoji: "✍️")
}
}
Loading

0 comments on commit 40ff9d5

Please sign in to comment.