Replying to a message on the watched channel now posts the reply as a reply to the posts that message produced, instead of as a standalone post. Discord tells us the parent through MessageReference, and the store maps it back to the Ref each output returned; that Ref rides along on the new Post.ReplyTo. X takes it as in_reply_to_tweet_id. Bluesky needs the thread root as well as the parent, so a Ref now carries the root it was posted under and a post that is not itself a reply acts as its own root. Replying to something we never distributed, an error notice from the bot for instance, still posts normally. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
93 lines
1.9 KiB
Go
93 lines
1.9 KiB
Go
package discord
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/bwmarrin/discordgo"
|
|
)
|
|
|
|
// EventKind tells apart the things that can happen to a watched message.
|
|
type EventKind int
|
|
|
|
const (
|
|
MessageCreated EventKind = iota
|
|
MessageDeleted
|
|
)
|
|
|
|
// Event is a change on the watched channel that the distributor has to mirror.
|
|
type Event struct {
|
|
Kind EventKind
|
|
MessageID string
|
|
Content string
|
|
Attachments []*discordgo.MessageAttachment
|
|
// ReplyToID is the ID of the message this one replies to, empty when the
|
|
// message is not a reply.
|
|
ReplyToID string
|
|
}
|
|
|
|
type Client struct {
|
|
Token string
|
|
ChannelID string
|
|
dgsession *discordgo.Session
|
|
}
|
|
|
|
func Discord(token string, channelId string) *Client {
|
|
return &Client{
|
|
Token: token,
|
|
ChannelID: channelId,
|
|
}
|
|
}
|
|
|
|
func (d *Client) BeginRead(eventchannel chan<- Event) {
|
|
d.read(eventchannel)
|
|
}
|
|
|
|
func (d *Client) read(eventchannel chan<- Event) {
|
|
dgsession, err := discordgo.New("Bot " + d.Token)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
dgsession.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
|
|
if m.ChannelID != d.ChannelID || m.Author.ID == s.State.User.ID {
|
|
return
|
|
}
|
|
event := Event{
|
|
Kind: MessageCreated,
|
|
MessageID: m.ID,
|
|
Content: m.Content,
|
|
Attachments: m.Attachments,
|
|
}
|
|
if m.MessageReference != nil {
|
|
event.ReplyToID = m.MessageReference.MessageID
|
|
}
|
|
eventchannel <- event
|
|
})
|
|
|
|
// Deleted messages arrive without an author, so the channel is the only
|
|
// thing to filter on; messages we never distributed are dropped later.
|
|
dgsession.AddHandler(func(s *discordgo.Session, m *discordgo.MessageDelete) {
|
|
if m.ChannelID != d.ChannelID {
|
|
return
|
|
}
|
|
eventchannel <- Event{
|
|
Kind: MessageDeleted,
|
|
MessageID: m.ID,
|
|
}
|
|
})
|
|
|
|
d.dgsession = dgsession
|
|
|
|
errOpen := dgsession.Open()
|
|
if errOpen != nil {
|
|
fmt.Println(errOpen)
|
|
}
|
|
}
|
|
func Read() string {
|
|
return "discord"
|
|
}
|
|
|
|
func (d *Client) Write(str string) {
|
|
d.dgsession.ChannelMessageSend(d.ChannelID, str)
|
|
}
|