1
0
Fork 0

Implement search by phone number

Searching by phone number will check for a contact on WhatsApp, and if
existing, automatically add that contact to the roster. However, contact
names are not resolved until after they've been added to the roster and
been communicated with.
This commit is contained in:
Alex Palaistras 2023-12-14 19:54:52 +00:00 committed by nicoco
parent 5ee1a02463
commit f4ecbee5d2
3 changed files with 56 additions and 4 deletions

View File

@ -3,7 +3,7 @@ from pathlib import Path
from shelve import open
from typing import TYPE_CHECKING
from slidge import BaseGateway, GatewayUser, global_config
from slidge import BaseGateway, FormField, GatewayUser, global_config
from . import config
from .generated import whatsapp
@ -33,6 +33,10 @@ class Gateway(BaseGateway):
WELCOME_MESSAGE = WELCOME_MESSAGE
REGISTRATION_FIELDS = []
SEARCH_FIELDS = [
FormField(var="phone", label="Phone number", required=True),
]
ROSTER_GROUP = "WhatsApp"
MARK_ALL_MESSAGES = True

View File

@ -435,6 +435,10 @@ func (s *Session) GetGroups() ([]Group, error) {
// is also given, GetAvatar will return an empty [Avatar] instance with no error if the remote state
// for the given ID has not changed.
func (s *Session) GetAvatar(resourceID, avatarID string) (Avatar, error) {
if s.client == nil || s.client.Store.ID == nil {
return Avatar{}, fmt.Errorf("Cannot get avatar for unauthenticated session")
}
jid, err := types.ParseJID(resourceID)
if err != nil {
return Avatar{}, fmt.Errorf("Could not parse JID for avatar: %s", err)
@ -454,6 +458,10 @@ func (s *Session) GetAvatar(resourceID, avatarID string) (Avatar, error) {
// profile picture for our own user by providing an empty JID. The unique picture ID is returned,
// typically used as a cache reference or in providing to future calls for [Session.GetAvatar].
func (s *Session) SetAvatar(resourceID, avatarPath string) (string, error) {
if s.client == nil || s.client.Store.ID == nil {
return "", fmt.Errorf("Cannot set avatar for unauthenticated session")
}
var jid types.JID
var err error
@ -485,6 +493,27 @@ func (s *Session) SetAvatar(resourceID, avatarPath string) (string, error) {
}
}
// FindContact attempts to check for a registered contact on WhatsApp corresponding to the given
// phone number, returning a concrete instance if found; typically, only the contact JID is set. No
// error is returned if no contact was found, but any unexpected errors will otherwise be returned
// directly.
func (s *Session) FindContact(phone string) (Contact, error) {
if s.client == nil || s.client.Store.ID == nil {
return Contact{}, fmt.Errorf("Cannot find contact for unauthenticated session")
}
resp, err := s.client.IsOnWhatsApp([]string{phone})
if err != nil {
return Contact{}, fmt.Errorf("Failed looking up contact '%s': %s", phone, err)
} else if len(resp) != 1 {
return Contact{}, fmt.Errorf("Failed looking up contact '%s': invalid response", phone)
} else if !resp[0].IsIn || resp[0].JID.IsEmpty() {
return Contact{}, nil
}
return Contact{JID: resp[0].JID.ToNonAD().String()}, nil
}
// SetEventHandler assigns the given handler function for propagating internal events into the Python
// gateway. Note that the event handler function is not entirely safe to use directly, and all calls
// should instead be made via the [propagateEvent] function.

View File

@ -8,12 +8,13 @@ from re import search
from shelve import open
from tempfile import mkstemp
from threading import Lock
from typing import Optional, Union
from typing import Optional, Union, cast
from aiohttp import ClientSession
from linkpreview import Link, LinkPreview
from slidge import BaseSession, GatewayUser, global_config
from slidge import BaseSession, FormField, GatewayUser, SearchResult, global_config
from slidge.contact.roster import ContactIsUser
from slidge.util import is_valid_phone_number
from slidge.util.types import (
LegacyAttachment,
MessageReference,
@ -467,7 +468,25 @@ class Session(BaseSession[str, Recipient]):
self.whatsapp.SetAvatar("", await get_bytes_temp(bytes_) if bytes_ else "")
async def search(self, form_values: dict[str, str]):
self.send_gateway_message("Searching on WhatsApp has not been implemented yet.")
"""
Searches for, and automatically adds, WhatsApp contact based on phone number. Phone numbers
not registered on WhatsApp will be ignored with no error.
"""
phone = form_values.get("phone")
if not is_valid_phone_number(phone):
raise ValueError("Not a valid phone number", phone)
data = self.whatsapp.FindContact(phone)
if not data.JID:
return
await self.contacts.add_whatsapp_contact(data)
contact = await self.contacts.by_legacy_id(data.JID)
return SearchResult(
fields=[FormField("phone"), FormField("jid", type="jid-single")],
items=[{"phone": cast(str, phone), "jid": contact.jid.bare}],
)
async def get_contact_or_participant(
self, legacy_contact_id: str, legacy_group_jid: str