Quellcode durchsuchen

Add puppet and portal stuff and fix config stuff

Tulir Asokan vor 6 Jahren
Ursprung
Commit
9c48eeb534
12 geänderte Dateien mit 474 neuen und 30 gelöschten Zeilen
  1. 3 0
      .gitignore
  2. 14 12
      config/bridge.go
  3. 4 1
      config/registration.go
  4. 11 1
      database/database.go
  5. 2 2
      database/portal.go
  6. 98 0
      database/puppet.go
  7. 27 11
      database/user.go
  8. 1 1
      example-config.yaml
  9. 5 1
      main.go
  10. 62 1
      matrix.go
  11. 89 0
      portal.go
  12. 158 0
      user.go

+ 3 - 0
.gitignore

@@ -1,2 +1,5 @@
 .idea
 *.session
+
+*.yaml
+!example-config.yaml

+ 14 - 12
config/bridge.go

@@ -22,24 +22,26 @@ import (
 )
 
 type BridgeConfig struct {
-	RawUsernameTemplate    string             `yaml:"username_template"`
-	RawDisplaynameTemplate string             `yaml:"displayname_template"`
-	UsernameTemplate       *template.Template `yaml:"-"`
-	DisplaynameTemplate    *template.Template `yaml:"-"`
+	UsernameTemplate    string             `yaml:"username_template"`
+	DisplaynameTemplate string             `yaml:"displayname_template"`
+	usernameTemplate    *template.Template `yaml:"-"`
+	displaynameTemplate *template.Template `yaml:"-"`
 }
 
