|
|
|
// Copyright (c) 2021 Tulir Asokan
|
|
|
|
//
|
|
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
|
|
|
|
// Package types contains various structs and other types used by whatsmeow.
|
|
|
|
package types
|
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
|
|
|
"database/sql/driver"
|
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
signalProtocol "go.mau.fi/libsignal/protocol"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Known JID servers on WhatsApp
|
|
|
|
const (
|
|
|
|
DefaultUserServer = "s.whatsapp.net"
|
|
|
|
GroupServer = "g.us"
|
|
|
|
LegacyUserServer = "c.us"
|
|
|
|
BroadcastServer = "broadcast"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Some JIDs that are contacted often.
|
|
|
|
var (
|
|
|
|
EmptyJID = JID{}
|
|
|
|
GroupServerJID = NewJID("", GroupServer)
|
|
|
|
ServerJID = NewJID("", DefaultUserServer)
|
|
|
|
BroadcastServerJID = NewJID("", BroadcastServer)
|
|
|
|
StatusBroadcastJID = NewJID("status", BroadcastServer)
|
|
|
|
PSAJID = NewJID("0", LegacyUserServer)
|
|
|
|
OfficialBusinessJID = NewJID("16505361212", LegacyUserServer)
|
|
|
|
)
|
|
|
|
|
|
|
|
// MessageID is the internal ID of a WhatsApp message.
|
|
|
|
type MessageID = string
|
|
|
|
|
|
|
|
// JID represents a WhatsApp user ID.
|
|
|
|
//
|
|
|
|
// There are two types of JIDs: regular JID pairs (user and server) and AD-JIDs (user, agent and device).
|
|
|
|
// AD JIDs are only used to refer to specific devices of users, so the server is always s.whatsapp.net (DefaultUserServer).
|
|
|
|
// Regular JIDs can be used for entities on any servers (users, groups, broadcasts).
|
|
|
|
type JID struct {
|
|
|
|
User string
|
|
|
|
Agent uint8
|
|
|
|
Device uint8
|
|
|
|
Server string
|
|
|
|
AD bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// UserInt returns the user as an integer. This is only safe to run on normal users, not on groups or broadcast lists.
|
|
|
|
func (jid JID) UserInt() uint64 {
|
|
|
|
number, _ := strconv.ParseUint(jid.User, 10, 64)
|
|
|
|
return number
|
|
|
|
}
|
|
|
|
|
|
|
|
// ToNonAD returns a version of the JID struct that doesn't have the agent and device set.
|
|
|
|
func (jid JID) ToNonAD() JID {
|
|
|
|
if jid.AD {
|
|
|
|
return JID{
|
|
|
|
User: jid.User,
|
|
|
|
Server: DefaultUserServer,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return jid
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SignalAddress returns the Signal protocol address for the user.
|
|
|
|
func (jid JID) SignalAddress() *signalProtocol.SignalAddress {
|
|
|
|
user := jid.User
|
|
|
|
if jid.Agent != 0 {
|
|
|
|
user = fmt.Sprintf("%s_%d", jid.User, jid.Agent)
|
|
|
|
}
|
|
|
|
return signalProtocol.NewSignalAddress(user, uint32(jid.Device))
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsBroadcastList returns true if the JID is a broadcast list, but not the status broadcast.
|
|
|
|
func (jid JID) IsBroadcastList() bool {
|
|
|
|
return jid.Server == BroadcastServer && jid.User != StatusBroadcastJID.User
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewADJID creates a new AD JID.
|
|
|
|
func NewADJID(user string, agent, device uint8) JID {
|
|
|
|
return JID{
|
|
|
|
User: user,
|
|
|
|
Agent: agent,
|
|
|
|
Device: device,
|
|
|
|
Server: DefaultUserServer,
|
|
|
|
AD: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func parseADJID(user string) (JID, error) {
|
|
|
|
var fullJID JID
|
|
|
|
fullJID.AD = true
|
|
|
|
fullJID.Server = DefaultUserServer
|
|
|
|
|
|
|
|
dotIndex := strings.IndexRune(user, '.')
|
|
|
|
colonIndex := strings.IndexRune(user, ':')
|
|
|
|
if dotIndex < 0 || colonIndex < 0 || colonIndex+1 <= dotIndex {
|
|
|
|
return fullJID, fmt.Errorf("failed to parse ADJID: missing separators")
|
|
|
|
}
|
|
|
|
|
|
|
|
fullJID.User = user[:dotIndex]
|
|
|
|
agent, err := strconv.Atoi(user[dotIndex+1 : colonIndex])
|
|
|
|
if err != nil {
|
|
|
|
return fullJID, fmt.Errorf("failed to parse agent from JID: %w", err)
|
|
|
|
} else if agent < 0 || agent > 255 {
|
|
|
|
return fullJID, fmt.Errorf("failed to parse agent from JID: invalid value (%d)", agent)
|
|
|
|
}
|
|
|
|
device, err := strconv.Atoi(user[colonIndex+1:])
|
|
|
|
if err != nil {
|
|
|
|
return fullJID, fmt.Errorf("failed to parse device from JID: %w", err)
|
|
|
|
} else if device < 0 || device > 255 {
|
|
|
|
return fullJID, fmt.Errorf("failed to parse device from JID: invalid value (%d)", device)
|
|
|
|
}
|
|
|
|
fullJID.Agent = uint8(agent)
|
|
|
|
fullJID.Device = uint8(device)
|
|
|
|
return fullJID, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseJID parses a JID out of the given string. It supports both regular and AD JIDs.
|
|
|
|
func ParseJID(jid string) (JID, error) {
|
|
|
|
parts := strings.Split(jid, "@")
|
|
|
|
if len(parts) == 1 {
|
|
|
|
return NewJID("", parts[0]), nil
|
|
|
|
} else if strings.ContainsRune(parts[0], ':') && strings.ContainsRune(parts[0], '.') && parts[1] == DefaultUserServer {
|
|
|
|
return parseADJID(parts[0])
|
|
|
|
}
|
|
|
|
return NewJID(parts[0], parts[1]), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewJID creates a new regular JID.
|
|
|
|
func NewJID(user, server string) JID {
|
|
|
|
return JID{
|
|
|
|
User: user,
|
|
|
|
Server: server,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// String converts the JID to a string representation.
|
|
|
|
// The output string can be parsed with ParseJID, except for JIDs with no User part specified.
|
|
|
|
func (jid JID) String() string {
|
|
|
|
if jid.AD {
|
|
|
|
return fmt.Sprintf("%s.%d:%d@%s", jid.User, jid.Agent, jid.Device, jid.Server)
|
|
|
|
} else if len(jid.User) > 0 {
|
|
|
|
return fmt.Sprintf("%s@%s", jid.User, jid.Server)
|
|
|
|
} else {
|
|
|
|
return jid.Server
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MarshalText implements encoding.TextMarshaler for JID
|
|
|
|
func (jid JID) MarshalText() ([]byte, error) {
|
|
|
|
return []byte(jid.String()), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnmarshalText implements encoding.TextUnmarshaler for JID
|
|
|
|
func (jid *JID) UnmarshalText(val []byte) error {
|
|
|
|
out, err := ParseJID(string(val))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
*jid = out
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsEmpty returns true if the JID has no server (which is required for all JIDs).
|
|
|
|
func (jid JID) IsEmpty() bool {
|
|
|
|
return len(jid.Server) == 0
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ sql.Scanner = (*JID)(nil)
|
|
|
|
|
|
|
|
// Scan scans the given SQL value into this JID.
|
|
|
|
func (jid *JID) Scan(src interface{}) error {
|
|
|
|
if src == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
var out JID
|
|
|
|
var err error
|
|
|
|
switch val := src.(type) {
|
|
|
|
case string:
|
|
|
|
out, err = ParseJID(val)
|
|
|
|
case []byte:
|
|
|
|
out, err = ParseJID(string(val))
|
|
|
|
default:
|
|
|
|
err = fmt.Errorf("unsupported type %T for scanning JID", val)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
*jid = out
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Value returns the string representation of the JID as a value that the SQL package can use.
|
|
|
|
func (jid JID) Value() (driver.Value, error) {
|
|
|
|
if len(jid.Server) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return jid.String(), nil
|
|
|
|
}
|