mirror of
https://git.sr.ht/~nicoco/slidge-whatsapp
synced 2024-09-28 09:32:48 +00:00
157 lines
4.9 KiB
Go
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{}
|
||
|
}
|