1
0
mirror of https://github.com/nikoksr/notify.git synced 2025-01-10 00:28:36 +02:00
notify/service/whatsapp/whatsapp.go
2021-02-23 01:23:31 +01:00

191 lines
4.3 KiB
Go

package whatsapp
import (
"context"
"encoding/gob"
"fmt"
"os"
"path/filepath"
"time"
qrcodeTerminal "github.com/Baozisoftware/qrcode-terminal-go"
"github.com/Rhymen/go-whatsapp"
"github.com/pkg/errors"
)
const (
qrLoginWaitTime = 3 * time.Second
clientTimeout = 5 * time.Second
)
var sessionFilePath = filepath.Join(os.TempDir(), "whatsappSession.gob")
// whatsappClient abstracts go-whatsapp for writing unit tests
type whatsappClient interface {
Login(qrChan chan<- string) (whatsapp.Session, error)
RestoreWithSession(session whatsapp.Session) (whatsapp.Session, error)
Send(msg interface{}) (string, error)
}
// Service encapsulates the WhatsApp client along with internal state for storing contacts.
type Service struct {
client whatsappClient
contacts []string
}
// New returns a new instance of a WhatsApp notification service.
func New() (*Service, error) {
client, err := whatsapp.NewConn(clientTimeout)
if err != nil {
return nil, err
}
s := &Service{
client: client,
contacts: []string{},
}
return s, nil
}
// LoginWithSessionCredentials provides helper for authentication using whatsapp.Session credentials.
func (s *Service) LoginWithSessionCredentials(clientID, clientToken, serverToken, wid string, encKey, macKey []byte) error {
session := whatsapp.Session{
ClientId: clientID,
ClientToken: clientToken,
ServerToken: serverToken,
Wid: wid,
EncKey: encKey,
MacKey: macKey,
}
session, err := s.client.RestoreWithSession(session)
if err != nil {
return fmt.Errorf("restoring session failed: %v", err)
}
// Save the updated session for future use without login.
err = writeSession(&session)
if err != nil {
return fmt.Errorf("error saving session: %v", err)
}
return nil
}
// LoginWithQRCode provides helper for authentication using QR code on terminal.
// Refer: https://github.com/Rhymen/go-whatsapp#login for more information.
func (s *Service) LoginWithQRCode() error {
session, err := readSession()
if err == nil {
session, err = s.client.RestoreWithSession(session)
if err != nil {
return fmt.Errorf("restoring session failed: %v", err)
}
} else {
// No saved session found; need to login again.
qr := make(chan string)
go func() {
terminal := qrcodeTerminal.New()
terminal.Get(<-qr).Print()
}()
session, err = s.client.Login(qr)
if err != nil {
return fmt.Errorf("error during login: %v", err)
}
}
err = writeSession(&session)
if err != nil {
return fmt.Errorf("error saving session: %v", err)
}
<-time.After(qrLoginWaitTime)
return nil
}
// readSession helps load saved WhatsApp session from local file system.
func readSession() (whatsapp.Session, error) {
session := whatsapp.Session{}
file, err := os.Open(sessionFilePath)
if err != nil {
return session, err
}
defer func() {
cerr := file.Close()
if cerr != nil {
if err != nil {
err = errors.Wrap(err, cerr.Error())
} else {
err = cerr
}
}
}()
decoder := gob.NewDecoder(file)
err = decoder.Decode(&session)
if err != nil {
return session, err
}
return session, nil
}
// writeSession helps save WhatsApp session to local file system.
func writeSession(session *whatsapp.Session) error {
file, err := os.Create(sessionFilePath)
if err != nil {
return err
}
defer func() {
cerr := file.Close()
if cerr != nil {
if err != nil {
err = errors.Wrap(err, cerr.Error())
} else {
err = cerr
}
}
}()
encoder := gob.NewEncoder(file)
err = encoder.Encode(*session)
if err != nil {
return err
}
return nil
}
// AddReceivers takes WhatsApp contacts and adds them to the internal contacts list. The Send method will send
// a given message to all those contacts.
func (s *Service) AddReceivers(contacts ...string) {
s.contacts = append(s.contacts, contacts...)
}
// Send takes a message subject and a message body and sends them to all previously set contacts.
func (s *Service) Send(ctx context.Context, subject, message string) error {
msg := whatsapp.TextMessage{
Text: subject + "\n" + message,
}
for _, contact := range s.contacts {
select {
case <-ctx.Done():
return ctx.Err()
default:
msg.Info = whatsapp.MessageInfo{
RemoteJid: contact + "@s.whatsapp.net",
}
_, err := s.client.Send(msg)
if err != nil {
return errors.Wrapf(err, "failed to send message to WhatsApp contact '%s'", contact)
}
}
}
return nil
}