-func (bc BridgeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
-	err := unmarshal(bc)
+type umBridgeConfig BridgeConfig
+
+func (bc *BridgeConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
+	err := unmarshal((*umBridgeConfig)(bc))
 	if err != nil {
 		return err
 	}
 
-	bc.UsernameTemplate, err = template.New("username").Parse(bc.RawUsernameTemplate)
+	bc.usernameTemplate, err = template.New("username").Parse(bc.UsernameTemplate)
 	if err != nil {
 		return err
 	}
 
-	bc.DisplaynameTemplate, err = template.New("displayname").Parse(bc.RawDisplaynameTemplate)
+	bc.displaynameTemplate, err = template.New("displayname").Parse(bc.DisplaynameTemplate)
 	return err
 }
 
@@ -54,7 +56,7 @@ type UsernameTemplateArgs struct {
 
 func (bc BridgeConfig) FormatDisplayname(displayname string) string {
 	var buf bytes.Buffer
-	bc.DisplaynameTemplate.Execute(&buf, DisplaynameTemplateArgs{
+	bc.displaynameTemplate.Execute(&buf, DisplaynameTemplateArgs{
 		Displayname: displayname,
 	})
 	return buf.String()
@@ -62,7 +64,7 @@ func (bc BridgeConfig) FormatDisplayname(displayname string) string {
 
 func (bc BridgeConfig) FormatUsername(receiver, userID string) string {
 	var buf bytes.Buffer
-	bc.UsernameTemplate.Execute(&buf, UsernameTemplateArgs{
+	bc.usernameTemplate.Execute(&buf, UsernameTemplateArgs{
 		Receiver: receiver,
 		UserID:   userID,
 	})
@@ -70,7 +72,7 @@ func (bc BridgeConfig) FormatUsername(receiver, userID string) string {
 }
 
 func (bc BridgeConfig) MarshalYAML() (interface{}, error) {
-	bc.RawDisplaynameTemplate = bc.FormatDisplayname("{{.Displayname}}")
-	bc.RawUsernameTemplate = bc.FormatUsername("{{.Receiver}}", "{{.UserID}}")
+	bc.DisplaynameTemplate = bc.FormatDisplayname("{{.Displayname}}")
+	bc.UsernameTemplate = bc.FormatUsername("{{.Receiver}}", "{{.UserID}}")
 	return bc, nil
 }

+ 4 - 1
config/registration.go

@@ -19,6 +19,7 @@ package config
 import (
 	"maunium.net/go/mautrix-appservice"
 	"regexp"
+	"fmt"
 )
 
 func (config *Config) NewRegistration() (*appservice.Registration, error) {
@@ -53,7 +54,9 @@ func (config *Config) copyToRegistration(registration *appservice.Registration)
 	registration.RateLimited = false
 	registration.SenderLocalpart = config.AppService.Bot.Username
 
-	userIDRegex, err := regexp.Compile(config.Bridge.FormatUsername("[0-9]+", "[0-9]+"))
+	userIDRegex, err := regexp.Compile(fmt.Sprintf("@%s:%s",
+		config.Bridge.FormatUsername("[0-9]+", "[0-9]+"),
+		config.Homeserver.Domain))
 	if err != nil {
 		return err
 	}

+ 11 - 1
database/database.go

@@ -26,7 +26,9 @@ type Database struct {
 	*sql.DB
 	log *log.Sublogger
 
-	User *UserQuery
+	User   *UserQuery
+	Portal *PortalQuery
+	Puppet *PuppetQuery
 }
 
 func New(file string) (*Database, error) {
@@ -43,6 +45,14 @@ func New(file string) (*Database, error) {
 		db:  db,
 		log: log.CreateSublogger("Database/User", log.LevelDebug),
 	}
+	db.Portal = &PortalQuery{
+		db:  db,
+		log: log.CreateSublogger("Database/Portal", log.LevelDebug),
+	}
+	db.Puppet = &PuppetQuery{
+		db:  db,
+		log: log.CreateSublogger("Database/Puppet", log.LevelDebug),
+	}
 	return db, nil
 }
 

+ 2 - 2
database/portal.go

@@ -44,8 +44,8 @@ func (pq *PortalQuery) New() *Portal {
 	}
 }
 
-func (pq *PortalQuery) GetAll() (portals []*Portal) {
-	rows, err := pq.db.Query("SELECT * FROM portal")
+func (pq *PortalQuery) GetAll(owner string) (portals []*Portal) {
+	rows, err := pq.db.Query("SELECT * FROM portal WHERE owner=?", owner)
 	if err != nil || rows == nil {
 		return nil
 	}

+ 98 - 0
database/puppet.go

@@ -0,0 +1,98 @@
+// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
+// Copyright (C) 2018 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package database
+
+import (
+	log "maunium.net/go/maulogger"
+)
+
+type PuppetQuery struct {
+	db  *Database
+	log *log.Sublogger
+}
+
+func (pq *PuppetQuery) CreateTable() error {
+	_, err := pq.db.Exec(`CREATE TABLE IF NOT EXISTS puppet (
+		jid      VARCHAR(255),
+		receiver VARCHAR(255),
+
+		displayname VARCHAR(255),
+		avatar      VARCHAR(255),
+
+		PRIMARY KEY(jid, receiver)
+	)`)
+	return err
+}
+
+func (pq *PuppetQuery) New() *Puppet {
+	return &Puppet{
+		db:  pq.db,
+		log: pq.log,
+	}
+}
+
+func (pq *PuppetQuery) GetAll() (puppets []*Puppet) {
+	rows, err := pq.db.Query("SELECT * FROM puppet")
+	if err != nil || rows == nil {
+		return nil
+	}
+	defer rows.Close()
+	for rows.Next() {
+		puppets = append(puppets, pq.New().Scan(rows))
+	}
+	return
+}
+
+func (pq *PuppetQuery) Get(jid, receiver string) *Puppet {
+	row := pq.db.QueryRow("SELECT * FROM user WHERE jid=? AND receiver=?", jid, receiver)
+	if row == nil {
+		return nil
+	}
+	return pq.New().Scan(row)
+}
+
+type Puppet struct {
+	db  *Database
+	log *log.Sublogger
+
+	JID      string
+	Receiver string
+
+	Displayname string
+	Avatar      string
+}
+
+func (puppet *Puppet) Scan(row Scannable) *Puppet {
+	err := row.Scan(&puppet.JID, &puppet.Receiver, &puppet.Displayname, &puppet.Avatar)
+	if err != nil {
+		puppet.log.Fatalln("Database scan failed:", err)
+	}
+	return puppet
+}
+
+func (puppet *Puppet) Insert() error {
+	_, err := puppet.db.Exec("INSERT INTO puppet VALUES (?, ?, ?, ?)",
+		puppet.JID, puppet.Receiver, puppet.Displayname, puppet.Avatar)
+	return err
+}
+
+func (puppet *Puppet) Update() error {
+	_, err := puppet.db.Exec("UPDATE puppet SET displayname=?, avatar=? WHERE jid=? AND receiver=?",
+		puppet.Displayname, puppet.Avatar,
+		puppet.JID, puppet.Receiver)
+	return err
+}

+ 27 - 11
database/user.go

@@ -28,7 +28,9 @@ type UserQuery struct {
 
 func (uq *UserQuery) CreateTable() error {
 	_, err := uq.db.Exec(`CREATE TABLE IF NOT EXISTS user (
-		mxid  VARCHAR(255) PRIMARY KEY,
+		mxid VARCHAR(255) PRIMARY KEY,
+
+		management_room VARCHAR(255),
 
 		client_id    VARCHAR(255),
 		client_token VARCHAR(255),
@@ -71,29 +73,43 @@ type User struct {
 	db  *Database
 	log *log.Sublogger
 
-	UserID string
-
-	session whatsapp.Session
+	UserID         string
+	ManagementRoom string
+	Session        *whatsapp.Session
 }
 
 func (user *User) Scan(row Scannable) *User {
-	err := row.Scan(&user.UserID, &user.session.ClientId, &user.session.ClientToken, &user.session.ServerToken,
-		&user.session.EncKey, &user.session.MacKey, &user.session.Wid)
+	sess := whatsapp.Session{}
+	err := row.Scan(&user.UserID, &user.ManagementRoom, &sess.ClientId, &sess.ClientToken, &sess.ServerToken,
+		&sess.EncKey, &sess.MacKey, &sess.Wid)
 	if err != nil {
 		user.log.Fatalln("Database scan failed:", err)
 	}
+	if len(sess.ClientId) > 0 {
+		user.Session = &sess
+	} else {
+		user.Session = nil
+	}
 	return user
 }
 
 func (user *User) Insert() error {
-	_, err := user.db.Exec("INSERT INTO user VALUES (?, ?, ?, ?, ?, ?, ?)", user.UserID, user.session.ClientId,
-		user.session.ClientToken, user.session.ServerToken, user.session.EncKey, user.session.MacKey, user.session.Wid)
+	var sess whatsapp.Session
+	if user.Session != nil {
+		sess = *user.Session
+	}
+	_, err := user.db.Exec("INSERT INTO user VALUES (?, ?, ?, ?, ?, ?, ?, ?)", user.UserID, user.ManagementRoom,
+		sess.ClientId, sess.ClientToken, sess.ServerToken, sess.EncKey, sess.MacKey, sess.Wid)
 	return err
 }
 
 func (user *User) Update() error {
-	_, err := user.db.Exec("UPDATE user SET client_id=?, client_token=?, server_token=?, enc_key=?, mac_key=?, wid=? WHERE mxid=?",
-		user.session.ClientId, user.session.ClientToken, user.session.ServerToken, user.session.EncKey, user.session.MacKey,
-		user.session.Wid, user.UserID)
+	var sess whatsapp.Session
+	if user.Session != nil {
+		sess = *user.Session
+	}
+	_, err := user.db.Exec("UPDATE user SET management_room=?, client_id=?, client_token=?, server_token=?, enc_key=?, mac_key=?, wid=? WHERE mxid=?",
+		user.ManagementRoom,
+		sess.ClientId, sess.ClientToken, sess.ServerToken, sess.EncKey, sess.MacKey, sess.Wid, user.UserID)
 	return err
 }

+ 1 - 1
example-config.yaml

@@ -52,7 +52,7 @@ logging:
   # The directory for log files. Will be created if not found.
   directory: ./logs
   # Available variables: .date for the file date and .index for different log files on the same day.
-  file_name_format: {{.date}}-{{.index}.log
+  file_name_format: "{{.date}}-{{.index}.log"
   # Date format for file names in the Go time format: https://golang.org/pkg/time/#pkg-constants
   file_date_format: 2006-01-02
   # Log file permissions.

+ 5 - 1
main.go

@@ -67,6 +67,8 @@ type Bridge struct {
 	Log        *log.Logger
 
 	MatrixListener *MatrixListener
+
+	users map[string]*User
 }
 
 func NewBridge() *Bridge {
@@ -133,7 +135,9 @@ func (bridge *Bridge) Main() {
 }
 
 func main() {
-	flag.SetHelpTitles("mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.", "[-h] [-c <path>] [-r <path>] [-g]")
+	flag.SetHelpTitles(
+		"mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.",
+		"mautrix-whatsapp [-h] [-c <path>] [-r <path>] [-g]")
 	err := flag.Parse()
 	if err != nil {
 		fmt.Fprintln(os.Stderr, err)

+ 62 - 1
matrix.go

@@ -18,10 +18,13 @@ package main
 
 import (
 	log "maunium.net/go/maulogger"
+	"maunium.net/go/mautrix-appservice"
+	"maunium.net/go/gomatrix"
 )
 
 type MatrixListener struct {
 	bridge *Bridge
+	as     *appservice.AppService
 	log    *log.Sublogger
 	stop   chan struct{}
 }
@@ -29,8 +32,9 @@ type MatrixListener struct {
 func NewMatrixListener(bridge *Bridge) *MatrixListener {
 	return &MatrixListener{
 		bridge: bridge,
+		as:     bridge.AppService,
 		stop:   make(chan struct{}, 1),
-		log: bridge.Log.CreateSublogger("Matrix", log.LevelDebug),
+		log:    bridge.Log.CreateSublogger("Matrix", log.LevelDebug),
 	}
 }
 
@@ -39,12 +43,69 @@ func (ml *MatrixListener) Start() {
 		select {
 		case evt := <-ml.bridge.AppService.Events:
 			log.Debugln("Received Matrix event:", evt)
+			switch evt.Type {
+			case gomatrix.StateMember:
+				ml.HandleMembership(evt)
+			case gomatrix.EventMessage:
+				ml.HandleMessage(evt)
+			}
 		case <-ml.stop:
 			return
 		}
 	}
 }
 
+func (ml *MatrixListener) HandleBotInvite(evt *gomatrix.Event) {
+	cli := ml.as.BotClient()
+
+	resp, err := cli.JoinRoom(evt.RoomID, "", nil)
+	if err != nil {
+		ml.log.Debugln("Failed to join room", evt.RoomID, "with invite from", evt.Sender)
+		return
+	}
+
+	members, err := cli.JoinedMembers(resp.RoomID)
+	if err != nil {
+		ml.log.Debugln("Failed to get members in room", resp.RoomID, "after accepting invite from", evt.Sender)
+		cli.LeaveRoom(resp.RoomID)
+		return
+	}
+
+	if len(members.Joined) < 2 {
+		ml.log.Debugln("Leaving empty room", resp.RoomID, "after accepting invite from", evt.Sender)
+		cli.LeaveRoom(resp.RoomID)
+		return
+	}
+	for mxid, _ := range members.Joined {
+		if mxid == cli.UserID || mxid == evt.Sender {
+			continue
+		} else if true { // TODO check if mxid is WhatsApp puppet
+
+			continue
+		}
+		ml.log.Debugln("Leaving multi-user room", resp.RoomID, "after accepting invite from", evt.Sender)
+		cli.SendNotice(resp.RoomID, "This bridge is user-specific, please don't invite me into rooms with other users.")
+		cli.LeaveRoom(resp.RoomID)
+		return
+	}
+
+	user := ml.bridge.GetUser(evt.Sender)
+	user.ManagementRoom = resp.RoomID
+	user.Update()
+	cli.SendNotice(user.ManagementRoom, "This room has been registered as your bridge management/status room.")
+	ml.log.Debugln(resp.RoomID, "registered as a management room with", evt.Sender)
+}
+
+func (ml *MatrixListener) HandleMembership(evt *gomatrix.Event) {
+	if evt.Content.Membership == "invite" && evt.GetStateKey() == ml.as.BotMXID() {
+		ml.HandleBotInvite(evt)
+	}
+}
+
+func (ml *MatrixListener) HandleMessage(evt *gomatrix.Event) {
+
+}
+
 func (ml *MatrixListener) Stop() {
 	ml.stop <- struct{}{}
 }

+ 89 - 0
portal.go

@@ -0,0 +1,89 @@
+// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
+// Copyright (C) 2018 Tulir Asokan
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+package main
+
+import (
+	"maunium.net/go/mautrix-whatsapp/database"
+	log "maunium.net/go/maulogger"
+	"fmt"
+)
+
+func (user *User) GetPortalByMXID(mxid string) *Portal {
+	portal, ok := user.portalsByMXID[mxid]
+	if !ok {
+		dbPortal := user.bridge.DB.Portal.GetByMXID(mxid)
+		if dbPortal == nil || dbPortal.Owner != user.UserID {
+			return nil
+		}
+		portal = user.NewPortal(dbPortal)
+		user.portalsByJID[portal.JID] = portal
+		if len(portal.MXID) > 0 {
+			user.portalsByMXID[portal.MXID] = portal
+		}
+	}
+	return portal
+}
+
+func (user *User) GetPortalByJID(jid string) *Portal {
+	portal, ok := user.portalsByJID[jid]
+	if !ok {
+		dbPortal := user.bridge.DB.Portal.GetByJID(user.UserID, jid)
+		if dbPortal == nil {
+			return nil
+		}
+		portal = user.NewPortal(dbPortal)
+		user.portalsByJID[portal.JID] = portal
+		if len(portal.MXID) > 0 {
+			user.portalsByMXID[portal.MXID] = portal
+		}
+	}
+	return portal
+}
+
+func (user *User) GetAllPortals() []*Portal {
+	dbPortals := user.bridge.DB.Portal.GetAll(user.UserID)
+	output := make([]*Portal, len(dbPortals))
+	for index, dbPortal := range dbPortals {
+		portal, ok := user.portalsByJID[dbPortal.JID]
+		if !ok {
+			portal = user.NewPortal(dbPortal)
+			user.portalsByJID[dbPortal.JID] = portal
+			if len(dbPortal.MXID) > 0 {
+				user.portalsByMXID[dbPortal.MXID] = portal
+			}
+		}
+		output[index] = portal
+	}
+	return output
+}
+
+func (user *User) NewPortal(dbPortal *database.Portal) *Portal {
+	return &Portal{
+		Portal: dbPortal,
+		user:   user,
+		bridge: user.bridge,
+		log:    user.bridge.Log.CreateSublogger(fmt.Sprintf("Portal/%s/%s", user.UserID, dbPortal.JID), log.LevelDebug),
+	}
+}
+
+type Portal struct {
+	*database.Portal
+
+	user   *User
+	bridge *Bridge
+	log    *log.Sublogger
+}

+ 158 - 0
user.go

@@ -15,3 +15,161 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 package main
+
+import (
+	"maunium.net/go/mautrix-whatsapp/database"
+	"github.com/Rhymen/go-whatsapp"
+	"time"
+	"fmt"
+	"os"
+	"github.com/skip2/go-qrcode"
+	log "maunium.net/go/maulogger"
+)
+
+type User struct {
+	*database.User
+	Conn *whatsapp.Conn
+
+	bridge *Bridge
+	log    *log.Sublogger
+
+	portalsByMXID map[string]*Portal
+	portalsByJID  map[string]*Portal
+	puppets       map[string]*Portal
+}
+
+func (bridge *Bridge) GetUser(userID string) *User {
+	user, ok := bridge.users[userID]
+	if !ok {
+		dbUser := bridge.DB.User.Get(userID)
+		if dbUser == nil {
+			dbUser = bridge.DB.User.New()
+			dbUser.Insert()
+		}
+		user = bridge.NewUser(dbUser)
+		bridge.users[user.UserID] = user
+	}
+	return user
+}
+
+func (bridge *Bridge) GetAllUsers() []*User {
+	dbUsers := bridge.DB.User.GetAll()
+	output := make([]*User, len(dbUsers))
+	for index, dbUser := range dbUsers {
+		user, ok := bridge.users[dbUser.UserID]
+		if !ok {
+			user = bridge.NewUser(dbUser)
+			bridge.users[user.UserID] = user
+		}
+		output[index] = user
+	}
+	return output
+}
+
+func (bridge *Bridge) InitWhatsApp() {
+	users := bridge.GetAllUsers()
+	for _, user := range users {
+		user.Connect()
+	}
+}
+
+func (bridge *Bridge) NewUser(dbUser *database.User) *User {
+	return &User{
+		User:   dbUser,
+		bridge: bridge,
+		log:    bridge.Log.CreateSublogger(fmt.Sprintf("User/%s", dbUser.UserID), log.LevelDebug),
+	}
+}
+
+func (user *User) Connect() {
+	var err error
+	user.Conn, err = whatsapp.NewConn(20 * time.Second)
+	if err != nil {
+		user.log.Errorln("Failed to connect to WhatsApp:", err)
+		return
+	}
+	user.Conn.AddHandler(user)
+	user.RestoreSession()
+}
+
+func (user *User) RestoreSession() {
+	if user.Session != nil {
+		sess, err := user.Conn.RestoreSession(*user.Session)
+		if err != nil {
+			user.log.Errorln("Failed to restore session:", err)
+			user.Session = nil
+			return
+		}
+		user.Session = &sess
+		user.log.Debugln("Session restored")
+	}
+	return
+}
+
+func (user *User) Login(roomID string) {
+	bot := user.bridge.AppService.BotClient()
+
+	qrChan := make(chan string, 2)
+	go func() {
+		code := <-qrChan
+		if code == "error" {
+			return
+		}
+		qrCode, err := qrcode.Encode(code, qrcode.Low, 256)
+		if err != nil {
+			user.log.Errorln("Failed to encode QR code:", err)
+			bot.SendNotice(roomID, "Failed to encode QR code (see logs for details)")
+			return
+		}
+
+		resp, err := bot.UploadBytes(qrCode, "image/png")
+		if err != nil {
+			user.log.Errorln("Failed to upload QR code:", err)
+			bot.SendNotice(roomID, "Failed to upload QR code (see logs for details)")
+			return
+		}
+
+		bot.SendImage(roomID, string(qrCode), resp.ContentURI)
+	}()
+	session, err := user.Conn.Login(qrChan)
+	if err != nil {
+		user.log.Warnln("Failed to log in:", err)
+		bot.SendNotice(roomID, "Failed to log in: "+err.Error())
+		qrChan <- "error"
+		return
+	}
+	user.Session = &session
+	user.Update()
+	bot.SendNotice(roomID, "Successfully logged in. Synchronizing chats...")
+	go user.Sync()
+}
+
+func (user *User) Sync() {
+	chats, err := user.Conn.Chats()
+	if err != nil {
+		user.log.Warnln("Failed to get chats")
+		return
+	}
+	user.log.Debugln(chats)
+}
+
+func (user *User) HandleError(err error) {
+	user.log.Errorln("WhatsApp error:", err)
+	fmt.Fprintf(os.Stderr, "%v", err)
+}
+
+func (user *User) HandleTextMessage(message whatsapp.TextMessage) {
+	fmt.Println(message)
+}
+
+func (user *User) HandleImageMessage(message whatsapp.ImageMessage) {
+	fmt.Println(message)
+}
+
+func (user *User) HandleVideoMessage(message whatsapp.VideoMessage) {
+	fmt.Println(message)
+}
+
+func (user *User) HandleJsonMessage(message string) {
+	fmt.Println(message)
+}