whatsapp-python 4.0.2__py3-none-any.whl → 4.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- whatsapp/__init__.py +418 -4
- whatsapp/async_ext/_buttons.py +118 -0
- whatsapp/async_ext/_media.py +169 -0
- whatsapp/async_ext/_message.py +164 -0
- whatsapp/async_ext/_property.py +5 -0
- whatsapp/async_ext/_send_media.py +377 -0
- whatsapp/async_ext/_send_others.py +120 -0
- whatsapp/async_ext/_static.py +329 -0
- whatsapp/constants.py +1 -1
- whatsapp/ext/_buttons.py +2 -0
- whatsapp/ext/_media.py +5 -6
- {whatsapp_python-4.0.2.dist-info → whatsapp_python-4.2.0.dist-info}/METADATA +5 -3
- whatsapp_python-4.2.0.dist-info/RECORD +20 -0
- {whatsapp_python-4.0.2.dist-info → whatsapp_python-4.2.0.dist-info}/WHEEL +1 -1
- whatsapp_python-4.0.2.dist-info/RECORD +0 -13
- {whatsapp_python-4.0.2.dist-info → whatsapp_python-4.2.0.dist-info}/licenses/LICENSE +0 -0
whatsapp/__init__.py
CHANGED
|
@@ -4,6 +4,9 @@ Unofficial Python wrapper for the WhatsApp Cloud API.
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
import requests
|
|
6
6
|
import logging
|
|
7
|
+
import json
|
|
8
|
+
import asyncio
|
|
9
|
+
import aiohttp
|
|
7
10
|
from bs4 import BeautifulSoup
|
|
8
11
|
from fastapi import FastAPI, HTTPException, Request
|
|
9
12
|
from uvicorn import run as _run
|
|
@@ -15,7 +18,14 @@ from .ext._send_media import send_image, send_video, send_audio, send_location,
|
|
|
15
18
|
from .ext._media import upload_media, query_media_url, download_media, delete_media
|
|
16
19
|
from .ext._buttons import send_button, create_button, send_reply_button
|
|
17
20
|
from .ext._static import is_message, get_mobile, get_author, get_name, get_message, get_message_id, get_message_type, get_message_timestamp, get_audio, get_delivery, get_document, get_image, get_sticker, get_interactive_response, get_location, get_video, changed_field
|
|
18
|
-
|
|
21
|
+
|
|
22
|
+
from .async_ext._property import authorized as async_authorized
|
|
23
|
+
from .async_ext._send_others import send_custom_json as async_send_custom_json, send_contacts as async_send_contacts
|
|
24
|
+
from .async_ext._message import send_template as async_send_template
|
|
25
|
+
from .async_ext._send_media import send_image as async_send_image, send_video as async_send_video, send_audio as async_send_audio, send_location as async_send_location, send_sticker as async_send_sticker, send_document as async_send_document
|
|
26
|
+
from .async_ext._media import upload_media as async_upload_media, query_media_url as async_query_media_url, download_media as async_download_media, delete_media as async_delete_media
|
|
27
|
+
from .async_ext._buttons import send_button as async_send_button, create_button as async_create_button, send_reply_button as async_send_reply_button
|
|
28
|
+
from .async_ext._static import is_message as async_is_message, get_mobile as async_get_mobile, get_author as async_get_author, get_name as async_get_name, get_message as async_get_message, get_message_id as async_get_message_id, get_message_type as async_get_message_type, get_message_timestamp as async_get_message_timestamp, get_audio as async_get_audio, get_delivery as async_get_delivery, get_document as async_get_document, get_image as async_get_image, get_sticker as async_get_sticker, get_interactive_response as async_get_interactive_response, get_location as async_get_location, get_video as async_get_video, changed_field as async_changed_field
|
|
19
29
|
|
|
20
30
|
|
|
21
31
|
class WhatsApp(object):
|
|
@@ -132,8 +142,8 @@ class WhatsApp(object):
|
|
|
132
142
|
if r.query_params.get("hub.verify_token") == self.verify_token:
|
|
133
143
|
logging.debug("Webhook verified successfully")
|
|
134
144
|
challenge = r.query_params.get("hub.challenge")
|
|
135
|
-
self.verification_handler(challenge)
|
|
136
|
-
self.other_handler(challenge)
|
|
145
|
+
await self.verification_handler(challenge)
|
|
146
|
+
await self.other_handler(challenge)
|
|
137
147
|
return int(challenge)
|
|
138
148
|
logging.error("Webhook Verification failed - token mismatch")
|
|
139
149
|
await self.verification_handler(False)
|
|
@@ -202,9 +212,9 @@ class WhatsApp(object):
|
|
|
202
212
|
send_template = send_template
|
|
203
213
|
send_custom_json = send_custom_json
|
|
204
214
|
send_contacts = send_contacts
|
|
205
|
-
|
|
206
215
|
authorized = property(authorized)
|
|
207
216
|
|
|
217
|
+
|
|
208
218
|
def create_message(self, **kwargs) -> Message:
|
|
209
219
|
"""
|
|
210
220
|
Create a message object
|
|
@@ -248,6 +258,238 @@ class WhatsApp(object):
|
|
|
248
258
|
_run(self.app, host=host, port=port, **options)
|
|
249
259
|
|
|
250
260
|
|
|
261
|
+
class AsyncWhatsApp(WhatsApp):
|
|
262
|
+
def __init__(self, token: str = "", phone_number_id: dict = "", logger: bool = True, update_check: bool = True, verify_token: str = "", debug: bool = True, version: str = "latest"):
|
|
263
|
+
"""
|
|
264
|
+
Initialize the WhatsApp Object
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
token[str]: Token for the WhatsApp cloud API obtained from the Facebook developer portal
|
|
268
|
+
phone_number_id[str]: Phone number id for the WhatsApp cloud API obtained from the developer portal
|
|
269
|
+
logger[bool]: Whether to enable logging or not (default: True)
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
# Check if the version is up to date
|
|
273
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
274
|
+
|
|
275
|
+
self.l = phone_number_id
|
|
276
|
+
|
|
277
|
+
if isinstance(phone_number_id, dict):
|
|
278
|
+
# use first phone number id as default
|
|
279
|
+
phone_number_id = phone_number_id[list(phone_number_id.keys())[0]]
|
|
280
|
+
|
|
281
|
+
elif phone_number_id == "":
|
|
282
|
+
logging.error("Phone number ID not provided")
|
|
283
|
+
raise ValueError(
|
|
284
|
+
"Phone number ID not provided but required")
|
|
285
|
+
elif isinstance(phone_number_id, str):
|
|
286
|
+
logging.critical(
|
|
287
|
+
"The phone number ID should be a dictionary of phone numbers and their IDs. Using strings is deprecated.")
|
|
288
|
+
raise ValueError(
|
|
289
|
+
"Phone number ID should be a dictionary of phone numbers and their IDs")
|
|
290
|
+
else:
|
|
291
|
+
pass
|
|
292
|
+
|
|
293
|
+
self.VERSION = VERSION # package version
|
|
294
|
+
r = requests.get("https://developers.facebook.com/docs/graph-api/changelog/").text
|
|
295
|
+
|
|
296
|
+
# dynamically get the latest version of the API
|
|
297
|
+
if version == "latest":
|
|
298
|
+
soup = BeautifulSoup(r, features="html.parser")
|
|
299
|
+
t1 = soup.findAll("table")
|
|
300
|
+
|
|
301
|
+
def makeversion(table):
|
|
302
|
+
result = []
|
|
303
|
+
allrows = table.findAll('tr')
|
|
304
|
+
for row in allrows:
|
|
305
|
+
result.append([])
|
|
306
|
+
allcols = row.findAll('td')
|
|
307
|
+
for col in allcols:
|
|
308
|
+
thestrings = [(s) for s in col.findAll(text=True)]
|
|
309
|
+
thetext = ''.join(thestrings)
|
|
310
|
+
result[-1].append(thetext)
|
|
311
|
+
return result[0][1]
|
|
312
|
+
self.LATEST = makeversion(t1[0]) # latest version of the API
|
|
313
|
+
else:
|
|
314
|
+
self.LATEST = version
|
|
315
|
+
|
|
316
|
+
if update_check is True:
|
|
317
|
+
latest = str(requests.get(
|
|
318
|
+
"https://pypi.org/pypi/whatsapp-python/json").json()["info"]["version"])
|
|
319
|
+
if self.VERSION != latest:
|
|
320
|
+
try:
|
|
321
|
+
version_int = int(self.VERSION.replace(".", ""))
|
|
322
|
+
except:
|
|
323
|
+
version_int = 0
|
|
324
|
+
try:
|
|
325
|
+
latest_int = int(latest.replace(".", ""))
|
|
326
|
+
except:
|
|
327
|
+
latest_int = 0
|
|
328
|
+
# this is to avoid the case where the version is 1.0.10 and the latest is 1.0.2 (possible if user is using the github version)
|
|
329
|
+
if version_int < latest_int:
|
|
330
|
+
if version_int == 0:
|
|
331
|
+
logging.critical(
|
|
332
|
+
f"There was an error while checking for updates, please check for updates manually. This may be due to the version being a post-release version (e.g. 1.0.0.post1) or a pre-release version (e.g. 1.0.0a1). READ THE CHANGELOG BEFORE UPDATING. NEW VERSIONS MAY BREAK YOUR CODE IF NOT PROPERLY UPDATED.")
|
|
333
|
+
else:
|
|
334
|
+
logging.critical(
|
|
335
|
+
f"Whatsapp-python is out of date. Please update to the latest version {latest}. READ THE CHANGELOG BEFORE UPDATING. NEW VERSIONS MAY BREAK YOUR CODE IF NOT PROPERLY UPDATED.")
|
|
336
|
+
|
|
337
|
+
if token == "":
|
|
338
|
+
logging.error("Token not provided")
|
|
339
|
+
raise ValueError("Token not provided but required")
|
|
340
|
+
if phone_number_id == "":
|
|
341
|
+
logging.error("Phone number ID not provided")
|
|
342
|
+
raise ValueError(
|
|
343
|
+
"Phone number ID not provided but required")
|
|
344
|
+
self.token = token
|
|
345
|
+
self.phone_number_id = phone_number_id
|
|
346
|
+
self.base_url = f"https://graph.facebook.com/{self.LATEST}"
|
|
347
|
+
self.url = f"{self.base_url}/{phone_number_id}/messages"
|
|
348
|
+
self.verify_token = verify_token
|
|
349
|
+
|
|
350
|
+
async def base(*args):
|
|
351
|
+
pass
|
|
352
|
+
self.message_handler = base
|
|
353
|
+
self.other_handler = base
|
|
354
|
+
self.verification_handler = base
|
|
355
|
+
self.headers = {
|
|
356
|
+
"Content-Type": "application/json",
|
|
357
|
+
"Authorization": f"Bearer {self.token}"
|
|
358
|
+
}
|
|
359
|
+
if logger is False:
|
|
360
|
+
logging.disable(logging.INFO)
|
|
361
|
+
logging.disable(logging.ERROR)
|
|
362
|
+
if debug is False:
|
|
363
|
+
logging.disable(logging.DEBUG)
|
|
364
|
+
logging.disable(logging.ERROR)
|
|
365
|
+
|
|
366
|
+
self.app = FastAPI()
|
|
367
|
+
|
|
368
|
+
# Verification handler has 1 argument: challenge (str | bool): str if verification is successful, False if not
|
|
369
|
+
|
|
370
|
+
@self.app.get("/")
|
|
371
|
+
async def verify_endpoint(r: Request):
|
|
372
|
+
if r.query_params.get("hub.verify_token") == self.verify_token:
|
|
373
|
+
logging.debug("Webhook verified successfully")
|
|
374
|
+
challenge = r.query_params.get("hub.challenge")
|
|
375
|
+
await self.verification_handler(challenge)
|
|
376
|
+
await self.other_handler(challenge)
|
|
377
|
+
return int(challenge)
|
|
378
|
+
logging.error("Webhook Verification failed - token mismatch")
|
|
379
|
+
await self.verification_handler(False)
|
|
380
|
+
await self.other_handler(False)
|
|
381
|
+
return {"success": False}
|
|
382
|
+
|
|
383
|
+
@self.app.post("/")
|
|
384
|
+
async def hook(r: Request):
|
|
385
|
+
try:
|
|
386
|
+
# Handle Webhook Subscriptions
|
|
387
|
+
data = await r.json()
|
|
388
|
+
if data is None:
|
|
389
|
+
return {"success": False}
|
|
390
|
+
data_str = json.dumps(data, indent=4)
|
|
391
|
+
# log the data received only if the log level is debug
|
|
392
|
+
logging.debug(f"Received webhook data: {data_str}")
|
|
393
|
+
|
|
394
|
+
changed_field = self.changed_field(data)
|
|
395
|
+
if changed_field == "messages":
|
|
396
|
+
new_message = self.is_message(data)
|
|
397
|
+
if new_message:
|
|
398
|
+
msg = Message(instance=self, data=data)
|
|
399
|
+
await self.message_handler(msg)
|
|
400
|
+
await self.other_handler(msg)
|
|
401
|
+
return {"success": True}
|
|
402
|
+
except Exception as e:
|
|
403
|
+
logging.error(f"Error parsing message: {e}")
|
|
404
|
+
raise HTTPException(status_code=500, detail={
|
|
405
|
+
"success": False,
|
|
406
|
+
"error": str(e)
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
# all the files starting with _ are imported here, and should not be imported directly.
|
|
410
|
+
|
|
411
|
+
is_message = staticmethod(is_message)
|
|
412
|
+
get_mobile = staticmethod(get_mobile)
|
|
413
|
+
get_name = staticmethod(get_name)
|
|
414
|
+
get_message = staticmethod(get_message)
|
|
415
|
+
get_message_id = staticmethod(get_message_id)
|
|
416
|
+
get_message_type = staticmethod(get_message_type)
|
|
417
|
+
get_message_timestamp = staticmethod(get_message_timestamp)
|
|
418
|
+
get_audio = staticmethod(get_audio)
|
|
419
|
+
get_delivery = staticmethod(get_delivery)
|
|
420
|
+
get_document = staticmethod(get_document)
|
|
421
|
+
get_image = staticmethod(get_image)
|
|
422
|
+
get_sticker = staticmethod(get_sticker)
|
|
423
|
+
get_interactive_response = staticmethod(get_interactive_response)
|
|
424
|
+
get_location = staticmethod(get_location)
|
|
425
|
+
get_video = staticmethod(get_video)
|
|
426
|
+
changed_field = staticmethod(changed_field)
|
|
427
|
+
get_author = staticmethod(get_author)
|
|
428
|
+
|
|
429
|
+
send_button = async_send_button
|
|
430
|
+
create_button = async_create_button
|
|
431
|
+
send_reply_button = async_send_reply_button
|
|
432
|
+
send_image = async_send_image
|
|
433
|
+
send_video = async_send_video
|
|
434
|
+
send_audio = async_send_audio
|
|
435
|
+
send_location = async_send_location
|
|
436
|
+
send_sticker = async_send_sticker
|
|
437
|
+
send_document = async_send_document
|
|
438
|
+
upload_media = async_upload_media
|
|
439
|
+
query_media_url = async_query_media_url
|
|
440
|
+
download_media = async_download_media
|
|
441
|
+
delete_media = async_delete_media
|
|
442
|
+
send_template = async_send_template
|
|
443
|
+
send_custom_json = async_send_custom_json
|
|
444
|
+
send_contacts = async_send_contacts
|
|
445
|
+
authorized = property(async_authorized)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def create_message(self, **kwargs) -> AsyncMessage:
|
|
450
|
+
"""
|
|
451
|
+
Create a message object
|
|
452
|
+
|
|
453
|
+
Args:
|
|
454
|
+
data[dict]: The message data
|
|
455
|
+
content[str]: The message content
|
|
456
|
+
to[str]: The recipient
|
|
457
|
+
rec_type[str]: The recipient type (individual/group)
|
|
458
|
+
"""
|
|
459
|
+
return AsyncMessage(**kwargs, instance=self)
|
|
460
|
+
|
|
461
|
+
def on_message(self, handler: function):
|
|
462
|
+
"""
|
|
463
|
+
Set the handler for incoming messages
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
handler[function]: The handler function
|
|
467
|
+
"""
|
|
468
|
+
self.message_handler = handler
|
|
469
|
+
|
|
470
|
+
def on_event(self, handler: function):
|
|
471
|
+
"""
|
|
472
|
+
Set the handler for other events
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
handler[function]: The handler function
|
|
476
|
+
"""
|
|
477
|
+
self.other_handler = handler
|
|
478
|
+
|
|
479
|
+
def on_verification(self, handler: function):
|
|
480
|
+
"""
|
|
481
|
+
Set the handler for verification
|
|
482
|
+
|
|
483
|
+
Args:
|
|
484
|
+
handler[function]: The handler function
|
|
485
|
+
"""
|
|
486
|
+
self.verification_handler = handler
|
|
487
|
+
|
|
488
|
+
def run(self, host: str = "localhost", port: int = 5000, **options):
|
|
489
|
+
_run(self.app, host=host, port=port, **options)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
|
|
251
493
|
class Message(object):
|
|
252
494
|
# type: ignore
|
|
253
495
|
def __init__(self, id: int = None, data: dict = {}, instance: WhatsApp = None, content: str = "", to: str = "", rec_type: str = "individual"):
|
|
@@ -397,3 +639,175 @@ class Message(object):
|
|
|
397
639
|
logging.info(f"Status code: {r.status_code}")
|
|
398
640
|
logging.debug(f"Response: {r.json()}")
|
|
399
641
|
return r.json()
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
class AsyncMessage(object):
|
|
646
|
+
# type: ignore
|
|
647
|
+
def __init__(self, id: int = None, data: dict = {}, instance: WhatsApp = None, content: str = "", to: str = "", rec_type: str = "individual"):
|
|
648
|
+
self.instance = instance
|
|
649
|
+
self.url = self.instance.url
|
|
650
|
+
self.headers = self.instance.headers
|
|
651
|
+
|
|
652
|
+
try:
|
|
653
|
+
self.id = instance.get_message_id(data)
|
|
654
|
+
except:
|
|
655
|
+
self.id = id
|
|
656
|
+
try:
|
|
657
|
+
self.type = self.instance.get_message_type(data)
|
|
658
|
+
except:
|
|
659
|
+
self.type = "text"
|
|
660
|
+
self.data = data
|
|
661
|
+
self.rec = rec_type
|
|
662
|
+
self.to = to
|
|
663
|
+
try:
|
|
664
|
+
self.content = content if content != "" else self.instance.get_message(
|
|
665
|
+
data)
|
|
666
|
+
except:
|
|
667
|
+
self.content = content
|
|
668
|
+
try:
|
|
669
|
+
self.name = self.instance.get_name(data)
|
|
670
|
+
except:
|
|
671
|
+
self.name = None
|
|
672
|
+
|
|
673
|
+
if self.type == "image":
|
|
674
|
+
try:
|
|
675
|
+
self.image = self.instance.get_image(data)
|
|
676
|
+
except:
|
|
677
|
+
self.image = None
|
|
678
|
+
if self.type == "sticker":
|
|
679
|
+
try:
|
|
680
|
+
self.sticker = self.instance.get_sticker(data)
|
|
681
|
+
except:
|
|
682
|
+
self.sticker = None
|
|
683
|
+
elif self.type == "video":
|
|
684
|
+
try:
|
|
685
|
+
self.video = self.instance.get_video(data)
|
|
686
|
+
except:
|
|
687
|
+
self.video = None
|
|
688
|
+
elif self.type == "audio":
|
|
689
|
+
try:
|
|
690
|
+
self.audio = self.instance.get_audio(data)
|
|
691
|
+
except:
|
|
692
|
+
pass
|
|
693
|
+
elif self.type == "document":
|
|
694
|
+
try:
|
|
695
|
+
self.document = self.instance.get_document(data)
|
|
696
|
+
except:
|
|
697
|
+
pass
|
|
698
|
+
elif self.type == "location":
|
|
699
|
+
try:
|
|
700
|
+
self.location = self.instance.get_location(data)
|
|
701
|
+
except:
|
|
702
|
+
pass
|
|
703
|
+
elif self.type == "interactive":
|
|
704
|
+
try:
|
|
705
|
+
self.interactive = self.instance.get_interactive_response(data)
|
|
706
|
+
except:
|
|
707
|
+
pass
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
async def reply(self, reply_text: str = "", preview_url: bool = True) -> asyncio.Future:
|
|
711
|
+
if self.data == {}:
|
|
712
|
+
return {"error": "No data provided"}
|
|
713
|
+
author = self.instance.get_author(self.data)
|
|
714
|
+
payload = {
|
|
715
|
+
"messaging_product": "whatsapp",
|
|
716
|
+
"recipient_type": "individual",
|
|
717
|
+
"to": str(author),
|
|
718
|
+
"type": "text",
|
|
719
|
+
"context": {"message_id": self.id},
|
|
720
|
+
"text": {"preview_url": preview_url, "body": reply_text},
|
|
721
|
+
}
|
|
722
|
+
logging.info(f"Replying to {self.id}")
|
|
723
|
+
async def call():
|
|
724
|
+
async with aiohttp.ClientSession() as session:
|
|
725
|
+
async with session.post(self.url, headers=self.headers, json=payload) as response:
|
|
726
|
+
if response.status == 200:
|
|
727
|
+
logging.info(f"Message sent to {self.instance.get_author(self.data)}")
|
|
728
|
+
return await response.json()
|
|
729
|
+
logging.info(f"Message not sent to {self.instance.get_author(self.data)}")
|
|
730
|
+
logging.info(f"Status code: {response.status}")
|
|
731
|
+
logging.error(f"Response: {await response.json()}")
|
|
732
|
+
return await response.json()
|
|
733
|
+
f = asyncio.ensure_future(call())
|
|
734
|
+
await asyncio.sleep(.001) # make asyncio run the task
|
|
735
|
+
return f
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
async def mark_as_read(self) -> asyncio.Future:
|
|
739
|
+
|
|
740
|
+
payload = {
|
|
741
|
+
"messaging_product": "whatsapp",
|
|
742
|
+
"status": "read",
|
|
743
|
+
"message_id": self.id,
|
|
744
|
+
}
|
|
745
|
+
async def call():
|
|
746
|
+
async with aiohttp.ClientSession() as session:
|
|
747
|
+
async with session.post(f"{self.instance.url}", headers=self.instance.headers, json=payload) as response:
|
|
748
|
+
if response.status == 200:
|
|
749
|
+
logging.info(await response.json())
|
|
750
|
+
return await response.json()
|
|
751
|
+
else:
|
|
752
|
+
logging.error(await response.json())
|
|
753
|
+
return await response.json()
|
|
754
|
+
f = asyncio.ensure_future(call())
|
|
755
|
+
await asyncio.sleep(.001) # make asyncio run the task
|
|
756
|
+
return f
|
|
757
|
+
|
|
758
|
+
async def send(self, sender=None, preview_url: bool = True) -> asyncio.Future:
|
|
759
|
+
try:
|
|
760
|
+
sender = dict(self.instance.l)[sender]
|
|
761
|
+
|
|
762
|
+
except:
|
|
763
|
+
sender = self.instance.phone_number_id
|
|
764
|
+
|
|
765
|
+
if sender == None:
|
|
766
|
+
sender = self.instance.phone_number_id
|
|
767
|
+
|
|
768
|
+
url = f"https://graph.facebook.com/{self.instance.LATEST}/{sender}/messages"
|
|
769
|
+
data = {
|
|
770
|
+
"messaging_product": "whatsapp",
|
|
771
|
+
"recipient_type": self.rec,
|
|
772
|
+
"to": self.to,
|
|
773
|
+
"type": "text",
|
|
774
|
+
"text": {"preview_url": preview_url, "body": self.content},
|
|
775
|
+
}
|
|
776
|
+
logging.info(f"Sending message to {self.to}")
|
|
777
|
+
async def call():
|
|
778
|
+
print("sending")
|
|
779
|
+
async with aiohttp.ClientSession() as session:
|
|
780
|
+
async with session.post(url, headers=self.headers, json=data) as response:
|
|
781
|
+
if response.status == 200:
|
|
782
|
+
logging.info(f"Message sent to {self.to}")
|
|
783
|
+
return await response.json()
|
|
784
|
+
logging.info(f"Message not sent to {self.to}")
|
|
785
|
+
logging.info(f"Status code: {response.status}")
|
|
786
|
+
logging.error(f"Response: {await response.json()}")
|
|
787
|
+
return await response.json()
|
|
788
|
+
f = asyncio.ensure_future(call())
|
|
789
|
+
await asyncio.sleep(.001) # make asyncio run the task
|
|
790
|
+
return f
|
|
791
|
+
|
|
792
|
+
async def react(self, emoji: str) -> asyncio.Future:
|
|
793
|
+
data = {
|
|
794
|
+
"messaging_product": "whatsapp",
|
|
795
|
+
"recipient_type": "individual",
|
|
796
|
+
"to": self.to,
|
|
797
|
+
"type": "reaction",
|
|
798
|
+
"reaction": {"message_id": self.id, "emoji": emoji},
|
|
799
|
+
}
|
|
800
|
+
logging.info(f"Reacting to {self.id}")
|
|
801
|
+
async def call():
|
|
802
|
+
async with aiohttp.ClientSession() as session:
|
|
803
|
+
async with session.post(self.url, headers=self.headers, json=data) as response:
|
|
804
|
+
if response.status == 200:
|
|
805
|
+
logging.info(f"Reaction sent to {self.to}")
|
|
806
|
+
return await response.json()
|
|
807
|
+
logging.info(f"Reaction not sent to {self.to}")
|
|
808
|
+
logging.info(f"Status code: {response.status}")
|
|
809
|
+
logging.debug(f"Response: {await response.json()}")
|
|
810
|
+
return await response.json()
|
|
811
|
+
f = asyncio.ensure_future(call())
|
|
812
|
+
await asyncio.sleep(.001)
|
|
813
|
+
return f
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import aiohttp
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Dict, Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def create_button(self, button: Dict[Any, Any]) -> Dict[Any, Any]:
|
|
8
|
+
"""
|
|
9
|
+
Method to create a button object to be used in the send_message method.
|
|
10
|
+
|
|
11
|
+
This is method is designed to only be used internally by the send_button method.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
button[dict]: A dictionary containing the button data
|
|
15
|
+
"""
|
|
16
|
+
data = {"type": "list", "action": button.get("action")}
|
|
17
|
+
if button.get("header"):
|
|
18
|
+
data["header"] = {"type": "text", "text": button.get("header")}
|
|
19
|
+
if button.get("body"):
|
|
20
|
+
data["body"] = {"text": button.get("body")}
|
|
21
|
+
if button.get("footer"):
|
|
22
|
+
data["footer"] = {"text": button.get("footer")}
|
|
23
|
+
if button.get("type"):
|
|
24
|
+
data["type"] = button.get("type")
|
|
25
|
+
return data
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def send_button(self, button: Dict[Any, Any], recipient_id: str, sender=None) -> asyncio.Future:
|
|
29
|
+
"""
|
|
30
|
+
Sends an interactive buttons message to a WhatsApp user
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
button[dict]: A dictionary containing the button data(rows-title may not exceed 20 characters)
|
|
34
|
+
recipient_id[str]: Phone number of the user with country code wihout +
|
|
35
|
+
|
|
36
|
+
check https://github.com/Neurotech-HQ/whatsapp#sending-interactive-reply-buttons for an example.
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
sender = dict(self.l)[sender]
|
|
40
|
+
|
|
41
|
+
except:
|
|
42
|
+
sender = self.phone_number_id
|
|
43
|
+
|
|
44
|
+
if sender == None:
|
|
45
|
+
sender = self.phone_number_id
|
|
46
|
+
|
|
47
|
+
url = f"https://graph.facebook.com/{self.LATEST}/{sender}/messages"
|
|
48
|
+
data = {
|
|
49
|
+
"messaging_product": "whatsapp",
|
|
50
|
+
"to": recipient_id,
|
|
51
|
+
"type": "interactive",
|
|
52
|
+
"interactive": self.create_button(button),
|
|
53
|
+
}
|
|
54
|
+
logging.info(f"Sending buttons to {recipient_id}")
|
|
55
|
+
async def call():
|
|
56
|
+
async with aiohttp.ClientSession() as session:
|
|
57
|
+
async with session.post(url, headers=self.headers, json=data) as r:
|
|
58
|
+
if r.status == 200:
|
|
59
|
+
logging.info(f"Buttons sent to {recipient_id}")
|
|
60
|
+
return await r.json()
|
|
61
|
+
logging.info(f"Buttons not sent to {recipient_id}")
|
|
62
|
+
logging.info(f"Status code: {r.status}")
|
|
63
|
+
logging.info(f"Response: {await r.json()}")
|
|
64
|
+
return await r.json()
|
|
65
|
+
f = asyncio.ensure_future(call())
|
|
66
|
+
await asyncio.sleep(.001) # make asyncio run the task
|
|
67
|
+
return f
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
async def send_reply_button(
|
|
72
|
+
self, button: Dict[Any, Any], recipient_id: str, sender=None
|
|
73
|
+
) -> asyncio.Future:
|
|
74
|
+
"""
|
|
75
|
+
Sends an interactive reply buttons[menu] message to a WhatsApp user
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
button[dict]: A dictionary containing the button data
|
|
79
|
+
recipient_id[str]: Phone number of the user with country code wihout +
|
|
80
|
+
|
|
81
|
+
Note:
|
|
82
|
+
The maximum number of buttons is 3, more than 3 buttons will rise an error.
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
sender = dict(self.l)[sender]
|
|
86
|
+
|
|
87
|
+
except:
|
|
88
|
+
sender = self.phone_number_id
|
|
89
|
+
|
|
90
|
+
if sender == None:
|
|
91
|
+
sender = self.phone_number_id
|
|
92
|
+
|
|
93
|
+
url = f"https://graph.facebook.com/{self.LATEST}/{sender}/messages"
|
|
94
|
+
if len(button["action"]["buttons"]) > 3:
|
|
95
|
+
raise ValueError("The maximum number of buttons is 3.")
|
|
96
|
+
|
|
97
|
+
data = {
|
|
98
|
+
"messaging_product": "whatsapp",
|
|
99
|
+
"recipient_type": "individual",
|
|
100
|
+
"to": recipient_id,
|
|
101
|
+
"type": "interactive",
|
|
102
|
+
"interactive": button,
|
|
103
|
+
}
|
|
104
|
+
logging.info(f"Sending buttons to {recipient_id}")
|
|
105
|
+
async def call():
|
|
106
|
+
async with aiohttp.ClientSession() as session:
|
|
107
|
+
async with session.post(url, headers=self.headers, json=data) as r:
|
|
108
|
+
if r.status == 200:
|
|
109
|
+
logging.info(f"Buttons sent to {recipient_id}")
|
|
110
|
+
return await r.json()
|
|
111
|
+
logging.info(f"Buttons not sent to {recipient_id}")
|
|
112
|
+
logging.info(f"Status code: {r.status}")
|
|
113
|
+
logging.info(f"Response: {await r.json()}")
|
|
114
|
+
return await r.json()
|
|
115
|
+
|
|
116
|
+
f = asyncio.ensure_future(call())
|
|
117
|
+
await asyncio.sleep(.001) # make asyncio run the task
|
|
118
|
+
return f
|