whatsapp-python 4.0.2__py3-none-any.whl → 4.3.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 +580 -35
- whatsapp/async_ext/_buttons.py +122 -0
- whatsapp/async_ext/_media.py +169 -0
- whatsapp/async_ext/_message.py +178 -0
- whatsapp/async_ext/_property.py +5 -0
- whatsapp/async_ext/_send_media.py +392 -0
- whatsapp/async_ext/_send_others.py +124 -0
- whatsapp/async_ext/_static.py +331 -0
- whatsapp/constants.py +1 -1
- whatsapp/errors.py +290 -0
- whatsapp/ext/_buttons.py +8 -3
- whatsapp/ext/_media.py +14 -10
- whatsapp/ext/_message.py +17 -7
- whatsapp/ext/_send_media.py +41 -30
- whatsapp/ext/_send_others.py +3 -2
- whatsapp/ext/_static.py +2 -0
- {whatsapp_python-4.0.2.dist-info → whatsapp_python-4.3.0.dist-info}/METADATA +16 -15
- whatsapp_python-4.3.0.dist-info/RECORD +21 -0
- {whatsapp_python-4.0.2.dist-info → whatsapp_python-4.3.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.3.0.dist-info}/licenses/LICENSE +0 -0
whatsapp/__init__.py
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Unofficial Python wrapper for the WhatsApp Cloud API.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from __future__ import annotations
|
|
5
6
|
import requests
|
|
6
7
|
import logging
|
|
8
|
+
import json
|
|
9
|
+
import asyncio
|
|
10
|
+
import aiohttp
|
|
7
11
|
from bs4 import BeautifulSoup
|
|
8
12
|
from fastapi import FastAPI, HTTPException, Request
|
|
9
13
|
from uvicorn import run as _run
|
|
@@ -11,15 +15,95 @@ from .constants import VERSION
|
|
|
11
15
|
from .ext._property import authorized
|
|
12
16
|
from .ext._send_others import send_custom_json, send_contacts
|
|
13
17
|
from .ext._message import send_template
|
|
14
|
-
from .ext._send_media import
|
|
18
|
+
from .ext._send_media import (
|
|
19
|
+
send_image,
|
|
20
|
+
send_video,
|
|
21
|
+
send_audio,
|
|
22
|
+
send_location,
|
|
23
|
+
send_sticker,
|
|
24
|
+
send_document,
|
|
25
|
+
)
|
|
15
26
|
from .ext._media import upload_media, query_media_url, download_media, delete_media
|
|
16
27
|
from .ext._buttons import send_button, create_button, send_reply_button
|
|
17
|
-
from .ext._static import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
from .ext._static import (
|
|
29
|
+
is_message,
|
|
30
|
+
get_mobile,
|
|
31
|
+
get_author,
|
|
32
|
+
get_name,
|
|
33
|
+
get_message,
|
|
34
|
+
get_message_id,
|
|
35
|
+
get_message_type,
|
|
36
|
+
get_message_timestamp,
|
|
37
|
+
get_audio,
|
|
38
|
+
get_delivery,
|
|
39
|
+
get_document,
|
|
40
|
+
get_image,
|
|
41
|
+
get_sticker,
|
|
42
|
+
get_interactive_response,
|
|
43
|
+
get_location,
|
|
44
|
+
get_video,
|
|
45
|
+
changed_field,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
from .async_ext._property import authorized as async_authorized
|
|
49
|
+
from .async_ext._send_others import (
|
|
50
|
+
send_custom_json as async_send_custom_json,
|
|
51
|
+
send_contacts as async_send_contacts,
|
|
52
|
+
)
|
|
53
|
+
from .async_ext._message import send_template as async_send_template
|
|
54
|
+
from .async_ext._send_media import (
|
|
55
|
+
send_image as async_send_image,
|
|
56
|
+
send_video as async_send_video,
|
|
57
|
+
send_audio as async_send_audio,
|
|
58
|
+
send_location as async_send_location,
|
|
59
|
+
send_sticker as async_send_sticker,
|
|
60
|
+
send_document as async_send_document,
|
|
61
|
+
)
|
|
62
|
+
from .async_ext._media import (
|
|
63
|
+
upload_media as async_upload_media,
|
|
64
|
+
query_media_url as async_query_media_url,
|
|
65
|
+
download_media as async_download_media,
|
|
66
|
+
delete_media as async_delete_media,
|
|
67
|
+
)
|
|
68
|
+
from .async_ext._buttons import (
|
|
69
|
+
send_button as async_send_button,
|
|
70
|
+
create_button as async_create_button,
|
|
71
|
+
send_reply_button as async_send_reply_button,
|
|
72
|
+
)
|
|
73
|
+
from .async_ext._static import (
|
|
74
|
+
is_message as async_is_message,
|
|
75
|
+
get_mobile as async_get_mobile,
|
|
76
|
+
get_author as async_get_author,
|
|
77
|
+
get_name as async_get_name,
|
|
78
|
+
get_message as async_get_message,
|
|
79
|
+
get_message_id as async_get_message_id,
|
|
80
|
+
get_message_type as async_get_message_type,
|
|
81
|
+
get_message_timestamp as async_get_message_timestamp,
|
|
82
|
+
get_audio as async_get_audio,
|
|
83
|
+
get_delivery as async_get_delivery,
|
|
84
|
+
get_document as async_get_document,
|
|
85
|
+
get_image as async_get_image,
|
|
86
|
+
get_sticker as async_get_sticker,
|
|
87
|
+
get_interactive_response as async_get_interactive_response,
|
|
88
|
+
get_location as async_get_location,
|
|
89
|
+
get_video as async_get_video,
|
|
90
|
+
changed_field as async_changed_field,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
from .errors import Handle
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class WhatsApp:
|
|
97
|
+
def __init__(
|
|
98
|
+
self,
|
|
99
|
+
token: str = "",
|
|
100
|
+
phone_number_id: dict = "",
|
|
101
|
+
logger: bool = True,
|
|
102
|
+
update_check: bool = True,
|
|
103
|
+
verify_token: str = "",
|
|
104
|
+
debug: bool = True,
|
|
105
|
+
version: str = "latest",
|
|
106
|
+
):
|
|
23
107
|
"""
|
|
24
108
|
Initialize the WhatsApp Object
|
|
25
109
|
|
|
@@ -40,42 +124,49 @@ class WhatsApp(object):
|
|
|
40
124
|
|
|
41
125
|
elif phone_number_id == "":
|
|
42
126
|
logging.error("Phone number ID not provided")
|
|
43
|
-
raise ValueError(
|
|
44
|
-
"Phone number ID not provided but required")
|
|
127
|
+
raise ValueError("Phone number ID not provided but required")
|
|
45
128
|
elif isinstance(phone_number_id, str):
|
|
46
129
|
logging.critical(
|
|
47
|
-
"The phone number ID should be a dictionary of phone numbers and their IDs. Using strings is deprecated."
|
|
130
|
+
"The phone number ID should be a dictionary of phone numbers and their IDs. Using strings is deprecated."
|
|
131
|
+
)
|
|
48
132
|
raise ValueError(
|
|
49
|
-
"Phone number ID should be a dictionary of phone numbers and their IDs"
|
|
133
|
+
"Phone number ID should be a dictionary of phone numbers and their IDs"
|
|
134
|
+
)
|
|
50
135
|
else:
|
|
51
136
|
pass
|
|
52
137
|
|
|
53
138
|
self.VERSION = VERSION # package version
|
|
54
|
-
r = requests.get(
|
|
139
|
+
r = requests.get(
|
|
140
|
+
"https://developers.facebook.com/docs/graph-api/changelog/"
|
|
141
|
+
).text
|
|
55
142
|
|
|
56
143
|
# dynamically get the latest version of the API
|
|
57
144
|
if version == "latest":
|
|
58
145
|
soup = BeautifulSoup(r, features="html.parser")
|
|
59
146
|
t1 = soup.findAll("table")
|
|
60
147
|
|
|
61
|
-
def makeversion(table):
|
|
148
|
+
def makeversion(table: BeautifulSoup) -> str:
|
|
62
149
|
result = []
|
|
63
|
-
allrows = table.findAll(
|
|
150
|
+
allrows = table.findAll("tr")
|
|
64
151
|
for row in allrows:
|
|
65
152
|
result.append([])
|
|
66
|
-
allcols = row.findAll(
|
|
153
|
+
allcols = row.findAll("td")
|
|
67
154
|
for col in allcols:
|
|
68
155
|
thestrings = [(s) for s in col.findAll(text=True)]
|
|
69
|
-
thetext =
|
|
156
|
+
thetext = "".join(thestrings)
|
|
70
157
|
result[-1].append(thetext)
|
|
71
158
|
return result[0][1]
|
|
159
|
+
|
|
72
160
|
self.LATEST = makeversion(t1[0]) # latest version of the API
|
|
73
161
|
else:
|
|
74
162
|
self.LATEST = version
|
|
75
163
|
|
|
76
164
|
if update_check is True:
|
|
77
|
-
latest = str(
|
|
78
|
-
"https://pypi.org/pypi/whatsapp-python/json").json()[
|
|
165
|
+
latest = str(
|
|
166
|
+
requests.get("https://pypi.org/pypi/whatsapp-python/json").json()[
|
|
167
|
+
"info"
|
|
168
|
+
]["version"]
|
|
169
|
+
)
|
|
79
170
|
if self.VERSION != latest:
|
|
80
171
|
try:
|
|
81
172
|
version_int = int(self.VERSION.replace(".", ""))
|
|
@@ -89,18 +180,19 @@ class WhatsApp(object):
|
|
|
89
180
|
if version_int < latest_int:
|
|
90
181
|
if version_int == 0:
|
|
91
182
|
logging.critical(
|
|
92
|
-
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."
|
|
183
|
+
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."
|
|
184
|
+
)
|
|
93
185
|
else:
|
|
94
186
|
logging.critical(
|
|
95
|
-
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."
|
|
187
|
+
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."
|
|
188
|
+
)
|
|
96
189
|
|
|
97
190
|
if token == "":
|
|
98
191
|
logging.error("Token not provided")
|
|
99
192
|
raise ValueError("Token not provided but required")
|
|
100
193
|
if phone_number_id == "":
|
|
101
194
|
logging.error("Phone number ID not provided")
|
|
102
|
-
raise ValueError(
|
|
103
|
-
"Phone number ID not provided but required")
|
|
195
|
+
raise ValueError("Phone number ID not provided but required")
|
|
104
196
|
self.token = token
|
|
105
197
|
self.phone_number_id = phone_number_id
|
|
106
198
|
self.base_url = f"https://graph.facebook.com/{self.LATEST}"
|
|
@@ -109,12 +201,13 @@ class WhatsApp(object):
|
|
|
109
201
|
|
|
110
202
|
async def base(*args):
|
|
111
203
|
pass
|
|
204
|
+
|
|
112
205
|
self.message_handler = base
|
|
113
206
|
self.other_handler = base
|
|
114
207
|
self.verification_handler = base
|
|
115
208
|
self.headers = {
|
|
116
209
|
"Content-Type": "application/json",
|
|
117
|
-
"Authorization": f"Bearer {self.token}"
|
|
210
|
+
"Authorization": f"Bearer {self.token}",
|
|
118
211
|
}
|
|
119
212
|
if logger is False:
|
|
120
213
|
logging.disable(logging.INFO)
|
|
@@ -132,8 +225,8 @@ class WhatsApp(object):
|
|
|
132
225
|
if r.query_params.get("hub.verify_token") == self.verify_token:
|
|
133
226
|
logging.debug("Webhook verified successfully")
|
|
134
227
|
challenge = r.query_params.get("hub.challenge")
|
|
135
|
-
self.verification_handler(challenge)
|
|
136
|
-
self.other_handler(challenge)
|
|
228
|
+
await self.verification_handler(challenge)
|
|
229
|
+
await self.other_handler(challenge)
|
|
137
230
|
return int(challenge)
|
|
138
231
|
logging.error("Webhook Verification failed - token mismatch")
|
|
139
232
|
await self.verification_handler(False)
|
|
@@ -161,10 +254,9 @@ class WhatsApp(object):
|
|
|
161
254
|
return {"success": True}
|
|
162
255
|
except Exception as e:
|
|
163
256
|
logging.error(f"Error parsing message: {e}")
|
|
164
|
-
raise HTTPException(
|
|
165
|
-
"success": False,
|
|
166
|
-
|
|
167
|
-
})
|
|
257
|
+
raise HTTPException(
|
|
258
|
+
status_code=500, detail={"success": False, "error": str(e)}
|
|
259
|
+
)
|
|
168
260
|
|
|
169
261
|
# all the files starting with _ are imported here, and should not be imported directly.
|
|
170
262
|
|
|
@@ -202,7 +294,6 @@ class WhatsApp(object):
|
|
|
202
294
|
send_template = send_template
|
|
203
295
|
send_custom_json = send_custom_json
|
|
204
296
|
send_contacts = send_contacts
|
|
205
|
-
|
|
206
297
|
authorized = property(authorized)
|
|
207
298
|
|
|
208
299
|
def create_message(self, **kwargs) -> Message:
|
|
@@ -248,9 +339,265 @@ class WhatsApp(object):
|
|
|
248
339
|
_run(self.app, host=host, port=port, **options)
|
|
249
340
|
|
|
250
341
|
|
|
251
|
-
class
|
|
342
|
+
class AsyncWhatsApp(WhatsApp):
|
|
343
|
+
def __init__(
|
|
344
|
+
self,
|
|
345
|
+
token: str = "",
|
|
346
|
+
phone_number_id: dict = "",
|
|
347
|
+
logger: bool = True,
|
|
348
|
+
update_check: bool = True,
|
|
349
|
+
verify_token: str = "",
|
|
350
|
+
debug: bool = True,
|
|
351
|
+
version: str = "latest",
|
|
352
|
+
):
|
|
353
|
+
"""
|
|
354
|
+
Initialize the WhatsApp Object
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
token[str]: Token for the WhatsApp cloud API obtained from the Facebook developer portal
|
|
358
|
+
phone_number_id[str]: Phone number id for the WhatsApp cloud API obtained from the developer portal
|
|
359
|
+
logger[bool]: Whether to enable logging or not (default: True)
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
# Check if the version is up to date
|
|
363
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
364
|
+
self.l = phone_number_id
|
|
365
|
+
|
|
366
|
+
if isinstance(phone_number_id, dict):
|
|
367
|
+
# use first phone number id as default
|
|
368
|
+
phone_number_id = phone_number_id[list(phone_number_id.keys())[0]]
|
|
369
|
+
|
|
370
|
+
elif phone_number_id == "":
|
|
371
|
+
logging.error("Phone number ID not provided")
|
|
372
|
+
raise ValueError("Phone number ID not provided but required")
|
|
373
|
+
elif isinstance(phone_number_id, str):
|
|
374
|
+
logging.critical(
|
|
375
|
+
"The phone number ID should be a dictionary of phone numbers and their IDs. Using strings is deprecated."
|
|
376
|
+
)
|
|
377
|
+
raise ValueError(
|
|
378
|
+
"Phone number ID should be a dictionary of phone numbers and their IDs"
|
|
379
|
+
)
|
|
380
|
+
else:
|
|
381
|
+
pass
|
|
382
|
+
|
|
383
|
+
self.VERSION = VERSION # package version
|
|
384
|
+
r = requests.get(
|
|
385
|
+
"https://developers.facebook.com/docs/graph-api/changelog/"
|
|
386
|
+
).text
|
|
387
|
+
|
|
388
|
+
# dynamically get the latest version of the API
|
|
389
|
+
if version == "latest":
|
|
390
|
+
soup = BeautifulSoup(r, features="html.parser")
|
|
391
|
+
t1 = soup.findAll("table")
|
|
392
|
+
|
|
393
|
+
def makeversion(table: BeautifulSoup) -> str:
|
|
394
|
+
result = []
|
|
395
|
+
allrows = table.findAll("tr")
|
|
396
|
+
for row in allrows:
|
|
397
|
+
result.append([])
|
|
398
|
+
allcols = row.findAll("td")
|
|
399
|
+
for col in allcols:
|
|
400
|
+
thestrings = [(s) for s in col.findAll(text=True)]
|
|
401
|
+
thetext = "".join(thestrings)
|
|
402
|
+
result[-1].append(thetext)
|
|
403
|
+
return result[0][1]
|
|
404
|
+
|
|
405
|
+
self.LATEST = makeversion(t1[0]) # latest version of the API
|
|
406
|
+
else:
|
|
407
|
+
self.LATEST = version
|
|
408
|
+
|
|
409
|
+
if update_check is True:
|
|
410
|
+
latest = str(
|
|
411
|
+
requests.get("https://pypi.org/pypi/whatsapp-python/json").json()[
|
|
412
|
+
"info"
|
|
413
|
+
]["version"]
|
|
414
|
+
)
|
|
415
|
+
if self.VERSION != latest:
|
|
416
|
+
try:
|
|
417
|
+
version_int = int(self.VERSION.replace(".", ""))
|
|
418
|
+
except:
|
|
419
|
+
version_int = 0
|
|
420
|
+
try:
|
|
421
|
+
latest_int = int(latest.replace(".", ""))
|
|
422
|
+
except:
|
|
423
|
+
latest_int = 0
|
|
424
|
+
# 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)
|
|
425
|
+
if version_int < latest_int:
|
|
426
|
+
if version_int == 0:
|
|
427
|
+
logging.critical(
|
|
428
|
+
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."
|
|
429
|
+
)
|
|
430
|
+
else:
|
|
431
|
+
logging.critical(
|
|
432
|
+
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."
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
if token == "":
|
|
436
|
+
logging.error("Token not provided")
|
|
437
|
+
raise ValueError("Token not provided but required")
|
|
438
|
+
if phone_number_id == "":
|
|
439
|
+
logging.error("Phone number ID not provided")
|
|
440
|
+
raise ValueError("Phone number ID not provided but required")
|
|
441
|
+
self.token = token
|
|
442
|
+
self.phone_number_id = phone_number_id
|
|
443
|
+
self.base_url = f"https://graph.facebook.com/{self.LATEST}"
|
|
444
|
+
self.url = f"{self.base_url}/{phone_number_id}/messages"
|
|
445
|
+
self.verify_token = verify_token
|
|
446
|
+
|
|
447
|
+
async def base(*args):
|
|
448
|
+
pass
|
|
449
|
+
|
|
450
|
+
self.message_handler = base
|
|
451
|
+
self.other_handler = base
|
|
452
|
+
self.verification_handler = base
|
|
453
|
+
self.headers = {
|
|
454
|
+
"Content-Type": "application/json",
|
|
455
|
+
"Authorization": f"Bearer {self.token}",
|
|
456
|
+
}
|
|
457
|
+
if logger is False:
|
|
458
|
+
logging.disable(logging.INFO)
|
|
459
|
+
logging.disable(logging.ERROR)
|
|
460
|
+
if debug is False:
|
|
461
|
+
logging.disable(logging.DEBUG)
|
|
462
|
+
logging.disable(logging.ERROR)
|
|
463
|
+
|
|
464
|
+
self.app = FastAPI()
|
|
465
|
+
|
|
466
|
+
# Verification handler has 1 argument: challenge (str | bool): str if verification is successful, False if not
|
|
467
|
+
|
|
468
|
+
@self.app.get("/")
|
|
469
|
+
async def verify_endpoint(r: Request):
|
|
470
|
+
if r.query_params.get("hub.verify_token") == self.verify_token:
|
|
471
|
+
logging.debug("Webhook verified successfully")
|
|
472
|
+
challenge = r.query_params.get("hub.challenge")
|
|
473
|
+
await self.verification_handler(challenge)
|
|
474
|
+
await self.other_handler(challenge)
|
|
475
|
+
return int(challenge)
|
|
476
|
+
logging.error("Webhook Verification failed - token mismatch")
|
|
477
|
+
await self.verification_handler(False)
|
|
478
|
+
await self.other_handler(False)
|
|
479
|
+
return {"success": False}
|
|
480
|
+
|
|
481
|
+
@self.app.post("/")
|
|
482
|
+
async def hook(r: Request):
|
|
483
|
+
try:
|
|
484
|
+
# Handle Webhook Subscriptions
|
|
485
|
+
data = await r.json()
|
|
486
|
+
if data is None:
|
|
487
|
+
return {"success": False}
|
|
488
|
+
data_str = json.dumps(data, indent=4)
|
|
489
|
+
# log the data received only if the log level is debug
|
|
490
|
+
logging.debug(f"Received webhook data: {data_str}")
|
|
491
|
+
|
|
492
|
+
changed_field = self.changed_field(data)
|
|
493
|
+
if changed_field == "messages":
|
|
494
|
+
new_message = self.is_message(data)
|
|
495
|
+
if new_message:
|
|
496
|
+
msg = Message(instance=self, data=data)
|
|
497
|
+
await self.message_handler(msg)
|
|
498
|
+
await self.other_handler(msg)
|
|
499
|
+
return {"success": True}
|
|
500
|
+
except Exception as e:
|
|
501
|
+
logging.error(f"Error parsing message: {e}")
|
|
502
|
+
raise HTTPException(
|
|
503
|
+
status_code=500, detail={"success": False, "error": str(e)}
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
# all the files starting with _ are imported here, and should not be imported directly.
|
|
507
|
+
|
|
508
|
+
is_message = staticmethod(is_message)
|
|
509
|
+
get_mobile = staticmethod(get_mobile)
|
|
510
|
+
get_name = staticmethod(get_name)
|
|
511
|
+
get_message = staticmethod(get_message)
|
|
512
|
+
get_message_id = staticmethod(get_message_id)
|
|
513
|
+
get_message_type = staticmethod(get_message_type)
|
|
514
|
+
get_message_timestamp = staticmethod(get_message_timestamp)
|
|
515
|
+
get_audio = staticmethod(get_audio)
|
|
516
|
+
get_delivery = staticmethod(get_delivery)
|
|
517
|
+
get_document = staticmethod(get_document)
|
|
518
|
+
get_image = staticmethod(get_image)
|
|
519
|
+
get_sticker = staticmethod(get_sticker)
|
|
520
|
+
get_interactive_response = staticmethod(get_interactive_response)
|
|
521
|
+
get_location = staticmethod(get_location)
|
|
522
|
+
get_video = staticmethod(get_video)
|
|
523
|
+
changed_field = staticmethod(changed_field)
|
|
524
|
+
get_author = staticmethod(get_author)
|
|
525
|
+
|
|
526
|
+
send_button = async_send_button
|
|
527
|
+
create_button = async_create_button
|
|
528
|
+
send_reply_button = async_send_reply_button
|
|
529
|
+
send_image = async_send_image
|
|
530
|
+
send_video = async_send_video
|
|
531
|
+
send_audio = async_send_audio
|
|
532
|
+
send_location = async_send_location
|
|
533
|
+
send_sticker = async_send_sticker
|
|
534
|
+
send_document = async_send_document
|
|
535
|
+
upload_media = async_upload_media
|
|
536
|
+
query_media_url = async_query_media_url
|
|
537
|
+
download_media = async_download_media
|
|
538
|
+
delete_media = async_delete_media
|
|
539
|
+
send_template = async_send_template
|
|
540
|
+
send_custom_json = async_send_custom_json
|
|
541
|
+
send_contacts = async_send_contacts
|
|
542
|
+
authorized = property(async_authorized)
|
|
543
|
+
|
|
544
|
+
def handle(self, data: dict):
|
|
545
|
+
return Handle(data)
|
|
546
|
+
|
|
547
|
+
def create_message(self, **kwargs) -> AsyncMessage:
|
|
548
|
+
"""
|
|
549
|
+
Create a message object
|
|
550
|
+
|
|
551
|
+
Args:
|
|
552
|
+
data[dict]: The message data
|
|
553
|
+
content[str]: The message content
|
|
554
|
+
to[str]: The recipient
|
|
555
|
+
rec_type[str]: The recipient type (individual/group)
|
|
556
|
+
"""
|
|
557
|
+
return AsyncMessage(**kwargs, instance=self)
|
|
558
|
+
|
|
559
|
+
def on_message(self, handler: function):
|
|
560
|
+
"""
|
|
561
|
+
Set the handler for incoming messages
|
|
562
|
+
|
|
563
|
+
Args:
|
|
564
|
+
handler[function]: The handler function
|
|
565
|
+
"""
|
|
566
|
+
self.message_handler = handler
|
|
567
|
+
|
|
568
|
+
def on_event(self, handler: function):
|
|
569
|
+
"""
|
|
570
|
+
Set the handler for other events
|
|
571
|
+
|
|
572
|
+
Args:
|
|
573
|
+
handler[function]: The handler function
|
|
574
|
+
"""
|
|
575
|
+
self.other_handler = handler
|
|
576
|
+
|
|
577
|
+
def on_verification(self, handler: function):
|
|
578
|
+
"""
|
|
579
|
+
Set the handler for verification
|
|
580
|
+
|
|
581
|
+
Args:
|
|
582
|
+
handler[function]: The handler function
|
|
583
|
+
"""
|
|
584
|
+
self.verification_handler = handler
|
|
585
|
+
|
|
586
|
+
def run(self, host: str = "localhost", port: int = 5000, **options):
|
|
587
|
+
_run(self.app, host=host, port=port, **options)
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
class Message:
|
|
252
591
|
# type: ignore
|
|
253
|
-
def __init__(
|
|
592
|
+
def __init__(
|
|
593
|
+
self,
|
|
594
|
+
id: int = None,
|
|
595
|
+
data: dict = {},
|
|
596
|
+
instance: WhatsApp = None,
|
|
597
|
+
content: str = "",
|
|
598
|
+
to: str = "",
|
|
599
|
+
rec_type: str = "individual",
|
|
600
|
+
):
|
|
254
601
|
self.instance = instance
|
|
255
602
|
self.url = self.instance.url
|
|
256
603
|
self.headers = self.instance.headers
|
|
@@ -267,8 +614,7 @@ class Message(object):
|
|
|
267
614
|
self.rec = rec_type
|
|
268
615
|
self.to = to
|
|
269
616
|
try:
|
|
270
|
-
self.content = content if content != "" else self.instance.get_message(
|
|
271
|
-
data)
|
|
617
|
+
self.content = content if content != "" else self.instance.get_message(data)
|
|
272
618
|
except:
|
|
273
619
|
self.content = content
|
|
274
620
|
try:
|
|
@@ -343,7 +689,8 @@ class Message(object):
|
|
|
343
689
|
}
|
|
344
690
|
|
|
345
691
|
response = requests.post(
|
|
346
|
-
f"{self.instance.url}", headers=self.instance.headers, json=payload
|
|
692
|
+
f"{self.instance.url}", headers=self.instance.headers, json=payload
|
|
693
|
+
)
|
|
347
694
|
if response.status_code == 200:
|
|
348
695
|
logging.info(response.json())
|
|
349
696
|
return response.json()
|
|
@@ -397,3 +744,201 @@ class Message(object):
|
|
|
397
744
|
logging.info(f"Status code: {r.status_code}")
|
|
398
745
|
logging.debug(f"Response: {r.json()}")
|
|
399
746
|
return r.json()
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
class AsyncMessage:
|
|
750
|
+
# type: ignore
|
|
751
|
+
def __init__(
|
|
752
|
+
self,
|
|
753
|
+
id: int = None,
|
|
754
|
+
data: dict = {},
|
|
755
|
+
instance: WhatsApp = None,
|
|
756
|
+
content: str = "",
|
|
757
|
+
to: str = "",
|
|
758
|
+
rec_type: str = "individual",
|
|
759
|
+
):
|
|
760
|
+
self.instance = instance
|
|
761
|
+
self.url = self.instance.url
|
|
762
|
+
self.headers = self.instance.headers
|
|
763
|
+
|
|
764
|
+
try:
|
|
765
|
+
self.id = instance.get_message_id(data)
|
|
766
|
+
except:
|
|
767
|
+
self.id = id
|
|
768
|
+
try:
|
|
769
|
+
self.type = self.instance.get_message_type(data)
|
|
770
|
+
except:
|
|
771
|
+
self.type = "text"
|
|
772
|
+
self.data = data
|
|
773
|
+
self.rec = rec_type
|
|
774
|
+
self.to = to
|
|
775
|
+
try:
|
|
776
|
+
self.content = content if content != "" else self.instance.get_message(data)
|
|
777
|
+
except:
|
|
778
|
+
self.content = content
|
|
779
|
+
try:
|
|
780
|
+
self.name = self.instance.get_name(data)
|
|
781
|
+
except:
|
|
782
|
+
self.name = None
|
|
783
|
+
|
|
784
|
+
if self.type == "image":
|
|
785
|
+
try:
|
|
786
|
+
self.image = self.instance.get_image(data)
|
|
787
|
+
except:
|
|
788
|
+
self.image = None
|
|
789
|
+
if self.type == "sticker":
|
|
790
|
+
try:
|
|
791
|
+
self.sticker = self.instance.get_sticker(data)
|
|
792
|
+
except:
|
|
793
|
+
self.sticker = None
|
|
794
|
+
elif self.type == "video":
|
|
795
|
+
try:
|
|
796
|
+
self.video = self.instance.get_video(data)
|
|
797
|
+
except:
|
|
798
|
+
self.video = None
|
|
799
|
+
elif self.type == "audio":
|
|
800
|
+
try:
|
|
801
|
+
self.audio = self.instance.get_audio(data)
|
|
802
|
+
except:
|
|
803
|
+
pass
|
|
804
|
+
elif self.type == "document":
|
|
805
|
+
try:
|
|
806
|
+
self.document = self.instance.get_document(data)
|
|
807
|
+
except:
|
|
808
|
+
pass
|
|
809
|
+
elif self.type == "location":
|
|
810
|
+
try:
|
|
811
|
+
self.location = self.instance.get_location(data)
|
|
812
|
+
except:
|
|
813
|
+
pass
|
|
814
|
+
elif self.type == "interactive":
|
|
815
|
+
try:
|
|
816
|
+
self.interactive = self.instance.get_interactive_response(data)
|
|
817
|
+
except:
|
|
818
|
+
pass
|
|
819
|
+
|
|
820
|
+
async def reply(
|
|
821
|
+
self, reply_text: str = "", preview_url: bool = True
|
|
822
|
+
) -> asyncio.Future:
|
|
823
|
+
if self.data == {}:
|
|
824
|
+
return {"error": "No data provided"}
|
|
825
|
+
author = self.instance.get_author(self.data)
|
|
826
|
+
payload = {
|
|
827
|
+
"messaging_product": "whatsapp",
|
|
828
|
+
"recipient_type": "individual",
|
|
829
|
+
"to": str(author),
|
|
830
|
+
"type": "text",
|
|
831
|
+
"context": {"message_id": self.id},
|
|
832
|
+
"text": {"preview_url": preview_url, "body": reply_text},
|
|
833
|
+
}
|
|
834
|
+
logging.info(f"Replying to {self.id}")
|
|
835
|
+
|
|
836
|
+
async def call():
|
|
837
|
+
async with aiohttp.ClientSession() as session:
|
|
838
|
+
async with session.post(
|
|
839
|
+
self.url, headers=self.headers, json=payload
|
|
840
|
+
) as response:
|
|
841
|
+
if response.status == 200:
|
|
842
|
+
logging.info(
|
|
843
|
+
f"Message sent to {self.instance.get_author(self.data)}"
|
|
844
|
+
)
|
|
845
|
+
return await response.json()
|
|
846
|
+
logging.info(
|
|
847
|
+
f"Message not sent to {self.instance.get_author(self.data)}"
|
|
848
|
+
)
|
|
849
|
+
logging.info(f"Status code: {response.status}")
|
|
850
|
+
logging.error(f"Response: {await response.json()}")
|
|
851
|
+
return await response.json()
|
|
852
|
+
|
|
853
|
+
f = asyncio.ensure_future(call())
|
|
854
|
+
await asyncio.sleep(0.001) # make asyncio run the task
|
|
855
|
+
return f
|
|
856
|
+
|
|
857
|
+
async def mark_as_read(self) -> asyncio.Future:
|
|
858
|
+
|
|
859
|
+
payload = {
|
|
860
|
+
"messaging_product": "whatsapp",
|
|
861
|
+
"status": "read",
|
|
862
|
+
"message_id": self.id,
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
async def call():
|
|
866
|
+
async with aiohttp.ClientSession() as session:
|
|
867
|
+
async with session.post(
|
|
868
|
+
f"{self.instance.url}", headers=self.instance.headers, json=payload
|
|
869
|
+
) as response:
|
|
870
|
+
if response.status == 200:
|
|
871
|
+
logging.info(await response.json())
|
|
872
|
+
return await response.json()
|
|
873
|
+
else:
|
|
874
|
+
logging.error(await response.json())
|
|
875
|
+
return await response.json()
|
|
876
|
+
|
|
877
|
+
f = asyncio.ensure_future(call())
|
|
878
|
+
await asyncio.sleep(0.001) # make asyncio run the task
|
|
879
|
+
return f
|
|
880
|
+
|
|
881
|
+
async def send(self, sender=None, preview_url: bool = True) -> asyncio.Future:
|
|
882
|
+
try:
|
|
883
|
+
sender = dict(self.instance.l)[sender]
|
|
884
|
+
|
|
885
|
+
except:
|
|
886
|
+
sender = self.instance.phone_number_id
|
|
887
|
+
|
|
888
|
+
if sender == None:
|
|
889
|
+
sender = self.instance.phone_number_id
|
|
890
|
+
|
|
891
|
+
url = f"https://graph.facebook.com/{self.instance.LATEST}/{sender}/messages"
|
|
892
|
+
data = {
|
|
893
|
+
"messaging_product": "whatsapp",
|
|
894
|
+
"recipient_type": self.rec,
|
|
895
|
+
"to": self.to,
|
|
896
|
+
"type": "text",
|
|
897
|
+
"text": {"preview_url": preview_url, "body": self.content},
|
|
898
|
+
}
|
|
899
|
+
logging.info(f"Sending message to {self.to}")
|
|
900
|
+
|
|
901
|
+
async def call():
|
|
902
|
+
print("sending")
|
|
903
|
+
async with aiohttp.ClientSession() as session:
|
|
904
|
+
async with session.post(
|
|
905
|
+
url, headers=self.headers, json=data
|
|
906
|
+
) as response:
|
|
907
|
+
if response.status == 200:
|
|
908
|
+
logging.info(f"Message sent to {self.to}")
|
|
909
|
+
return await response.json()
|
|
910
|
+
logging.info(f"Message not sent to {self.to}")
|
|
911
|
+
logging.info(f"Status code: {response.status}")
|
|
912
|
+
logging.error(f"Response: {await response.json()}")
|
|
913
|
+
return await response.json()
|
|
914
|
+
|
|
915
|
+
f = asyncio.ensure_future(call())
|
|
916
|
+
await asyncio.sleep(0.001) # make asyncio run the task
|
|
917
|
+
return f
|
|
918
|
+
|
|
919
|
+
async def react(self, emoji: str) -> asyncio.Future:
|
|
920
|
+
data = {
|
|
921
|
+
"messaging_product": "whatsapp",
|
|
922
|
+
"recipient_type": "individual",
|
|
923
|
+
"to": self.to,
|
|
924
|
+
"type": "reaction",
|
|
925
|
+
"reaction": {"message_id": self.id, "emoji": emoji},
|
|
926
|
+
}
|
|
927
|
+
logging.info(f"Reacting to {self.id}")
|
|
928
|
+
|
|
929
|
+
async def call():
|
|
930
|
+
async with aiohttp.ClientSession() as session:
|
|
931
|
+
async with session.post(
|
|
932
|
+
self.url, headers=self.headers, json=data
|
|
933
|
+
) as response:
|
|
934
|
+
if response.status == 200:
|
|
935
|
+
logging.info(f"Reaction sent to {self.to}")
|
|
936
|
+
return await response.json()
|
|
937
|
+
logging.info(f"Reaction not sent to {self.to}")
|
|
938
|
+
logging.info(f"Status code: {response.status}")
|
|
939
|
+
logging.debug(f"Response: {await response.json()}")
|
|
940
|
+
return await response.json()
|
|
941
|
+
|
|
942
|
+
f = asyncio.ensure_future(call())
|
|
943
|
+
await asyncio.sleep(0.001)
|
|
944
|
+
return f
|