1
0
mirror of https://git.sr.ht/~nicoco/slidge-whatsapp synced 2024-06-24 11:28:39 +00:00
slidge-whatsapp/slidge_whatsapp/gateway.go
2023-05-02 16:35:30 +02:00

157 lines
4.9 KiB
Go

package whatsapp
import (
// Standard library.
"crypto/tls"
"fmt"
"net/http"
"runtime"
"time"
// Third-party libraries.
_ "github.com/mattn/go-sqlite3"
"go.mau.fi/whatsmeow/store"
"go.mau.fi/whatsmeow/store/sqlstore"
"go.mau.fi/whatsmeow/types"
walog "go.mau.fi/whatsmeow/util/log"
)
// A LinkedDevice represents a unique pairing session between the gateway and WhatsApp. It is not
// unique to the underlying "main" device (or phone number), as multiple linked devices may be paired
// with any main device.
type LinkedDevice struct {
// ID is an opaque string identifying this LinkedDevice to the Session. Noted that this string
// is currently equivalent to a password, and needs to be protected accordingly.
ID string
}
// JID returns the WhatsApp JID corresponding to the LinkedDevice ID. Empty or invalid device IDs
// may return invalid JIDs, and this function does not handle errors.
func (d LinkedDevice) JID() types.JID {
jid, _ := types.ParseJID(d.ID)
return jid
}
// A ErrorLevel is a value representing the severity of a log message being handled.
type ErrorLevel int
// The log levels handled by the overarching Session logger.
const (
LevelError ErrorLevel = 1 + iota
LevelWarning
LevelInfo
LevelDebug
)
// HandleLogFunc is the signature for the overarching Gateway log handling function.
type HandleLogFunc func(ErrorLevel, string)
// Errorf handles the given message as representing a (typically) fatal error.
func (h HandleLogFunc) Errorf(msg string, args ...interface{}) {
h(LevelError, fmt.Sprintf(msg, args...))
}
// Warn handles the given message as representing a non-fatal error or warning thereof.
func (h HandleLogFunc) Warnf(msg string, args ...interface{}) {
h(LevelWarning, fmt.Sprintf(msg, args...))
}
// Infof handles the given message as representing an informational notice.
func (h HandleLogFunc) Infof(msg string, args ...interface{}) {
h(LevelInfo, fmt.Sprintf(msg, args...))
}
// Debugf handles the given message as representing an internal-only debug message.
func (h HandleLogFunc) Debugf(msg string, args ...interface{}) {
h(LevelDebug, fmt.Sprintf(msg, args...))
}
// Sub is a no-op and will return the receiver itself.
func (h HandleLogFunc) Sub(string) walog.Logger {
return h
}
// A Gateway represents a persistent process for establishing individual sessions between linked
// devices and WhatsApp.
type Gateway struct {
DBPath string // The filesystem path for the client database.
Name string // The name to display when linking devices on WhatsApp.
SkipVerifyTLS bool // Whether or not our internal HTTP client will skip TLS certificate verification.
// Internal variables.
container *sqlstore.Container
httpClient *http.Client
logger walog.Logger
}
// NewSession returns a new for the LinkedDevice given. If the linked device does not have a valid
// ID, a pair operation will be required, as described in [Session.Login].
func (w *Gateway) NewSession(device LinkedDevice) *Session {
return &Session{device: device, gateway: w}
}
// CleanupSession will remove all invalid and obsolete references to the given device, and should be
// used when pairing a new device or unregistering from the Gateway.
func (w *Gateway) CleanupSession(device LinkedDevice) error {
devices, err := w.container.GetAllDevices()
if err != nil {
return err
}
for _, d := range devices {
if d.ID == nil {
w.logger.Infof("Removing invalid device %s from database", d.ID.String())
_ = d.Delete()
} else if device.ID != "" {
if jid := device.JID(); d.ID.ToNonAD() == jid.ToNonAD() && *d.ID != jid {
w.logger.Infof("Removing obsolete device %s from database", d.ID.String())
_ = d.Delete()
}
}
}
return nil
}
// Init performs initialization procedures for the Gateway, and is expected to be run before any
// calls to [Gateway.Session].
func (w *Gateway) Init() error {
container, err := sqlstore.New("sqlite3", w.DBPath, w.logger)
if err != nil {
return err
}
if w.Name != "" {
store.SetOSInfo(w.Name, [...]uint32{1, 0, 0})
}
// Set up shared HTTP client with less lenient timeouts.
w.httpClient = &http.Client{
Timeout: time.Second * 10,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: w.SkipVerifyTLS},
},
}
w.container = container
return nil
}
// SetLogHandler specifies the log handling function to use for all [Gateway] and [Session] operations.
func (w *Gateway) SetLogHandler(h HandleLogFunc) {
w.logger = HandleLogFunc(func(level ErrorLevel, message string) {
// Don't allow other Goroutines from using this thread, as this might lead to concurrent
// use of the GIL, which can lead to crashes.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
h(level, message)
})
}
// NewGateway returns a new, un-initialized Gateway. This function should always be followed by calls
// to [Gateway.Init], assuming a valid [Gateway.DBPath] is set.
func NewGateway() *Gateway {
return &Gateway{}
}