diff --git a/bridge_context.go b/bridge_context.go new file mode 100644 index 0000000..b466bd4 --- /dev/null +++ b/bridge_context.go @@ -0,0 +1,52 @@ +package main + +import ( + "github.com/Rhymen/go-whatsapp" + "github.com/hugot/go-deltachat/deltachat" +) + +type BridgeContext struct { + WhappConn *whatsapp.Conn + DCContext *deltachat.Context + DB *Database + DCUserID uint32 + DCUserChatID uint32 +} + +// Find or create a deltachat verified group chat for a whatsapp JID and return it's ID. +func (b *BridgeContext) GetOrCreateDCIDForJID(JID string, isGroup bool) (uint32, error) { + if DCID, _ := b.DB.GetDCIDForWhappJID(JID); DCID != nil { + return *DCID, nil + } + + chatName := JID + if isGroup { + chat, ok := b.WhappConn.Store.Chats[JID] + + if ok { + chatName = chat.Name + } + } else { + contact, ok := b.WhappConn.Store.Contacts[JID] + + if ok { + chatName = contact.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) +} diff --git a/chat_worker.go b/chat_worker.go new file mode 100644 index 0000000..5116fec --- /dev/null +++ b/chat_worker.go @@ -0,0 +1,23 @@ +package main + +import "log" + +type ChatWorker struct { + incomingHandlers chan MessageHandler +} + +func (w *ChatWorker) Start() { + go func() { + for { + select { + case handler := <-w.incomingHandlers: + log.Println("Chat worker executing action") + err := handler.Action() + + if err != nil { + log.Println(err) + } + } + } + }() +} diff --git a/deltachat.go b/deltachat.go index bad2889..b69454d 100644 --- a/deltachat.go +++ b/deltachat.go @@ -1,6 +1,7 @@ package main import ( + "encoding/binary" "errors" "fmt" "log" @@ -42,17 +43,37 @@ func DcClientFromConfig(databasePath string, config map[string]string) *deltacha return client } -func BootstrapDcClientFromConfig(config Config) *deltachat.Client { +func BootstrapDcClientFromConfig(config Config, ctx *BridgeContext) (*deltachat.Client, error) { dcClient := DcClientFromConfig(config.App.DataFolder+"/deltachat.db", config.Deltachat) - context := dcClient.Context() + DCCtx := dcClient.Context() userName := "user" + dcUserID := DCCtx.CreateContact(&userName, &config.App.UserAddress) - userID := context.CreateContact(&userName, &config.App.UserAddress) + userChatIDRaw := ctx.DB.Get([]byte("user-chat-id")) + var ( + userChatID uint32 + err error + ) + + if userChatIDRaw == nil { + userChatID, err = AddUserAsVerifiedContact(dcUserID, dcClient) + if err != nil { + return nil, err + } + } else { + userChatID = binary.LittleEndian.Uint32(userChatIDRaw) + } + + userChatIDbs := make([]byte, 4) + binary.LittleEndian.PutUint32(userChatIDbs, userChatID) + err = ctx.DB.Put([]byte("user-chat-id"), userChatIDbs) - context.SendTextMessage(context.CreateChatByContactID(userID), "Whapp-Deltachat initialized") + ctx.DCUserID = dcUserID + ctx.DCUserChatID = userChatID + ctx.DCContext = DCCtx - return dcClient + return dcClient, err } // Add a user as verified contact to the deltachat context. This is necessary to be able diff --git a/main.go b/main.go index 68979a9..839b788 100644 --- a/main.go +++ b/main.go @@ -1,13 +1,11 @@ package main import ( - "encoding/binary" "fmt" "log" "os" "os/signal" - "github.com/Rhymen/go-whatsapp" "github.com/hugot/go-deltachat/deltabot" "github.com/hugot/go-deltachat/deltachat" "github.com/hugot/whapp-deltachat/botcommands" @@ -32,12 +30,6 @@ func main() { ensureDirectoryOrDie(config.App.DataFolder) ensureDirectoryOrDie(config.App.DataFolder + "/tmp") - dcClient := BootstrapDcClientFromConfig(*config) - - // Give dc an opportunity to perform some close-down logic - // and close it's db etc. - defer dcClient.Close() - db := &Database{ dbPath: config.App.DataFolder + "/app.db", } @@ -48,37 +40,24 @@ func main() { log.Fatal(err) } - ctx := dcClient.Context() - userName := "user" - dcUserID := ctx.CreateContact(&userName, &config.App.UserAddress) - - userChatIDRaw := db.Get([]byte("user-chat-id")) - var userChatID uint32 - - if userChatIDRaw == nil { - userChatID, err = AddUserAsVerifiedContact(dcUserID, dcClient) - if err != nil { - log.Fatal(err) - } - } else { - userChatID = binary.LittleEndian.Uint32(userChatIDRaw) + bridgeCtx := &BridgeContext{ + DB: db, } - userChatIDbs := make([]byte, 4) - binary.LittleEndian.PutUint32(userChatIDbs, userChatID) - err = db.Put([]byte("user-chat-id"), userChatIDbs) + dcClient, err := BootstrapDcClientFromConfig(*config, bridgeCtx) if err != nil { log.Fatal(err) } - var wac *whatsapp.Conn + // Give dc an opportunity to perform some close-down logic + // and close it's db etc. + defer dcClient.Close() for i := 0; i < 10; i++ { - wac, err = CreateAndLoginWhappConnection( + err = CreateAndLoginWhappConnection( config.App.DataFolder, - dcClient.Context(), - dcUserID, + bridgeCtx, ) if err == nil { @@ -90,18 +69,19 @@ func main() { log.Fatal(err) } - wac.AddHandler(&WhappHandler{ - dcContext: dcClient.Context(), - db: db, - dcUserID: dcUserID, - wac: wac, + messageWorker := NewMessageWorker() + messageWorker.Start() + + bridgeCtx.WhappConn.AddHandler(&WhappHandler{ + BridgeContext: bridgeCtx, + MessageWorker: messageWorker, }) bot := &deltabot.Bot{} bot.AddCommand(&botcommands.Echo{}) bot.AddCommand(botcommands.NewWhappBridge( - wac, db, dcClient.Context().GetChatIDByContactID(dcUserID), + bridgeCtx.WhappConn, bridgeCtx.DB, bridgeCtx.DCUserChatID, )) dcClient.On(deltachat.DC_EVENT_INCOMING_MSG, bot.HandleMessage) diff --git a/message_handlers.go b/message_handlers.go new file mode 100644 index 0000000..f0ea219 --- /dev/null +++ b/message_handlers.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "log" + + "github.com/Rhymen/go-whatsapp" +) + +type MessageHandler struct { + Action MessageAction + Jid string +} + +type MessageAction func() error + +func MakeTextMessageAction(b *BridgeContext, m whatsapp.TextMessage) MessageAction { + return func() error { + JID := m.Info.RemoteJid + + DCID, err := b.GetOrCreateDCIDForJID(JID, m.Info.RemoteJid != m.Info.SenderJid) + + if err != nil { + log.Println(err) + b.SendLog(err.Error()) + } + + senderName := m.Info.Source.GetParticipant() + contact, ok := b.WhappConn.Store.Contacts[senderName] + if ok { + senderName = contact.Name + } + + b.DCContext.SendTextMessage( + DCID, + fmt.Sprintf("%s:\n%s", senderName, m.Text), + ) + + return nil + } +} diff --git a/message_worker.go b/message_worker.go new file mode 100644 index 0000000..928da2e --- /dev/null +++ b/message_worker.go @@ -0,0 +1,44 @@ +package main + +import "log" + +type MessageWorker struct { + incomingHandlers chan MessageHandler + chatWorkers map[string]chan MessageHandler +} + +func NewMessageWorker() *MessageWorker { + return &MessageWorker{ + incomingHandlers: make(chan MessageHandler), + chatWorkers: make(map[string]chan MessageHandler), + } +} + +func (w *MessageWorker) HandleMessage(m MessageHandler) { + w.incomingHandlers <- m +} + +func (w *MessageWorker) Start() { + go func() { + for { + select { + case handler := <-w.incomingHandlers: + log.Println("Got Handler for " + handler.Jid) + workerChan, ok := w.chatWorkers[handler.Jid] + + if !ok { + workerChan = make(chan MessageHandler) + + worker := &ChatWorker{ + incomingHandlers: workerChan, + } + + worker.Start() + w.chatWorkers[handler.Jid] = workerChan + } + + workerChan <- handler + } + } + }() +} diff --git a/whapp-handler.go b/whapp-handler.go index d7d354c..2144cc1 100644 --- a/whapp-handler.go +++ b/whapp-handler.go @@ -1,52 +1,14 @@ package main import ( - "fmt" "log" "github.com/Rhymen/go-whatsapp" - "github.com/hugot/go-deltachat/deltachat" ) type WhappHandler struct { - dcContext *deltachat.Context - db *Database - dcUserID uint32 - wac *whatsapp.Conn -} - -// Find or create a deltachat verified group chat for a whatsapp JID and return it's ID. -func (h *WhappHandler) getOrCreateDCIDForJID(JID string, isGroup bool) (uint32, error) { - if DCID, _ := h.db.GetDCIDForWhappJID(JID); DCID != nil { - return *DCID, nil - } - - chatName := JID - if isGroup { - chat, ok := h.wac.Store.Chats[JID] - - if ok { - chatName = chat.Name - } - } else { - contact, ok := h.wac.Store.Contacts[JID] - - if ok { - chatName = contact.Name - } - } - - DCID := h.dcContext.CreateGroupChat(true, chatName) - - err := h.db.StoreDCIDForJID(JID, DCID) - - if err != nil { - return DCID, err - } - - h.dcContext.AddContactToChat(DCID, h.dcUserID) - - return DCID, err + BridgeContext *BridgeContext + MessageWorker *MessageWorker } func (h *WhappHandler) HandleError(err error) { @@ -54,28 +16,10 @@ func (h *WhappHandler) HandleError(err error) { } func (h *WhappHandler) HandleTextMessage(m whatsapp.TextMessage) { - JID := m.Info.RemoteJid - - DCID, err := h.getOrCreateDCIDForJID(JID, m.Info.RemoteJid != m.Info.SenderJid) - - if err != nil { - log.Println(err) - - chatID := h.dcContext.GetChatIDByContactID(h.dcUserID) - h.dcContext.SendTextMessage( - chatID, - err.Error(), - ) - } - - senderName := m.Info.SenderJid - contact, ok := h.wac.Store.Contacts[m.Info.SenderJid] - if ok { - senderName = contact.Name + handler := MessageHandler{ + Jid: m.Info.RemoteJid, + Action: MakeTextMessageAction(h.BridgeContext, m), } - h.dcContext.SendTextMessage( - DCID, - fmt.Sprintf("%s:\n%s", senderName, m.Text), - ) + h.MessageWorker.HandleMessage(handler) } diff --git a/whapp.go b/whapp.go index 31218bd..85e2dd3 100644 --- a/whapp.go +++ b/whapp.go @@ -15,23 +15,24 @@ import ( func CreateAndLoginWhappConnection( storageDir string, - dcContext *deltachat.Context, - dcUserID uint32, -) (*whatsapp.Conn, error) { - wac, err := whatsapp.NewConn(20 * time.Second) + ctx *BridgeContext, +) error { + wac, err := whatsapp.NewConn(300 * time.Second) if err != nil { - return wac, err + return err } + ctx.WhappConn = wac + var session whatsapp.Session sessionFile := storageDir + "/whapp-session.json" if _, err := os.Stat(sessionFile); os.IsNotExist(err) { - session, err = WhappQrLogin(storageDir, wac, dcContext, dcUserID) + session, err = WhappQrLogin(storageDir, ctx) if err != nil { - return wac, err + return err } } else { session = whatsapp.Session{} @@ -41,26 +42,24 @@ func CreateAndLoginWhappConnection( err = json.Unmarshal(sessionJson, &session) if err != nil { - return wac, err + return err } session, err = wac.RestoreWithSession(session) if err != nil { - return wac, err + return err } } err = StoreWhappSession(session, storageDir) - return wac, err + return err } func WhappQrLogin( storageDir string, - wac *whatsapp.Conn, - dcContext *deltachat.Context, - dcUserID uint32, + ctx *BridgeContext, ) (whatsapp.Session, error) { qrChan := make(chan string) @@ -79,21 +78,21 @@ func WhappQrLogin( log.Fatal(err) } - message := dcContext.NewMessage(deltachat.DC_MSG_IMAGE) + message := ctx.DCContext.NewMessage(deltachat.DC_MSG_IMAGE) - log.Println("MIME: " + mime.TypeByExtension("png")) + log.Println("MIME: " + mime.TypeByExtension(".png")) message.SetFile(tmpFile.Name(), "image/png") message.SetText("Scan this QR code from whatsapp") - dcContext.SendMessage( - dcContext.GetChatIDByContactID(dcUserID), + ctx.DCContext.SendMessage( + ctx.DCUserChatID, message, ) }() - session, err := wac.Login(qrChan) + session, err := ctx.WhappConn.Login(qrChan) return session, err }