Open jakecoble opened 1 year ago
Maybe there's a nice generalization that the business logic could use? Like every message it receives includes a message ID as metadata. The business logic can then remember any message ID it may want to (in this case the one that kicked off the auction) and specify as part of its response like "make this be a reply to message ID X".
I think that might be pretty nice for this and potentially other use cases.
And maybe it's a reasonable way to implement this use case, if we think it's worth it?
Slack is going to be the platform to thwart us here. We can't get an ID for the message which instigates the command, and the webhook we're given for the replies expires in 30 minutes.
We could go ahead and implement this and just accept that it'll stop working if the auction goes on longer than 30 minutes, but, otherwise, I've not found a way to do this on Slack.
That makes sense, actually, since Slack just doesn't really have this nice feature that Discord has where any message can be a reply to any previous message. So could this message ID metadata just be null if the platform is Slack and the business logic can just deal?
To be clear, Slack does support replies. It's just that there isn't a way, so far as I know, to get the id of the message that issued the command while we're processing said command. There may be a hacky solution we discover down the line.
Looks like Discord.js does support replying to the original command with interaction.followUp()
, but that has a 15 minute time limit. Shall I check whether we're within the time limit and follow up if we're still within the 15 minutes and otherwise send a regular message? or does that limitation make replying to the original message not worth it?
Looks like Discord.js does support replying to the original command with
Wait, Discord.js? I thought we were talking about Slack...
Assuming Slack, I'm pretty confused about what this 15-minute followup thing could mean. From using Slack as a human it doesn't seem that Slack really supports replies at all. You can create a thread, but that's usually not what you want to do. Discord does replies really nicely, and has a more heavyweight threading feature that's separate from replies. Slack seems to just not have normal replies.
But maybe I'm really misunderstanding things.
But assuming I'm not profoundly confused, I tentatively stand by the opinion that the business logic should have access to a message ID of the thing the bot is replying to but we just set that ID to null if platform=slack.
It's fine. I'll get this working on Discord and we'll pass null to the command when it's on Slack. The rest of this is just clarification.
I realize now what you meant by "where any message can be a reply to any previous message". You don't consider threads to be replies. The library for Slack, Bolt.js, has a function called message.reply()
which starts a thread under message
, so I was using the term reply
to mean the threading feature.
Ok, so if threading doesn't satisfy the "Reply to original bid" requirement, then you were right and Slack can't give us what we want.
Discord supports replies without threading. But it doesn't give us the message ID of the instigating slash command message. It's not even clear that such a message exists: Discord slash commands don't let the invoking message get posted to the channel anyway, so there's no message to refer to. The thing we're "replying to" isn't a message, but an interaction, which is its own thing.
However, we're having the bot post the invoking message itself, which does create a message with an ID we have access to, so I can pass that into the slash command. On Discord.
You don't have to worry about the thing about following up and time limits. That's confusing and not relevant anymore. Replying to the interaction had limits, but, if we're replying to a message, that's different.
After some more tinkering, I've discovered a problem with the current flow that makes this impossible. When you issue a slash command, there is no message to reply to. That's why we have to echo the slash command ourselves.
I believed we could just save the id of the message we send to reply to, but we send it only after the business logic has been executed. If we want this to work, we may have to change how slash-command business logic is implemented.
Ha. Well. (Impressive exposition there, btw!)
This is now a fun design challenge! I like the current architecture where the business logic gets a {platform, channel, sender, msg} hash and returns a {voxmode, reply} hash. I'd also like to generalize beyond slash commands.
If the bot is replying to a non-slash-command, we can pass in the message ID no problem, right?
And, to review, the tricky thing is that for a slash command, there literally is no message until the bot sends its response to the slash command. So suppose we let the business logic pick its own message ID for the message it is currently sending? The Omnibot layer could keep a dictionary mapping the chosen ID to the platform's actual ID.
Walking through that to make sure it makes sense...
First, we augment the input and output hashes like so:
{platform, channel, sender, msg, msgID} → {voxmode, reply, replyToID, selfID}
If the input was a slash command then msgID will be null. And replyToID may be null if the bot's reply is not going to be a reply to anything. The final key, selfID, is also optional.
Here's a sketch of what actually happens in the case of the /bid command:
Finally, to avoid special cases when using the Message ID Dictionary (call it mdict), whenever Omnibot gets a message ID, M
, from the platform, it adds an M=>M
entry. That way, whenever it gets a message to send out with a replyToID in it, it can set the reply-to field to mdict[replyToID]
because that lookup will necessarily return a message ID the platform understands. (But if that's dumb, we could instead have the mdict[x]
lookup fall back to just using x
itself when x
isn't found in the dictionary.)
That could work. It may be less complex and indirect to pass in the reply functions as we'd done before voxmode
. holla
and blurt
could accept a replyTo
parameter and return promises that resolve with the posted message's ID.
{
execute: ({platform, channel, sender, msg, whisp, holla, blurt}) => {
// business logic stuff
holla(response)
.then(id => auctionCommandID = id);
// later...
blurt(response, auctionCommandID)
}
}
Something like that. That said, this would involve promises, and not everybody groks those.
That sounds smart. Maybe we could do something conceptually similar that doesn't need promises? Or maybe promises is the best implementation. But let me repeat the walk-through of the /bid example to make sure I understand.
(I now believe that this has #165 as a prereq. I just added a sketch of the input/output there.)
The key is that the input that the business logic gets includes a reply function that the business logic invokes to send its reply.
Conceptually, the bizlogic wants to ingest incoming messages with IDs attached that it can remember if it wants to. Also when it sends a message it wants a message ID for what it sent. That way it has flexibility to do replies (or emoji-react?) or otherwise interact with any message that it has seen. A monkey wrench is that a slash command is sent to the bizlogic as an incoming message but it's not a message and has no message ID. If voxmode=holla then Omnibot'll echo it back and _that_ will have a message ID. The bizlogic just has to cope with that. An incoming message may have msgID==null and the bizlogic understands that that means this message was essentially whispered to Omnibot. There's no public message that can later be referred to. Which is fine. Webhooks are like that too. Conceptually that's also like whispering something to Omnibot. It can then blurt something in a channel if it decides to. (DIGRESSION moved to #165) For message IDs, let's focus on the question of how the bizlogic can learn the message ID of what it's sending. Options: 1. The thing I sketched above where it makes up a UUID and the Omnibot layer maintains a mapping to the actual message ID for the platform. 2. The promise thing? 6. The bizlogic can make use of a send() function to send a message and that function returns the message ID of what was sent?
Questions from @jakecoble (note we've been abbreviating "business logic" as "bizlogic" and the complement of that as "partylogic"):
When the partylogic wants to reply to the original command, we could use interaction.reply(). That'd post a message as a reply to the command as you'd expect. However, if we do that, we can't reply with phem anymore.
To work around that, the current bizlogic creates a new message with the text of the command, then sends that message to Discord so the partylogic has something to reply to.
So we have this:
- A user calls /ping
- partylogic calls sendmesg({... mrid: msid, mesg: "pong!" })
- bizlogic sends a message containing the text /ping to Discord.
- bizlogic replies to the message sent in step 3 with a message containing the text "pong!"
If we want the partylogic to reply to the "original command" when the auction is done, we could do one of two things, as I see it:
- Use interaction.reply() and interaction.followUp() as normal and document the fact that replying to the original command means the loss of phem.
- In the bizlogic, save the id of the message from step 3. When the partylogic replies to the original command, we reply to that message instead if it already exists.
PS: oops, except bizlogic and partylogic may be swapped above! let's go with bizlogic vs infralogic from now on -- should be less confusing!
[currently paused while we hash out #165]