diff --git a/whappdc-bridge/bridge.go b/whappdc-bridge/bridge.go index 45e624c..bb69a08 100644 --- a/whappdc-bridge/bridge.go +++ b/whappdc-bridge/bridge.go @@ -10,15 +10,16 @@ import ( ) type Bridge struct { - Ctx *core.BridgeContext + Ctx *core.BridgeContext + whappHandler *whappdc.WhappHandler } func (b *Bridge) Init(config *core.Config) error { - messageWorker := whappdc.NewMessageWorker() - ctx := core.NewBridgeContext(config, 15*time.Minute) + ctx := core.NewBridgeContext(config) + whappHandler := whappdc.NewWhappHandler(ctx, 15*time.Minute) err := ctx.Init( - whappdc.NewWhappHandler(ctx, messageWorker), + whappHandler, []deltabot.Command{ &botcommands.Echo{}, botcommands.NewWhappBridge(ctx), @@ -29,13 +30,20 @@ func (b *Bridge) Init(config *core.Config) error { return err } - messageWorker.Start() + whappHandler.Start() + b.whappHandler = whappHandler b.Ctx = ctx return nil } func (b *Bridge) Close() error { + err := b.whappHandler.Stop() + + if err != nil { + return err + } + return b.Ctx.Close() } diff --git a/whappdc-core/bridge_context.go b/whappdc-core/bridge_context.go index f136815..de2e9d6 100644 --- a/whappdc-core/bridge_context.go +++ b/whappdc-core/bridge_context.go @@ -1,33 +1,27 @@ package core import ( - "log" - "time" - "github.com/Rhymen/go-whatsapp" "github.com/hugot/go-deltachat/deltabot" "github.com/hugot/go-deltachat/deltachat" ) type BridgeContext struct { - Config *Config - WhappConn *whatsapp.Conn - DCContext *deltachat.Context - DCClient *deltachat.Client - DB *Database - MessageTracker *MessageTracker - DCUserID uint32 - DCUserChatID uint32 + Config *Config + WhappConn *whatsapp.Conn + DCContext *deltachat.Context + DCClient *deltachat.Client + DB *Database + DCUserID uint32 + DCUserChatID uint32 } -func NewBridgeContext(config *Config, msgTrackerFlushInterval time.Duration) *BridgeContext { +func NewBridgeContext(config *Config) *BridgeContext { db := NewDatabase(config.App.DataFolder + "/app.db") - messageTracker := NewMessageTracker(db, msgTrackerFlushInterval) return &BridgeContext{ - Config: config, - DB: db, - MessageTracker: messageTracker, + Config: config, + DB: db, } } @@ -83,12 +77,6 @@ func (b *BridgeContext) Close() error { return err } - err = b.MessageTracker.Flush() - - if err != nil { - return err - } - b.DCClient.Close() err = b.DB.Close() @@ -96,60 +84,6 @@ func (b *BridgeContext) Close() error { return err } -// Find or create a deltachat verified group chat for a whatsapp JID and return it's ID. -func (b *BridgeContext) GetOrCreateDCIDForJID(JID string) (uint32, error) { - if DCID, _ := b.DB.GetDCIDForWhappJID(JID); DCID != nil { - return *DCID, nil - } - - chatName := JID - chat, ok := b.WhappConn.Store.Chats[JID] - - if ok { - chatName = chat.Name - } else if sender, ok := b.WhappConn.Store.Contacts[JID]; ok { - chatName = sender.Name - } - - DCID := b.DCContext.CreateGroupChat(true, chatName) - - err := b.DB.StoreDCIDForJID(JID, DCID) - - if err != nil { - return DCID, err - } - - b.DCContext.AddContactToChat(DCID, b.DCUserID) - - return DCID, err -} - func (b *BridgeContext) SendLog(logString string) { b.DCContext.SendTextMessage(b.DCUserChatID, logString) } - -func (b *BridgeContext) MessageWasSent(ID string) bool { - sent, err := b.MessageTracker.WasSent(ID) - - if err != nil { - log.Println(err) - b.SendLog(err.Error()) - } - - return sent -} - -func (b *BridgeContext) ShouldMessageBeSent(info whatsapp.MessageInfo) bool { - // Skip if the message has already been sent - if b.MessageWasSent(info.Id) { - return false - } - - // send if not from user - if !info.FromMe { - return true - } - - // If from user, only send when it is enabled in the config - return b.Config.App.ShowFromMe -} diff --git a/whappdc/message_handlers.go b/whappdc/message_handlers.go index 87ba14f..5a17467 100644 --- a/whappdc/message_handlers.go +++ b/whappdc/message_handlers.go @@ -17,228 +17,227 @@ type MessageHandler struct { type MessageAction func() error -func MakeTextMessageAction(b *core.BridgeContext, m whatsapp.TextMessage) MessageAction { +func MakeTextMessageAction(w *WhappContext, m whatsapp.TextMessage) MessageAction { return func() error { - if !b.ShouldMessageBeSent(m.Info) { + if !w.ShouldMessageBeSent(m.Info) { return nil } JID := m.Info.RemoteJid - DCID, err := b.GetOrCreateDCIDForJID(JID) + DCID, err := w.GetOrCreateDCIDForJID(JID) if err != nil { - b.SendLog(err.Error()) + w.BridgeCtx.SendLog(err.Error()) return err } - senderName := DetermineSenderName(b, m.Info) + senderName := DetermineSenderName(w.BridgeCtx, m.Info) - b.DCContext.SendTextMessage( + w.DCCtx().SendTextMessage( DCID, fmt.Sprintf("%s:\n%s", senderName, m.Text), ) - return b.MessageTracker.MarkSent(&m.Info.Id) + return w.MessageTracker.MarkSent(&m.Info.Id) } } -func MakeImageMessageAction(b *core.BridgeContext, m whatsapp.ImageMessage) MessageAction { +func MakeImageMessageAction(w *WhappContext, m whatsapp.ImageMessage) MessageAction { return func() error { - if !b.ShouldMessageBeSent(m.Info) { + if !w.ShouldMessageBeSent(m.Info) { return nil } JID := m.Info.RemoteJid - DCID, err := b.GetOrCreateDCIDForJID(JID) + DCID, err := w.GetOrCreateDCIDForJID(JID) if err != nil { - b.SendLog(err.Error()) + w.BridgeCtx.SendLog(err.Error()) return err } - senderName := DetermineSenderName(b, m.Info) + senderName := DetermineSenderName(w.BridgeCtx, m.Info) imageData, err := m.Download() if err != nil { - b.SendLog(err.Error()) + w.BridgeCtx.SendLog(err.Error()) return err } - filename, err := WriteTempFile(b, imageData, "img") + filename, err := WriteTempFile(w.BridgeCtx, imageData, "img") if err != nil { - b.SendLog(err.Error()) + w.BridgeCtx.SendLog(err.Error()) return err } - message := b.DCContext.NewMessage(deltachat.DC_MSG_IMAGE) + message := w.DCCtx().NewMessage(deltachat.DC_MSG_IMAGE) defer message.Unref() message.SetText(fmt.Sprintf("%s:\n%s", senderName, m.Caption)) message.SetFile(filename, m.Type) - b.DCContext.SendMessage(DCID, message) + w.DCCtx().SendMessage(DCID, message) - return b.MessageTracker.MarkSent(&m.Info.Id) + return w.MessageTracker.MarkSent(&m.Info.Id) } } -func MakeDocumentMessageAction(b *core.BridgeContext, m whatsapp.DocumentMessage) MessageAction { +func MakeDocumentMessageAction(w *WhappContext, m whatsapp.DocumentMessage) MessageAction { return func() error { - if !b.ShouldMessageBeSent(m.Info) { + if !w.ShouldMessageBeSent(m.Info) { return nil } JID := m.Info.RemoteJid - DCID, err := b.GetOrCreateDCIDForJID(JID) + DCID, err := w.GetOrCreateDCIDForJID(JID) if err != nil { - b.SendLog(err.Error()) + w.BridgeCtx.SendLog(err.Error()) return err } - senderName := DetermineSenderName(b, m.Info) + senderName := DetermineSenderName(w.BridgeCtx, m.Info) documentData, err := m.Download() if err != nil { - b.SendLog(err.Error()) + w.BridgeCtx.SendLog(err.Error()) return err } - filename, err := WriteTempFile(b, documentData, "doc") + filename, err := WriteTempFile(w.BridgeCtx, documentData, "doc") if err != nil { - b.SendLog(err.Error()) + w.BridgeCtx.SendLog(err.Error()) return err } - message := b.DCContext.NewMessage(deltachat.DC_MSG_FILE) + message := w.DCCtx().NewMessage(deltachat.DC_MSG_FILE) defer message.Unref() message.SetText(fmt.Sprintf("%s:\n%s", senderName, m.Title)) message.SetFile(filename, m.Type) - b.DCContext.SendMessage(DCID, message) + w.DCCtx().SendMessage(DCID, message) - return b.MessageTracker.MarkSent(&m.Info.Id) + return w.MessageTracker.MarkSent(&m.Info.Id) } } -func MakeAudioMessageAction(b *core.BridgeContext, m whatsapp.AudioMessage) MessageAction { +func MakeAudioMessageAction(w *WhappContext, m whatsapp.AudioMessage) MessageAction { return func() error { - if !b.ShouldMessageBeSent(m.Info) { + if !w.ShouldMessageBeSent(m.Info) { return nil } JID := m.Info.RemoteJid - DCID, err := b.GetOrCreateDCIDForJID(JID) + DCID, err := w.GetOrCreateDCIDForJID(JID) if err != nil { - b.SendLog(err.Error()) + w.BridgeCtx.SendLog(err.Error()) return err } - senderName := DetermineSenderName(b, m.Info) + senderName := DetermineSenderName(w.BridgeCtx, m.Info) audioData, err := m.Download() if err != nil { - b.SendLog(err.Error()) + w.BridgeCtx.SendLog(err.Error()) return err } - filename, err := WriteTempFile(b, audioData, "audio") + filename, err := WriteTempFile(w.BridgeCtx, audioData, "audio") if err != nil { - b.SendLog(err.Error()) + w.BridgeCtx.SendLog(err.Error()) return err } - message := b.DCContext.NewMessage(deltachat.DC_MSG_AUDIO) + message := w.DCCtx().NewMessage(deltachat.DC_MSG_AUDIO) defer message.Unref() message.SetText(fmt.Sprintf("%s:", senderName)) message.SetFile(filename, m.Type) - b.DCContext.SendMessage(DCID, message) + w.DCCtx().SendMessage(DCID, message) - return b.MessageTracker.MarkSent(&m.Info.Id) + return w.MessageTracker.MarkSent(&m.Info.Id) } } -func MakeVideoMessageAction(b *core.BridgeContext, m whatsapp.VideoMessage) MessageAction { +func MakeVideoMessageAction(w *WhappContext, m whatsapp.VideoMessage) MessageAction { return func() error { - if !b.ShouldMessageBeSent(m.Info) { + if !w.ShouldMessageBeSent(m.Info) { return nil } JID := m.Info.RemoteJid - DCID, err := b.GetOrCreateDCIDForJID(JID) + DCID, err := w.GetOrCreateDCIDForJID(JID) if err != nil { - b.SendLog(err.Error()) + w.BridgeCtx.SendLog(err.Error()) return err } - senderName := DetermineSenderName(b, m.Info) + senderName := DetermineSenderName(w.BridgeCtx, m.Info) videoData, err := m.Download() if err != nil { - b.SendLog(err.Error()) + w.BridgeCtx.SendLog(err.Error()) return err } - filename, err := WriteTempFile(b, videoData, "vid") + filename, err := WriteTempFile(w.BridgeCtx, videoData, "vid") if err != nil { - b.SendLog(err.Error()) + w.BridgeCtx.SendLog(err.Error()) return err } - message := b.DCContext.NewMessage(deltachat.DC_MSG_VIDEO) + message := w.DCCtx().NewMessage(deltachat.DC_MSG_VIDEO) defer message.Unref() message.SetText(fmt.Sprintf("%s:", senderName)) message.SetFile(filename, m.Type) - b.DCContext.SendMessage(DCID, message) + w.DCCtx().SendMessage(DCID, message) - return b.MessageTracker.MarkSent(&m.Info.Id) + return w.MessageTracker.MarkSent(&m.Info.Id) } } // 2020-01-14 10:34 TODO: Find out why this doesn't work. -func MakeContactMessageAction(b *core.BridgeContext, m whatsapp.ContactMessage) MessageAction { +func MakeContactMessageAction(w *WhappContext, m whatsapp.ContactMessage) MessageAction { return func() error { - if !b.ShouldMessageBeSent(m.Info) { + if !w.ShouldMessageBeSent(m.Info) { return nil } JID := m.Info.RemoteJid - DCID, err := b.GetOrCreateDCIDForJID(JID) + DCID, err := w.GetOrCreateDCIDForJID(JID) if err != nil { - b.SendLog(err.Error()) + w.BridgeCtx.SendLog(err.Error()) return err } - senderName := DetermineSenderName(b, m.Info) + senderName := DetermineSenderName(w.BridgeCtx, m.Info) - filename, err := WriteTempFile(b, []byte(m.Vcard), "vcf") + filename, err := WriteTempFile(w.BridgeCtx, []byte(m.Vcard), "vcf") if err != nil { - b.SendLog(err.Error()) + w.BridgeCtx.SendLog(err.Error()) return err } - message := b.DCContext.NewMessage(deltachat.DC_MSG_FILE) + message := w.DCCtx().NewMessage(deltachat.DC_MSG_FILE) defer message.Unref() message.SetText(fmt.Sprintf("%s:", senderName)) message.SetFile(filename, mime.TypeByExtension(".vcf")) - b.DCContext.SendMessage(DCID, message) - - return b.MessageTracker.MarkSent(&m.Info.Id) + w.DCCtx().SendMessage(DCID, message) + return w.MessageTracker.MarkSent(&m.Info.Id) } } diff --git a/whappdc-core/message_tracker.go b/whappdc/message_tracker.go similarity index 88% rename from whappdc-core/message_tracker.go rename to whappdc/message_tracker.go index edc712b..56f5c63 100644 --- a/whappdc-core/message_tracker.go +++ b/whappdc/message_tracker.go @@ -1,12 +1,14 @@ -package core +package whappdc import ( "log" "sync" "time" + + core "github.com/hugot/whapp-deltachat/whappdc-core" ) -func NewMessageTracker(DB *Database, flushInterval time.Duration) *MessageTracker { +func NewMessageTracker(DB *core.Database, flushInterval time.Duration) *MessageTracker { tracker := &MessageTracker{ DB: DB, } @@ -21,7 +23,7 @@ func NewMessageTracker(DB *Database, flushInterval time.Duration) *MessageTracke // that calling WasSent immediately after calling MarkSent will most likely not return an // up to date answer. type MessageTracker struct { - DB *Database + DB *core.Database delivered [80]*string deliveredMutex sync.RWMutex deliveredIdx int diff --git a/whappdc/message_worker.go b/whappdc/message_worker.go index 1619adf..9344ce6 100644 --- a/whappdc/message_worker.go +++ b/whappdc/message_worker.go @@ -5,12 +5,14 @@ import "log" type MessageWorker struct { incomingHandlers chan MessageHandler chatWorkers map[string]chan MessageHandler + quit chan bool } func NewMessageWorker() *MessageWorker { return &MessageWorker{ incomingHandlers: make(chan MessageHandler), chatWorkers: make(map[string]chan MessageHandler), + quit: make(chan bool), } } @@ -18,10 +20,16 @@ func (w *MessageWorker) HandleMessage(m MessageHandler) { w.incomingHandlers <- m } +func (w *MessageWorker) Stop() { + w.quit <- true +} + func (w *MessageWorker) Start() { go func() { for { select { + case <-w.quit: + return case handler := <-w.incomingHandlers: log.Println("Got Handler for " + handler.Jid) workerChan, ok := w.chatWorkers[handler.Jid] diff --git a/whappdc/whapp_context.go b/whappdc/whapp_context.go new file mode 100644 index 0000000..c20710f --- /dev/null +++ b/whappdc/whapp_context.go @@ -0,0 +1,93 @@ +package whappdc + +import ( + "log" + "time" + + "github.com/Rhymen/go-whatsapp" + "github.com/hugot/go-deltachat/deltachat" + core "github.com/hugot/whapp-deltachat/whappdc-core" +) + +func NewWhappContext( + bridgeCtx *core.BridgeContext, + msgTrackerFlushInterval time.Duration, +) *WhappContext { + messageTracker := NewMessageTracker(bridgeCtx.DB, msgTrackerFlushInterval) + + return &WhappContext{ + BridgeCtx: bridgeCtx, + MessageTracker: messageTracker, + } +} + +type WhappContext struct { + BridgeCtx *core.BridgeContext + MessageTracker *MessageTracker +} + +// Flushes the message tracker. While this method is called "Close", it currently doesn't +// make the context unusable. It might do so in the future though so no reason to change +// the name atm. +func (w *WhappContext) Close() error { + return w.MessageTracker.Flush() +} + +// Find or create a deltachat verified group chat for a whatsapp JID and return it's ID. +func (w *WhappContext) GetOrCreateDCIDForJID(JID string) (uint32, error) { + if DCID, _ := w.BridgeCtx.DB.GetDCIDForWhappJID(JID); DCID != nil { + return *DCID, nil + } + + chatName := JID + chat, ok := w.BridgeCtx.WhappConn.Store.Chats[JID] + + if ok { + chatName = chat.Name + } else if sender, ok := w.BridgeCtx.WhappConn.Store.Contacts[JID]; ok { + chatName = sender.Name + } + + DCID := w.BridgeCtx.DCContext.CreateGroupChat(true, chatName) + + err := w.BridgeCtx.DB.StoreDCIDForJID(JID, DCID) + + if err != nil { + return DCID, err + } + + w.BridgeCtx.DCContext.AddContactToChat(DCID, w.BridgeCtx.DCUserID) + + return DCID, err +} + +func (w *WhappContext) MessageWasSent(ID string) bool { + sent, err := w.MessageTracker.WasSent(ID) + + if err != nil { + log.Println(err) + w.BridgeCtx.SendLog(err.Error()) + } + + return sent +} + +func (w *WhappContext) ShouldMessageBeSent(info whatsapp.MessageInfo) bool { + // Skip if the message has already been sent + if w.MessageWasSent(info.Id) { + return false + } + + // send if not from user + if !info.FromMe { + return true + } + + // If from user, only send when it is enabled in the config + return w.BridgeCtx.Config.App.ShowFromMe +} + +// Alias for easy access of DC Context +func (w *WhappContext) DCCtx() *deltachat.Context { + return w.BridgeCtx.DCContext +} diff --git a/whappdc/whapp_handler.go b/whappdc/whapp_handler.go index 4946360..9660f34 100644 --- a/whappdc/whapp_handler.go +++ b/whappdc/whapp_handler.go @@ -3,35 +3,48 @@ package whappdc import ( "fmt" "log" + "time" "github.com/Rhymen/go-whatsapp" core "github.com/hugot/whapp-deltachat/whappdc-core" ) type WhappHandler struct { - BridgeContext *core.BridgeContext + WhappCtx *WhappContext MessageWorker *MessageWorker } -func NewWhappHandler(bridgeCtx *core.BridgeContext, messageWorker *MessageWorker) *WhappHandler { +func NewWhappHandler( + bridgeCtx *core.BridgeContext, + msgTrackerFlushInterval time.Duration, +) *WhappHandler { return &WhappHandler{ - BridgeContext: bridgeCtx, - MessageWorker: messageWorker, + WhappCtx: NewWhappContext(bridgeCtx, msgTrackerFlushInterval), + MessageWorker: NewMessageWorker(), } } +func (h *WhappHandler) Start() { + h.MessageWorker.Start() +} + +func (h *WhappHandler) Stop() error { + h.MessageWorker.Stop() + return h.WhappCtx.Close() +} + func (h *WhappHandler) HandleError(err error) { // If connection to the whapp servers failed for some reason, just retry. if _, connectionFailed := err.(*whatsapp.ErrConnectionFailed); connectionFailed { err = core.RestoreWhappSessionFromStorage( - h.BridgeContext.Config.App.DataFolder, - h.BridgeContext.WhappConn, + h.WhappCtx.BridgeCtx.Config.App.DataFolder, + h.WhappCtx.BridgeCtx.WhappConn, ) if err != nil { logString := "Failed to restore whatsapp connection: " + err.Error() log.Println(logString) - h.BridgeContext.SendLog(logString) + h.WhappCtx.BridgeCtx.SendLog(logString) } return @@ -47,14 +60,14 @@ func (h *WhappHandler) HandleError(err error) { // Invalid ws data seems to be pretty common, let's not bore the user with that.xg if err.Error() != "error processing data: "+whatsapp.ErrInvalidWsData.Error() { - h.BridgeContext.SendLog(logString) + h.WhappCtx.BridgeCtx.SendLog(logString) } } func (h *WhappHandler) HandleTextMessage(m whatsapp.TextMessage) { handler := MessageHandler{ Jid: m.Info.RemoteJid, - Action: MakeTextMessageAction(h.BridgeContext, m), + Action: MakeTextMessageAction(h.WhappCtx, m), } h.MessageWorker.HandleMessage(handler) @@ -63,7 +76,7 @@ func (h *WhappHandler) HandleTextMessage(m whatsapp.TextMessage) { func (h *WhappHandler) HandleImageMessage(m whatsapp.ImageMessage) { handler := MessageHandler{ Jid: m.Info.RemoteJid, - Action: MakeImageMessageAction(h.BridgeContext, m), + Action: MakeImageMessageAction(h.WhappCtx, m), } h.MessageWorker.HandleMessage(handler) @@ -72,7 +85,7 @@ func (h *WhappHandler) HandleImageMessage(m whatsapp.ImageMessage) { func (h *WhappHandler) HandleDocumentMessage(m whatsapp.DocumentMessage) { handler := MessageHandler{ Jid: m.Info.RemoteJid, - Action: MakeDocumentMessageAction(h.BridgeContext, m), + Action: MakeDocumentMessageAction(h.WhappCtx, m), } h.MessageWorker.HandleMessage(handler) @@ -81,7 +94,7 @@ func (h *WhappHandler) HandleDocumentMessage(m whatsapp.DocumentMessage) { func (h *WhappHandler) HandleAudioMessage(m whatsapp.AudioMessage) { handler := MessageHandler{ Jid: m.Info.RemoteJid, - Action: MakeAudioMessageAction(h.BridgeContext, m), + Action: MakeAudioMessageAction(h.WhappCtx, m), } h.MessageWorker.HandleMessage(handler) @@ -90,7 +103,7 @@ func (h *WhappHandler) HandleAudioMessage(m whatsapp.AudioMessage) { func (h *WhappHandler) HandleVideoMessage(m whatsapp.VideoMessage) { handler := MessageHandler{ Jid: m.Info.RemoteJid, - Action: MakeVideoMessageAction(h.BridgeContext, m), + Action: MakeVideoMessageAction(h.WhappCtx, m), } h.MessageWorker.HandleMessage(handler) @@ -99,7 +112,7 @@ func (h *WhappHandler) HandleVideoMessage(m whatsapp.VideoMessage) { func (h *WhappHandler) HandleContactMessage(m whatsapp.VideoMessage) { handler := MessageHandler{ Jid: m.Info.RemoteJid, - Action: MakeVideoMessageAction(h.BridgeContext, m), + Action: MakeVideoMessageAction(h.WhappCtx, m), } h.MessageWorker.HandleMessage(handler)