diff --git a/discord.go b/discord.go index db59670b90836599e969c511164b3bf048b7ce63..bd6048ee2ce84c2e6b6b55ede8080bdd1c3d3d8c 100644 --- a/discord.go +++ b/discord.go @@ -49,5 +49,7 @@ func openDiscord() { } func handleDiscord() { - // TODO + session.AddHandler(func(session *discordgo.Session, create *discordgo.MessageCreate) { + // TODO + }) } diff --git a/main.go b/main.go index 415593f1620800baf1b818605b25170cdfa07cdf..91f208fddd28e07747de63c7d392bff2201db7d3 100644 --- a/main.go +++ b/main.go @@ -6,12 +6,14 @@ import ( "os" "os/signal" "syscall" + "time" ) var ( - exec string - rest bool - ready = make(chan struct{}) + exec string + rest bool + ready = make(chan struct{}) + readyTime time.Time ) func main() { @@ -24,6 +26,7 @@ func main() { go func() { <-ready <-ready + readyTime = time.Now() log.Print("sessions ready, starting handlers") go handleDiscord() go handleTelegram() diff --git a/telegram.go b/telegram.go index 3e8ac1b64eea8d47e906088194831915ba0f7f5c..433b4ef3ccb40e5b324fc0760a216ebdfd7ff44a 100644 --- a/telegram.go +++ b/telegram.go @@ -3,12 +3,10 @@ package main import ( "github.com/bwmarrin/discordgo" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" - "io" "log" "net/http" "strconv" "strings" - "time" ) var botAPI *tgbotapi.BotAPI @@ -48,8 +46,23 @@ func handleTelegram() { log.Fatalf("error getting updates: %s", err) } else { if config.Telegram.BypassBacklog { - time.Sleep(time.Millisecond * 500) - updates.Clear() + for update := range updates { + if update.Message == nil { + continue + } + + if update.Message.Time().After(readyTime) { + if config.System.Verbose { + log.Printf("update %v after current, breaking bypass loop", update.UpdateID) + } + respondTelegram(update) + break + } + + if config.System.Verbose { + log.Printf("skipped update %v", update.UpdateID) + } + } } for update := range updates { @@ -68,15 +81,44 @@ func setPreviousCaller(cid int64, uid int) bool { return false } +var ( + messageReference = make(map[int]*discordgo.MessageReference) + hasHeader = make(map[int]bool) +) + func respondTelegram(update tgbotapi.Update) { - // TODO: cross reply // TODO: sticker object storage - if update.Message == nil || update.Message.Chat == nil { - if update.EditedMessage != nil { - // TODO: handle edit - return + if update.EditedMessage != nil { + if reference, ok := messageReference[update.EditedMessage.MessageID]; ok { + content := update.EditedMessage.Text + if hasHeader[update.EditedMessage.MessageID] { + if update.EditedMessage.Chat == nil || update.EditedMessage.From == nil { + if config.System.Verbose { + log.Printf("got irrelevant update %v in edit", update.UpdateID) + } + return + } + header, _ := makeHeader(update.EditedMessage, true) + content = header + content + } + if _, err := session.ChannelMessageEdit(reference.ChannelID, reference.MessageID, content); err != nil { + msg := tgbotapi.NewMessage(update.EditedMessage.Chat.ID, "error relaying edit") + msg.ReplyToMessageID = update.EditedMessage.MessageID + log.Printf("error relaying edit of %v: %s", update.EditedMessage.MessageID, err) + _, _ = botAPI.Send(msg) + return + } + log.Printf("T%vM%v -> D%sM%s @%s (%v) [edit]: %s", + update.EditedMessage.Chat.ID, update.EditedMessage.MessageID, + reference.ChannelID, reference.MessageID, + update.EditedMessage.From.UserName, update.EditedMessage.From.ID, + update.EditedMessage.Text) } + return + } + + if update.Message == nil || update.Message.Chat == nil { if config.System.Verbose { log.Printf("got irrelevant update %v", update.UpdateID) } @@ -104,6 +146,9 @@ func respondTelegram(update tgbotapi.Update) { tc bridgePlatformConf ) if c, ok := telegramBridge[update.Message.Chat.ID]; !ok { + if config.System.Verbose { + log.Printf("got update %v from unconfigured chat %v", update.UpdateID, update.Message.Chat.ID) + } return } else { dc = c.Discord @@ -111,28 +156,93 @@ func respondTelegram(update tgbotapi.Update) { } var ( - username = "unknown" - id = -1 - dMsgID = "-1" + username = "unknown" + id = -1 + dMessageID = "-1" + dGuildID = "-1" + dChannelID = "-1" ) if update.Message.From != nil { username = update.Message.From.UserName id = update.Message.From.ID } - - space := "" - if update.Message.From.LastName != "" { - space = " " + var reference *discordgo.MessageReference + if update.Message.ReplyToMessage != nil { + if r, ok := messageReference[update.Message.ReplyToMessage.MessageID]; ok { + reference = r + setPreviousCaller(update.Message.Chat.ID, -1) + } } - name := "" - if !config.System.DisplaceHeader || !setPreviousCaller(update.Message.Chat.ID, update.Message.From.ID) { - // TODO: check the discord side for header displace - name = strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(config.Telegram.NameFormat, - "$FIRST", update.Message.From.FirstName), - "$LAST", update.Message.From.LastName), - "$USER", update.Message.From.UserName), - "$SPACE", space) - name += "\n" + + header, has := makeHeader(update.Message, false) + + if update.Message.Photo != nil && len(*update.Message.Photo) > 0 { + fids := "" + files := make([]*discordgo.File, len(*update.Message.Photo)) + for i, info := range *update.Message.Photo { + var url string + if u, err := botAPI.GetFileDirectURL(info.FileID); err != nil { + msg := tgbotapi.NewMessage(update.Message.Chat.ID, "error getting URL") + msg.ReplyToMessageID = update.Message.MessageID + log.Printf("error getting URL of %s: %s", info.FileID, err) + _, _ = botAPI.Send(msg) + return + } else { + url = u + } + + if resp, err := http.Get(url); err != nil { + msg := tgbotapi.NewMessage(update.Message.Chat.ID, "error getting file") + msg.ReplyToMessageID = update.Message.MessageID + log.Printf("error getting file %s: %s", info.FileID, err) + _, _ = botAPI.Send(msg) + return + } else { + files[i] = &discordgo.File{ + Name: "sticker.webp", + ContentType: "image/webp", + Reader: resp.Body, + } + //goland:noinspection GoDeferInLoop + defer func() { + if err = resp.Body.Close(); err != nil { + log.Printf("error closing request body: %s", err) + } + }() + } + + if i != 0 { + fids += ", " + } + fids += info.FileID + } + + if message, err := session.ChannelMessageSendComplex(strconv.Itoa(dc.ID), &discordgo.MessageSend{ + Content: header + update.Message.Caption, + Files: files, + Reference: reference, + }); err != nil { + msg := tgbotapi.NewMessage(update.Message.Chat.ID, "error relaying message") + msg.ReplyToMessageID = update.Message.MessageID + log.Printf("error relaying message %v: %s", update.Message.MessageID, err) + _, _ = botAPI.Send(msg) + return + } else { + dMessageID = message.ID + dChannelID = message.ChannelID + dGuildID = message.GuildID + } + + messageReference[update.Message.MessageID] = &discordgo.MessageReference{ + MessageID: dMessageID, + ChannelID: dChannelID, + GuildID: dGuildID, + } + hasHeader[update.Message.MessageID] = has + + log.Printf("T%vM%v -> D%vM%s @%s (%v) [photo]: %s; caption: %s", + tc.ID, update.Message.MessageID, dc.ID, dMessageID, username, id, fids, update.Message.Caption) + return } if update.Message.Sticker != nil { @@ -147,10 +257,7 @@ func respondTelegram(update tgbotapi.Update) { url = u } - var ( - file *discordgo.File - body io.ReadCloser - ) + var file *discordgo.File if resp, err := http.Get(url); err != nil { msg := tgbotapi.NewMessage(update.Message.Chat.ID, "error getting file") msg.ReplyToMessageID = update.Message.MessageID @@ -163,16 +270,22 @@ func respondTelegram(update tgbotapi.Update) { ContentType: "image/webp", Reader: resp.Body, } - body = resp.Body + defer func() { + if err = resp.Body.Close(); err != nil { + log.Printf("error closing request body: %s", err) + } + }() } - content := name + content := header if config.Telegram.StickerEmoji { content += update.Message.Sticker.Emoji } + if message, err := session.ChannelMessageSendComplex(strconv.Itoa(dc.ID), &discordgo.MessageSend{ - Content: content, - File: file, + Content: content, + File: file, + Reference: reference, }); err != nil { msg := tgbotapi.NewMessage(update.Message.Chat.ID, "error relaying message") msg.ReplyToMessageID = update.Message.MessageID @@ -180,34 +293,64 @@ func respondTelegram(update tgbotapi.Update) { _, _ = botAPI.Send(msg) return } else { - dMsgID = message.ID + dMessageID = message.ID + dChannelID = message.ChannelID + dGuildID = message.GuildID } - if err := body.Close(); err != nil { - log.Printf("error closing request body: %s", err) + + messageReference[update.Message.MessageID] = &discordgo.MessageReference{ + MessageID: dMessageID, + ChannelID: dChannelID, + GuildID: dGuildID, } - log.Printf("T%vM%v -> D%vM%s @%s (%v): %s", - tc.ID, update.Message.MessageID, dc.ID, dMsgID, username, id, "Sticker: "+update.Message.Sticker.FileID) - return - } + hasHeader[update.Message.MessageID] = has - if update.Message.Contact != nil { - // TODO: handle contact + log.Printf("T%vM%v -> D%vM%s @%s (%v) [sticker]: %s", + tc.ID, update.Message.MessageID, dc.ID, dMessageID, username, id, update.Message.Sticker.FileID) return } if update.Message.Text == "" { + if config.System.Verbose { + log.Printf("got update %v with empty message", update.UpdateID) + } return } - if message, err := session.ChannelMessageSend(strconv.Itoa(dc.ID), name+update.Message.Text); err != nil { + if message, err := session.ChannelMessageSendReply(strconv.Itoa(dc.ID), header+update.Message.Text, reference); err != nil { msg := tgbotapi.NewMessage(update.Message.Chat.ID, "error relaying message") msg.ReplyToMessageID = update.Message.MessageID log.Printf("error relaying message %v: %s", update.Message.MessageID, err.Error()) _, _ = botAPI.Send(msg) } else { - dMsgID = message.ID + dMessageID = message.ID + dChannelID = message.ChannelID + dGuildID = message.GuildID } + messageReference[update.Message.MessageID] = &discordgo.MessageReference{ + MessageID: dMessageID, + ChannelID: dChannelID, + GuildID: dGuildID, + } + hasHeader[update.Message.MessageID] = has + log.Printf("T%vM%v -> D%vM%s @%s (%v): %s", - tc.ID, update.Message.MessageID, dc.ID, dMsgID, username, id, update.Message.Text) + tc.ID, update.Message.MessageID, dc.ID, dMessageID, username, id, update.Message.Text) +} + +func makeHeader(message *tgbotapi.Message, force bool) (string, bool) { + if !config.System.DisplaceHeader || force || !setPreviousCaller(message.Chat.ID, message.From.ID) { + // TODO: check the discord side for header displace + space := "" + if message.From.LastName != "" { + space = " " + } + return strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(config.Telegram.NameFormat, + "$FIRST", message.From.FirstName), + "$LAST", message.From.LastName), + "$USER", message.From.UserName), + "$SPACE", space) + "\n", true + } + return "", false }