Make whappdc package more self-contained

It's better to have whapp -> DC direction bridge code separate in the whappdc package.

Moved MessageTracker + some BridgeContext methods and made WhappHandler
start-and-stoppable.
master
Hugo Thunnissen 4 years ago
parent e392242c79
commit e14614da9e

@ -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()
}

@ -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
}

@ -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)
}
}

@ -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

@ -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]

@ -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
}

@ -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)

Loading…
Cancel
Save