whatsapp-python 4.2.0__py3-none-any.whl → 4.4.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 +363 -358
- whatsapp/async_ext/_buttons.py +119 -7
- whatsapp/async_ext/_media.py +27 -27
- whatsapp/async_ext/_message.py +39 -21
- whatsapp/async_ext/_send_media.py +91 -77
- whatsapp/async_ext/_send_others.py +12 -8
- whatsapp/async_ext/_static.py +18 -0
- whatsapp/constants.py +5 -1
- whatsapp/errors.py +340 -0
- whatsapp/ext/_buttons.py +102 -5
- whatsapp/ext/_media.py +14 -9
- whatsapp/ext/_message.py +33 -19
- whatsapp/ext/_send_media.py +79 -69
- whatsapp/ext/_send_others.py +5 -4
- whatsapp/ext/_static.py +18 -0
- whatsapp/graph_api.py +113 -0
- {whatsapp_python-4.2.0.dist-info → whatsapp_python-4.4.0.dist-info}/METADATA +26 -21
- whatsapp_python-4.4.0.dist-info/RECORD +22 -0
- {whatsapp_python-4.2.0.dist-info → whatsapp_python-4.4.0.dist-info}/WHEEL +1 -1
- {whatsapp_python-4.2.0.dist-info → whatsapp_python-4.4.0.dist-info}/licenses/LICENSE +1 -1
- whatsapp_python-4.2.0.dist-info/RECORD +0 -20
whatsapp/__init__.py
CHANGED
|
@@ -1,45 +1,178 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Unofficial Python wrapper for the WhatsApp Cloud API.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
import logging
|
|
7
|
-
import json
|
|
6
|
+
|
|
8
7
|
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any, Callable, Optional, Union
|
|
10
|
+
|
|
9
11
|
import aiohttp
|
|
10
|
-
|
|
12
|
+
import requests
|
|
11
13
|
from fastapi import FastAPI, HTTPException, Request
|
|
12
14
|
from uvicorn import run as _run
|
|
15
|
+
|
|
13
16
|
from .constants import VERSION
|
|
17
|
+
from .errors import Handle
|
|
18
|
+
from .graph_api import resolve_graph_api_version
|
|
14
19
|
from .ext._property import authorized
|
|
15
20
|
from .ext._send_others import send_custom_json, send_contacts
|
|
16
21
|
from .ext._message import send_template
|
|
17
|
-
from .ext._send_media import
|
|
22
|
+
from .ext._send_media import (
|
|
23
|
+
send_image,
|
|
24
|
+
send_video,
|
|
25
|
+
send_audio,
|
|
26
|
+
send_location,
|
|
27
|
+
send_sticker,
|
|
28
|
+
send_document,
|
|
29
|
+
)
|
|
18
30
|
from .ext._media import upload_media, query_media_url, download_media, delete_media
|
|
19
|
-
from .ext._buttons import
|
|
20
|
-
|
|
31
|
+
from .ext._buttons import (
|
|
32
|
+
send_button,
|
|
33
|
+
create_button,
|
|
34
|
+
send_reply_button,
|
|
35
|
+
send_cta_url,
|
|
36
|
+
send_location_request,
|
|
37
|
+
)
|
|
38
|
+
from .ext._static import (
|
|
39
|
+
is_message,
|
|
40
|
+
get_mobile,
|
|
41
|
+
get_author,
|
|
42
|
+
get_name,
|
|
43
|
+
get_message,
|
|
44
|
+
get_message_id,
|
|
45
|
+
get_message_type,
|
|
46
|
+
get_message_timestamp,
|
|
47
|
+
get_audio,
|
|
48
|
+
get_delivery,
|
|
49
|
+
get_document,
|
|
50
|
+
get_image,
|
|
51
|
+
get_sticker,
|
|
52
|
+
get_interactive_response,
|
|
53
|
+
get_location,
|
|
54
|
+
get_video,
|
|
55
|
+
get_reaction,
|
|
56
|
+
changed_field,
|
|
57
|
+
)
|
|
21
58
|
|
|
22
59
|
from .async_ext._property import authorized as async_authorized
|
|
23
|
-
from .async_ext._send_others import
|
|
60
|
+
from .async_ext._send_others import (
|
|
61
|
+
send_custom_json as async_send_custom_json,
|
|
62
|
+
send_contacts as async_send_contacts,
|
|
63
|
+
)
|
|
24
64
|
from .async_ext._message import send_template as async_send_template
|
|
25
|
-
from .async_ext._send_media import
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
65
|
+
from .async_ext._send_media import (
|
|
66
|
+
send_image as async_send_image,
|
|
67
|
+
send_video as async_send_video,
|
|
68
|
+
send_audio as async_send_audio,
|
|
69
|
+
send_location as async_send_location,
|
|
70
|
+
send_sticker as async_send_sticker,
|
|
71
|
+
send_document as async_send_document,
|
|
72
|
+
)
|
|
73
|
+
from .async_ext._media import (
|
|
74
|
+
upload_media as async_upload_media,
|
|
75
|
+
query_media_url as async_query_media_url,
|
|
76
|
+
download_media as async_download_media,
|
|
77
|
+
delete_media as async_delete_media,
|
|
78
|
+
)
|
|
79
|
+
from .async_ext._buttons import (
|
|
80
|
+
send_button as async_send_button,
|
|
81
|
+
create_button as async_create_button,
|
|
82
|
+
send_reply_button as async_send_reply_button,
|
|
83
|
+
send_cta_url as async_send_cta_url,
|
|
84
|
+
send_location_request as async_send_location_request,
|
|
85
|
+
)
|
|
86
|
+
from .async_ext._static import (
|
|
87
|
+
is_message as async_is_message,
|
|
88
|
+
get_mobile as async_get_mobile,
|
|
89
|
+
get_author as async_get_author,
|
|
90
|
+
get_name as async_get_name,
|
|
91
|
+
get_message as async_get_message,
|
|
92
|
+
get_message_id as async_get_message_id,
|
|
93
|
+
get_message_type as async_get_message_type,
|
|
94
|
+
get_message_timestamp as async_get_message_timestamp,
|
|
95
|
+
get_audio as async_get_audio,
|
|
96
|
+
get_delivery as async_get_delivery,
|
|
97
|
+
get_document as async_get_document,
|
|
98
|
+
get_image as async_get_image,
|
|
99
|
+
get_sticker as async_get_sticker,
|
|
100
|
+
get_interactive_response as async_get_interactive_response,
|
|
101
|
+
get_location as async_get_location,
|
|
102
|
+
get_video as async_get_video,
|
|
103
|
+
get_reaction as async_get_reaction,
|
|
104
|
+
changed_field as async_changed_field,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _check_package_update(current_version: str) -> None:
|
|
109
|
+
try:
|
|
110
|
+
latest = str(
|
|
111
|
+
requests.get(
|
|
112
|
+
"https://pypi.org/pypi/whatsapp-python/json", timeout=10
|
|
113
|
+
).json()["info"]["version"]
|
|
114
|
+
)
|
|
115
|
+
except Exception as exc: # noqa: BLE001
|
|
116
|
+
logging.debug("Skipped package update check: %s", exc)
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
if current_version == latest:
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
version_int = int(current_version.replace(".", ""))
|
|
124
|
+
except ValueError:
|
|
125
|
+
version_int = 0
|
|
126
|
+
try:
|
|
127
|
+
latest_int = int(latest.replace(".", ""))
|
|
128
|
+
except ValueError:
|
|
129
|
+
latest_int = 0
|
|
130
|
+
|
|
131
|
+
# Avoid false positives such as 1.0.10 vs 1.0.2 when using local builds.
|
|
132
|
+
if version_int >= latest_int:
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
if version_int == 0:
|
|
136
|
+
logging.critical(
|
|
137
|
+
"There was an error while checking for updates, please check for "
|
|
138
|
+
"updates manually. This may be due to the version being a "
|
|
139
|
+
"post-release version (e.g. 1.0.0.post1) or a pre-release version "
|
|
140
|
+
"(e.g. 1.0.0a1). READ THE CHANGELOG BEFORE UPDATING. NEW VERSIONS "
|
|
141
|
+
"MAY BREAK YOUR CODE IF NOT PROPERLY UPDATED."
|
|
142
|
+
)
|
|
143
|
+
else:
|
|
144
|
+
logging.critical(
|
|
145
|
+
"Whatsapp-python is out of date. Please update to the latest "
|
|
146
|
+
"version %s. READ THE CHANGELOG BEFORE UPDATING. NEW VERSIONS MAY "
|
|
147
|
+
"BREAK YOUR CODE IF NOT PROPERLY UPDATED.",
|
|
148
|
+
latest,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class WhatsApp:
|
|
153
|
+
def __init__(
|
|
154
|
+
self,
|
|
155
|
+
token: str = "",
|
|
156
|
+
phone_number_id: Union[dict, str] = "",
|
|
157
|
+
logger: bool = True,
|
|
158
|
+
update_check: bool = True,
|
|
159
|
+
verify_token: str = "",
|
|
160
|
+
debug: bool = True,
|
|
161
|
+
version: str = "latest",
|
|
162
|
+
):
|
|
33
163
|
"""
|
|
34
164
|
Initialize the WhatsApp Object
|
|
35
165
|
|
|
36
166
|
Args:
|
|
37
167
|
token[str]: Token for the WhatsApp cloud API obtained from the Facebook developer portal
|
|
38
|
-
phone_number_id[
|
|
168
|
+
phone_number_id[dict]: Mapping of friendly keys to phone number IDs from the developer portal
|
|
39
169
|
logger[bool]: Whether to enable logging or not (default: True)
|
|
170
|
+
update_check[bool]: Whether to check PyPI for package updates
|
|
171
|
+
verify_token[str]: Verification token used by the webhook challenge
|
|
172
|
+
debug[bool]: Whether to keep debug logs enabled
|
|
173
|
+
version[str]: Graph API version (``\"latest\"`` or e.g. ``\"v27.0\"``)
|
|
40
174
|
"""
|
|
41
175
|
|
|
42
|
-
# Check if the version is up to date
|
|
43
176
|
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
44
177
|
|
|
45
178
|
self.l = phone_number_id
|
|
@@ -47,70 +180,30 @@ class WhatsApp(object):
|
|
|
47
180
|
if isinstance(phone_number_id, dict):
|
|
48
181
|
# use first phone number id as default
|
|
49
182
|
phone_number_id = phone_number_id[list(phone_number_id.keys())[0]]
|
|
50
|
-
|
|
51
183
|
elif phone_number_id == "":
|
|
52
184
|
logging.error("Phone number ID not provided")
|
|
53
|
-
raise ValueError(
|
|
54
|
-
"Phone number ID not provided but required")
|
|
185
|
+
raise ValueError("Phone number ID not provided but required")
|
|
55
186
|
elif isinstance(phone_number_id, str):
|
|
56
187
|
logging.critical(
|
|
57
|
-
"The phone number ID should be a dictionary of phone numbers and their IDs. Using strings is deprecated."
|
|
188
|
+
"The phone number ID should be a dictionary of phone numbers and their IDs. Using strings is deprecated."
|
|
189
|
+
)
|
|
58
190
|
raise ValueError(
|
|
59
|
-
"Phone number ID should be a dictionary of phone numbers and their IDs"
|
|
60
|
-
|
|
61
|
-
pass
|
|
191
|
+
"Phone number ID should be a dictionary of phone numbers and their IDs"
|
|
192
|
+
)
|
|
62
193
|
|
|
63
|
-
self.VERSION = VERSION
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
# dynamically get the latest version of the API
|
|
67
|
-
if version == "latest":
|
|
68
|
-
soup = BeautifulSoup(r, features="html.parser")
|
|
69
|
-
t1 = soup.findAll("table")
|
|
70
|
-
|
|
71
|
-
def makeversion(table):
|
|
72
|
-
result = []
|
|
73
|
-
allrows = table.findAll('tr')
|
|
74
|
-
for row in allrows:
|
|
75
|
-
result.append([])
|
|
76
|
-
allcols = row.findAll('td')
|
|
77
|
-
for col in allcols:
|
|
78
|
-
thestrings = [(s) for s in col.findAll(text=True)]
|
|
79
|
-
thetext = ''.join(thestrings)
|
|
80
|
-
result[-1].append(thetext)
|
|
81
|
-
return result[0][1]
|
|
82
|
-
self.LATEST = makeversion(t1[0]) # latest version of the API
|
|
83
|
-
else:
|
|
84
|
-
self.LATEST = version
|
|
194
|
+
self.VERSION = VERSION
|
|
195
|
+
self.LATEST = resolve_graph_api_version(version)
|
|
85
196
|
|
|
86
197
|
if update_check is True:
|
|
87
|
-
|
|
88
|
-
"https://pypi.org/pypi/whatsapp-python/json").json()["info"]["version"])
|
|
89
|
-
if self.VERSION != latest:
|
|
90
|
-
try:
|
|
91
|
-
version_int = int(self.VERSION.replace(".", ""))
|
|
92
|
-
except:
|
|
93
|
-
version_int = 0
|
|
94
|
-
try:
|
|
95
|
-
latest_int = int(latest.replace(".", ""))
|
|
96
|
-
except:
|
|
97
|
-
latest_int = 0
|
|
98
|
-
# 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)
|
|
99
|
-
if version_int < latest_int:
|
|
100
|
-
if version_int == 0:
|
|
101
|
-
logging.critical(
|
|
102
|
-
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.")
|
|
103
|
-
else:
|
|
104
|
-
logging.critical(
|
|
105
|
-
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.")
|
|
198
|
+
_check_package_update(self.VERSION)
|
|
106
199
|
|
|
107
200
|
if token == "":
|
|
108
201
|
logging.error("Token not provided")
|
|
109
202
|
raise ValueError("Token not provided but required")
|
|
110
203
|
if phone_number_id == "":
|
|
111
204
|
logging.error("Phone number ID not provided")
|
|
112
|
-
raise ValueError(
|
|
113
|
-
|
|
205
|
+
raise ValueError("Phone number ID not provided but required")
|
|
206
|
+
|
|
114
207
|
self.token = token
|
|
115
208
|
self.phone_number_id = phone_number_id
|
|
116
209
|
self.base_url = f"https://graph.facebook.com/{self.LATEST}"
|
|
@@ -119,12 +212,13 @@ class WhatsApp(object):
|
|
|
119
212
|
|
|
120
213
|
async def base(*args):
|
|
121
214
|
pass
|
|
215
|
+
|
|
122
216
|
self.message_handler = base
|
|
123
217
|
self.other_handler = base
|
|
124
218
|
self.verification_handler = base
|
|
125
219
|
self.headers = {
|
|
126
220
|
"Content-Type": "application/json",
|
|
127
|
-
"Authorization": f"Bearer {self.token}"
|
|
221
|
+
"Authorization": f"Bearer {self.token}",
|
|
128
222
|
}
|
|
129
223
|
if logger is False:
|
|
130
224
|
logging.disable(logging.INFO)
|
|
@@ -157,24 +251,23 @@ class WhatsApp(object):
|
|
|
157
251
|
data = await r.json()
|
|
158
252
|
if data is None:
|
|
159
253
|
return {"success": False}
|
|
160
|
-
|
|
161
|
-
#
|
|
162
|
-
logging.debug(
|
|
254
|
+
# Avoid logging raw webhook payloads — they contain phone numbers
|
|
255
|
+
# and message content that CodeQL treats as sensitive clear-text data.
|
|
256
|
+
logging.debug("Received webhook payload")
|
|
163
257
|
|
|
164
|
-
|
|
165
|
-
if
|
|
258
|
+
changed = self.changed_field(data)
|
|
259
|
+
if changed == "messages":
|
|
166
260
|
new_message = self.is_message(data)
|
|
167
261
|
if new_message:
|
|
168
|
-
msg =
|
|
262
|
+
msg = self.create_message(data=data)
|
|
169
263
|
await self.message_handler(msg)
|
|
170
264
|
await self.other_handler(msg)
|
|
171
265
|
return {"success": True}
|
|
172
266
|
except Exception as e:
|
|
173
267
|
logging.error(f"Error parsing message: {e}")
|
|
174
|
-
raise HTTPException(
|
|
175
|
-
"success": False,
|
|
176
|
-
|
|
177
|
-
})
|
|
268
|
+
raise HTTPException(
|
|
269
|
+
status_code=500, detail={"success": False, "error": str(e)}
|
|
270
|
+
)
|
|
178
271
|
|
|
179
272
|
# all the files starting with _ are imported here, and should not be imported directly.
|
|
180
273
|
|
|
@@ -193,12 +286,15 @@ class WhatsApp(object):
|
|
|
193
286
|
get_interactive_response = staticmethod(get_interactive_response)
|
|
194
287
|
get_location = staticmethod(get_location)
|
|
195
288
|
get_video = staticmethod(get_video)
|
|
289
|
+
get_reaction = staticmethod(get_reaction)
|
|
196
290
|
changed_field = staticmethod(changed_field)
|
|
197
291
|
get_author = staticmethod(get_author)
|
|
198
292
|
|
|
199
293
|
send_button = send_button
|
|
200
294
|
create_button = create_button
|
|
201
295
|
send_reply_button = send_reply_button
|
|
296
|
+
send_cta_url = send_cta_url
|
|
297
|
+
send_location_request = send_location_request
|
|
202
298
|
send_image = send_image
|
|
203
299
|
send_video = send_video
|
|
204
300
|
send_audio = send_audio
|
|
@@ -214,8 +310,7 @@ class WhatsApp(object):
|
|
|
214
310
|
send_contacts = send_contacts
|
|
215
311
|
authorized = property(authorized)
|
|
216
312
|
|
|
217
|
-
|
|
218
|
-
def create_message(self, **kwargs) -> Message:
|
|
313
|
+
def create_message(self, **kwargs) -> "Message":
|
|
219
314
|
"""
|
|
220
315
|
Create a message object
|
|
221
316
|
|
|
@@ -227,7 +322,7 @@ class WhatsApp(object):
|
|
|
227
322
|
"""
|
|
228
323
|
return Message(**kwargs, instance=self)
|
|
229
324
|
|
|
230
|
-
def on_message(self, handler:
|
|
325
|
+
def on_message(self, handler: Callable[..., Any]):
|
|
231
326
|
"""
|
|
232
327
|
Set the handler for incoming messages
|
|
233
328
|
|
|
@@ -236,7 +331,7 @@ class WhatsApp(object):
|
|
|
236
331
|
"""
|
|
237
332
|
self.message_handler = handler
|
|
238
333
|
|
|
239
|
-
def on_event(self, handler:
|
|
334
|
+
def on_event(self, handler: Callable[..., Any]):
|
|
240
335
|
"""
|
|
241
336
|
Set the handler for other events
|
|
242
337
|
|
|
@@ -245,7 +340,7 @@ class WhatsApp(object):
|
|
|
245
340
|
"""
|
|
246
341
|
self.other_handler = handler
|
|
247
342
|
|
|
248
|
-
def on_verification(self, handler:
|
|
343
|
+
def on_verification(self, handler: Callable[..., Any]):
|
|
249
344
|
"""
|
|
250
345
|
Set the handler for verification
|
|
251
346
|
|
|
@@ -259,176 +354,32 @@ class WhatsApp(object):
|
|
|
259
354
|
|
|
260
355
|
|
|
261
356
|
class AsyncWhatsApp(WhatsApp):
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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)
|
|
357
|
+
"""Async variant of :class:`WhatsApp` that returns awaitable HTTP calls."""
|
|
358
|
+
|
|
359
|
+
is_message = staticmethod(async_is_message)
|
|
360
|
+
get_mobile = staticmethod(async_get_mobile)
|
|
361
|
+
get_name = staticmethod(async_get_name)
|
|
362
|
+
get_message = staticmethod(async_get_message)
|
|
363
|
+
get_message_id = staticmethod(async_get_message_id)
|
|
364
|
+
get_message_type = staticmethod(async_get_message_type)
|
|
365
|
+
get_message_timestamp = staticmethod(async_get_message_timestamp)
|
|
366
|
+
get_audio = staticmethod(async_get_audio)
|
|
367
|
+
get_delivery = staticmethod(async_get_delivery)
|
|
368
|
+
get_document = staticmethod(async_get_document)
|
|
369
|
+
get_image = staticmethod(async_get_image)
|
|
370
|
+
get_sticker = staticmethod(async_get_sticker)
|
|
371
|
+
get_interactive_response = staticmethod(async_get_interactive_response)
|
|
372
|
+
get_location = staticmethod(async_get_location)
|
|
373
|
+
get_video = staticmethod(async_get_video)
|
|
374
|
+
get_reaction = staticmethod(async_get_reaction)
|
|
375
|
+
changed_field = staticmethod(async_changed_field)
|
|
376
|
+
get_author = staticmethod(async_get_author)
|
|
428
377
|
|
|
429
378
|
send_button = async_send_button
|
|
430
379
|
create_button = async_create_button
|
|
431
380
|
send_reply_button = async_send_reply_button
|
|
381
|
+
send_cta_url = async_send_cta_url
|
|
382
|
+
send_location_request = async_send_location_request
|
|
432
383
|
send_image = async_send_image
|
|
433
384
|
send_video = async_send_video
|
|
434
385
|
send_audio = async_send_audio
|
|
@@ -443,12 +394,13 @@ class AsyncWhatsApp(WhatsApp):
|
|
|
443
394
|
send_custom_json = async_send_custom_json
|
|
444
395
|
send_contacts = async_send_contacts
|
|
445
396
|
authorized = property(async_authorized)
|
|
446
|
-
|
|
447
397
|
|
|
448
|
-
|
|
449
|
-
|
|
398
|
+
def handle(self, data: dict):
|
|
399
|
+
return Handle(data)
|
|
400
|
+
|
|
401
|
+
def create_message(self, **kwargs) -> "AsyncMessage":
|
|
450
402
|
"""
|
|
451
|
-
Create
|
|
403
|
+
Create an async message object
|
|
452
404
|
|
|
453
405
|
Args:
|
|
454
406
|
data[dict]: The message data
|
|
@@ -458,101 +410,93 @@ class AsyncWhatsApp(WhatsApp):
|
|
|
458
410
|
"""
|
|
459
411
|
return AsyncMessage(**kwargs, instance=self)
|
|
460
412
|
|
|
461
|
-
def on_message(self, handler: function):
|
|
462
|
-
"""
|
|
463
|
-
Set the handler for incoming messages
|
|
464
413
|
|
|
465
|
-
|
|
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
|
-
|
|
493
|
-
class Message(object):
|
|
414
|
+
class Message:
|
|
494
415
|
# type: ignore
|
|
495
|
-
def __init__(
|
|
416
|
+
def __init__(
|
|
417
|
+
self,
|
|
418
|
+
id: Optional[str] = None,
|
|
419
|
+
data: Optional[dict] = None,
|
|
420
|
+
instance: Optional[WhatsApp] = None,
|
|
421
|
+
content: str = "",
|
|
422
|
+
to: str = "",
|
|
423
|
+
rec_type: str = "individual",
|
|
424
|
+
):
|
|
425
|
+
data = data or {}
|
|
496
426
|
self.instance = instance
|
|
497
427
|
self.url = self.instance.url
|
|
498
428
|
self.headers = self.instance.headers
|
|
499
429
|
|
|
500
430
|
try:
|
|
501
431
|
self.id = instance.get_message_id(data)
|
|
502
|
-
except:
|
|
432
|
+
except Exception:
|
|
503
433
|
self.id = id
|
|
504
434
|
try:
|
|
505
435
|
self.type = self.instance.get_message_type(data)
|
|
506
|
-
except:
|
|
436
|
+
except Exception:
|
|
507
437
|
self.type = "text"
|
|
508
438
|
self.data = data
|
|
509
439
|
self.rec = rec_type
|
|
510
|
-
|
|
440
|
+
|
|
441
|
+
try:
|
|
442
|
+
self.to = to if to != "" else self.instance.get_mobile(data)
|
|
443
|
+
except Exception:
|
|
444
|
+
self.to = to
|
|
445
|
+
|
|
511
446
|
try:
|
|
512
|
-
self.
|
|
513
|
-
|
|
514
|
-
|
|
447
|
+
self.sender = self.instance.get_author(data) or self.to
|
|
448
|
+
except Exception:
|
|
449
|
+
self.sender = self.to
|
|
450
|
+
|
|
451
|
+
try:
|
|
452
|
+
self.content = content if content != "" else self.instance.get_message(data)
|
|
453
|
+
except Exception:
|
|
515
454
|
self.content = content
|
|
516
455
|
try:
|
|
517
456
|
self.name = self.instance.get_name(data)
|
|
518
|
-
except:
|
|
457
|
+
except Exception:
|
|
519
458
|
self.name = None
|
|
520
459
|
|
|
521
460
|
if self.type == "image":
|
|
522
461
|
try:
|
|
523
462
|
self.image = self.instance.get_image(data)
|
|
524
|
-
except:
|
|
463
|
+
except Exception:
|
|
525
464
|
self.image = None
|
|
526
465
|
if self.type == "sticker":
|
|
527
466
|
try:
|
|
528
467
|
self.sticker = self.instance.get_sticker(data)
|
|
529
|
-
except:
|
|
468
|
+
except Exception:
|
|
530
469
|
self.sticker = None
|
|
531
470
|
elif self.type == "video":
|
|
532
471
|
try:
|
|
533
472
|
self.video = self.instance.get_video(data)
|
|
534
|
-
except:
|
|
473
|
+
except Exception:
|
|
535
474
|
self.video = None
|
|
536
475
|
elif self.type == "audio":
|
|
537
476
|
try:
|
|
538
477
|
self.audio = self.instance.get_audio(data)
|
|
539
|
-
except:
|
|
478
|
+
except Exception:
|
|
540
479
|
pass
|
|
541
480
|
elif self.type == "document":
|
|
542
481
|
try:
|
|
543
482
|
self.document = self.instance.get_document(data)
|
|
544
|
-
except:
|
|
483
|
+
except Exception:
|
|
545
484
|
pass
|
|
546
485
|
elif self.type == "location":
|
|
547
486
|
try:
|
|
548
487
|
self.location = self.instance.get_location(data)
|
|
549
|
-
except:
|
|
488
|
+
except Exception:
|
|
550
489
|
pass
|
|
551
490
|
elif self.type == "interactive":
|
|
552
491
|
try:
|
|
553
492
|
self.interactive = self.instance.get_interactive_response(data)
|
|
554
|
-
except:
|
|
493
|
+
except Exception:
|
|
555
494
|
pass
|
|
495
|
+
elif self.type == "reaction":
|
|
496
|
+
try:
|
|
497
|
+
self.reaction = self.instance.get_reaction(data)
|
|
498
|
+
except Exception:
|
|
499
|
+
self.reaction = None
|
|
556
500
|
|
|
557
501
|
def reply(self, reply_text: str = "", preview_url: bool = True) -> dict:
|
|
558
502
|
if self.data == {}:
|
|
@@ -573,35 +517,47 @@ class Message(object):
|
|
|
573
517
|
return r.json()
|
|
574
518
|
logging.info(f"Message not sent to {self.instance.get_author(self.data)}")
|
|
575
519
|
logging.info(f"Status code: {r.status_code}")
|
|
576
|
-
logging.error(
|
|
520
|
+
logging.error("Request failed (status=%s)", r.status_code)
|
|
577
521
|
return r.json()
|
|
578
522
|
|
|
579
|
-
def mark_as_read(self) -> dict:
|
|
523
|
+
def mark_as_read(self, typing: bool = False) -> dict:
|
|
524
|
+
"""
|
|
525
|
+
Mark this inbound message as read.
|
|
580
526
|
|
|
581
|
-
|
|
527
|
+
Args:
|
|
528
|
+
typing: When True, also display a typing indicator (dismissed after
|
|
529
|
+
a reply or ~25 seconds). See Meta typing-indicators docs.
|
|
530
|
+
"""
|
|
531
|
+
payload: dict[str, Any] = {
|
|
582
532
|
"messaging_product": "whatsapp",
|
|
583
533
|
"status": "read",
|
|
584
534
|
"message_id": self.id,
|
|
585
535
|
}
|
|
536
|
+
if typing:
|
|
537
|
+
payload["typing_indicator"] = {"type": "text"}
|
|
586
538
|
|
|
587
539
|
response = requests.post(
|
|
588
|
-
f"{self.instance.url}", headers=self.instance.headers, json=payload
|
|
540
|
+
f"{self.instance.url}", headers=self.instance.headers, json=payload
|
|
541
|
+
)
|
|
589
542
|
if response.status_code == 200:
|
|
590
|
-
logging.info(response.
|
|
591
|
-
return response.json()
|
|
592
|
-
else:
|
|
593
|
-
logging.error(response.json())
|
|
543
|
+
logging.info("Request succeeded (status=%s)", response.status_code)
|
|
594
544
|
return response.json()
|
|
545
|
+
logging.error("Request failed (status=%s)", response.status_code)
|
|
546
|
+
return response.json()
|
|
547
|
+
|
|
548
|
+
def show_typing(self) -> dict:
|
|
549
|
+
"""Mark as read and show a typing indicator while preparing a reply."""
|
|
550
|
+
return self.mark_as_read(typing=True)
|
|
595
551
|
|
|
596
552
|
def send(self, sender=None, preview_url: bool = True) -> dict:
|
|
597
553
|
|
|
598
554
|
try:
|
|
599
555
|
sender = dict(self.instance.l)[sender]
|
|
600
556
|
|
|
601
|
-
except:
|
|
557
|
+
except Exception:
|
|
602
558
|
sender = self.instance.phone_number_id
|
|
603
559
|
|
|
604
|
-
if sender
|
|
560
|
+
if sender is None:
|
|
605
561
|
sender = self.instance.phone_number_id
|
|
606
562
|
|
|
607
563
|
url = f"https://graph.facebook.com/{self.instance.LATEST}/{sender}/messages"
|
|
@@ -619,7 +575,7 @@ class Message(object):
|
|
|
619
575
|
return r.json()
|
|
620
576
|
logging.info(f"Message not sent to {self.to}")
|
|
621
577
|
logging.info(f"Status code: {r.status_code}")
|
|
622
|
-
logging.error(
|
|
578
|
+
logging.error("Request failed (status=%s)", r.status_code)
|
|
623
579
|
return r.json()
|
|
624
580
|
|
|
625
581
|
def react(self, emoji: str) -> dict:
|
|
@@ -637,77 +593,97 @@ class Message(object):
|
|
|
637
593
|
return r.json()
|
|
638
594
|
logging.info(f"Reaction not sent to {self.to}")
|
|
639
595
|
logging.info(f"Status code: {r.status_code}")
|
|
640
|
-
logging.debug(
|
|
596
|
+
logging.debug("Request failed (status=%s)", r.status_code)
|
|
641
597
|
return r.json()
|
|
642
598
|
|
|
643
599
|
|
|
644
|
-
|
|
645
|
-
class AsyncMessage(object):
|
|
600
|
+
class AsyncMessage:
|
|
646
601
|
# type: ignore
|
|
647
|
-
def __init__(
|
|
602
|
+
def __init__(
|
|
603
|
+
self,
|
|
604
|
+
id: Optional[str] = None,
|
|
605
|
+
data: Optional[dict] = None,
|
|
606
|
+
instance: Optional[WhatsApp] = None,
|
|
607
|
+
content: str = "",
|
|
608
|
+
to: str = "",
|
|
609
|
+
rec_type: str = "individual",
|
|
610
|
+
):
|
|
611
|
+
data = data or {}
|
|
648
612
|
self.instance = instance
|
|
649
613
|
self.url = self.instance.url
|
|
650
614
|
self.headers = self.instance.headers
|
|
651
615
|
|
|
652
616
|
try:
|
|
653
617
|
self.id = instance.get_message_id(data)
|
|
654
|
-
except:
|
|
618
|
+
except Exception:
|
|
655
619
|
self.id = id
|
|
656
620
|
try:
|
|
657
621
|
self.type = self.instance.get_message_type(data)
|
|
658
|
-
except:
|
|
622
|
+
except Exception:
|
|
659
623
|
self.type = "text"
|
|
660
624
|
self.data = data
|
|
661
625
|
self.rec = rec_type
|
|
662
|
-
self.to = to
|
|
663
626
|
try:
|
|
664
|
-
self.
|
|
665
|
-
|
|
666
|
-
|
|
627
|
+
self.to = to if to != "" else self.instance.get_mobile(data)
|
|
628
|
+
except Exception:
|
|
629
|
+
self.to = to
|
|
630
|
+
try:
|
|
631
|
+
self.sender = self.instance.get_author(data) or self.to
|
|
632
|
+
except Exception:
|
|
633
|
+
self.sender = self.to
|
|
634
|
+
try:
|
|
635
|
+
self.content = content if content != "" else self.instance.get_message(data)
|
|
636
|
+
except Exception:
|
|
667
637
|
self.content = content
|
|
668
638
|
try:
|
|
669
639
|
self.name = self.instance.get_name(data)
|
|
670
|
-
except:
|
|
640
|
+
except Exception:
|
|
671
641
|
self.name = None
|
|
672
642
|
|
|
673
643
|
if self.type == "image":
|
|
674
644
|
try:
|
|
675
645
|
self.image = self.instance.get_image(data)
|
|
676
|
-
except:
|
|
646
|
+
except Exception:
|
|
677
647
|
self.image = None
|
|
678
648
|
if self.type == "sticker":
|
|
679
649
|
try:
|
|
680
650
|
self.sticker = self.instance.get_sticker(data)
|
|
681
|
-
except:
|
|
651
|
+
except Exception:
|
|
682
652
|
self.sticker = None
|
|
683
653
|
elif self.type == "video":
|
|
684
654
|
try:
|
|
685
655
|
self.video = self.instance.get_video(data)
|
|
686
|
-
except:
|
|
656
|
+
except Exception:
|
|
687
657
|
self.video = None
|
|
688
658
|
elif self.type == "audio":
|
|
689
659
|
try:
|
|
690
660
|
self.audio = self.instance.get_audio(data)
|
|
691
|
-
except:
|
|
661
|
+
except Exception:
|
|
692
662
|
pass
|
|
693
663
|
elif self.type == "document":
|
|
694
664
|
try:
|
|
695
665
|
self.document = self.instance.get_document(data)
|
|
696
|
-
except:
|
|
666
|
+
except Exception:
|
|
697
667
|
pass
|
|
698
668
|
elif self.type == "location":
|
|
699
669
|
try:
|
|
700
670
|
self.location = self.instance.get_location(data)
|
|
701
|
-
except:
|
|
671
|
+
except Exception:
|
|
702
672
|
pass
|
|
703
673
|
elif self.type == "interactive":
|
|
704
674
|
try:
|
|
705
675
|
self.interactive = self.instance.get_interactive_response(data)
|
|
706
|
-
except:
|
|
676
|
+
except Exception:
|
|
707
677
|
pass
|
|
708
|
-
|
|
678
|
+
elif self.type == "reaction":
|
|
679
|
+
try:
|
|
680
|
+
self.reaction = self.instance.get_reaction(data)
|
|
681
|
+
except Exception:
|
|
682
|
+
self.reaction = None
|
|
709
683
|
|
|
710
|
-
async def reply(
|
|
684
|
+
async def reply(
|
|
685
|
+
self, reply_text: str = "", preview_url: bool = True
|
|
686
|
+
) -> asyncio.Future:
|
|
711
687
|
if self.data == {}:
|
|
712
688
|
return {"error": "No data provided"}
|
|
713
689
|
author = self.instance.get_author(self.data)
|
|
@@ -720,49 +696,71 @@ class AsyncMessage(object):
|
|
|
720
696
|
"text": {"preview_url": preview_url, "body": reply_text},
|
|
721
697
|
}
|
|
722
698
|
logging.info(f"Replying to {self.id}")
|
|
699
|
+
|
|
723
700
|
async def call():
|
|
724
701
|
async with aiohttp.ClientSession() as session:
|
|
725
|
-
async with session.post(
|
|
702
|
+
async with session.post(
|
|
703
|
+
self.url, headers=self.headers, json=payload
|
|
704
|
+
) as response:
|
|
726
705
|
if response.status == 200:
|
|
727
|
-
logging.info(
|
|
706
|
+
logging.info(
|
|
707
|
+
f"Message sent to {self.instance.get_author(self.data)}"
|
|
708
|
+
)
|
|
728
709
|
return await response.json()
|
|
729
|
-
logging.info(
|
|
710
|
+
logging.info(
|
|
711
|
+
f"Message not sent to {self.instance.get_author(self.data)}"
|
|
712
|
+
)
|
|
730
713
|
logging.info(f"Status code: {response.status}")
|
|
731
|
-
logging.error(
|
|
714
|
+
logging.error("Request failed (status=%s)", response.status)
|
|
732
715
|
return await response.json()
|
|
716
|
+
|
|
733
717
|
f = asyncio.ensure_future(call())
|
|
734
|
-
await asyncio.sleep(.001)
|
|
718
|
+
await asyncio.sleep(0.001) # make asyncio run the task
|
|
735
719
|
return f
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
async def mark_as_read(self) -> asyncio.Future:
|
|
739
720
|
|
|
740
|
-
|
|
721
|
+
async def mark_as_read(self, typing: bool = False) -> asyncio.Future:
|
|
722
|
+
"""
|
|
723
|
+
Mark this inbound message as read.
|
|
724
|
+
|
|
725
|
+
Args:
|
|
726
|
+
typing: When True, also display a typing indicator (dismissed after
|
|
727
|
+
a reply or ~25 seconds). See Meta typing-indicators docs.
|
|
728
|
+
"""
|
|
729
|
+
payload: dict[str, Any] = {
|
|
741
730
|
"messaging_product": "whatsapp",
|
|
742
731
|
"status": "read",
|
|
743
732
|
"message_id": self.id,
|
|
744
733
|
}
|
|
734
|
+
if typing:
|
|
735
|
+
payload["typing_indicator"] = {"type": "text"}
|
|
736
|
+
|
|
745
737
|
async def call():
|
|
746
738
|
async with aiohttp.ClientSession() as session:
|
|
747
|
-
async with session.post(
|
|
739
|
+
async with session.post(
|
|
740
|
+
f"{self.instance.url}", headers=self.instance.headers, json=payload
|
|
741
|
+
) as response:
|
|
748
742
|
if response.status == 200:
|
|
749
|
-
logging.info(
|
|
750
|
-
return await response.json()
|
|
751
|
-
else:
|
|
752
|
-
logging.error(await response.json())
|
|
743
|
+
logging.info("Request succeeded (status=%s)", response.status)
|
|
753
744
|
return await response.json()
|
|
745
|
+
logging.error("Request failed (status=%s)", response.status)
|
|
746
|
+
return await response.json()
|
|
747
|
+
|
|
754
748
|
f = asyncio.ensure_future(call())
|
|
755
|
-
await asyncio.sleep(.001)
|
|
749
|
+
await asyncio.sleep(0.001) # make asyncio run the task
|
|
756
750
|
return f
|
|
757
|
-
|
|
751
|
+
|
|
752
|
+
async def show_typing(self) -> asyncio.Future:
|
|
753
|
+
"""Mark as read and show a typing indicator while preparing a reply."""
|
|
754
|
+
return await self.mark_as_read(typing=True)
|
|
755
|
+
|
|
758
756
|
async def send(self, sender=None, preview_url: bool = True) -> asyncio.Future:
|
|
759
757
|
try:
|
|
760
758
|
sender = dict(self.instance.l)[sender]
|
|
761
759
|
|
|
762
|
-
except:
|
|
760
|
+
except Exception:
|
|
763
761
|
sender = self.instance.phone_number_id
|
|
764
762
|
|
|
765
|
-
if sender
|
|
763
|
+
if sender is None:
|
|
766
764
|
sender = self.instance.phone_number_id
|
|
767
765
|
|
|
768
766
|
url = f"https://graph.facebook.com/{self.instance.LATEST}/{sender}/messages"
|
|
@@ -774,21 +772,24 @@ class AsyncMessage(object):
|
|
|
774
772
|
"text": {"preview_url": preview_url, "body": self.content},
|
|
775
773
|
}
|
|
776
774
|
logging.info(f"Sending message to {self.to}")
|
|
775
|
+
|
|
777
776
|
async def call():
|
|
778
|
-
print("sending")
|
|
779
777
|
async with aiohttp.ClientSession() as session:
|
|
780
|
-
async with session.post(
|
|
778
|
+
async with session.post(
|
|
779
|
+
url, headers=self.headers, json=data
|
|
780
|
+
) as response:
|
|
781
781
|
if response.status == 200:
|
|
782
782
|
logging.info(f"Message sent to {self.to}")
|
|
783
783
|
return await response.json()
|
|
784
784
|
logging.info(f"Message not sent to {self.to}")
|
|
785
785
|
logging.info(f"Status code: {response.status}")
|
|
786
|
-
logging.error(
|
|
786
|
+
logging.error("Request failed (status=%s)", response.status)
|
|
787
787
|
return await response.json()
|
|
788
|
+
|
|
788
789
|
f = asyncio.ensure_future(call())
|
|
789
|
-
await asyncio.sleep(.001)
|
|
790
|
+
await asyncio.sleep(0.001) # make asyncio run the task
|
|
790
791
|
return f
|
|
791
|
-
|
|
792
|
+
|
|
792
793
|
async def react(self, emoji: str) -> asyncio.Future:
|
|
793
794
|
data = {
|
|
794
795
|
"messaging_product": "whatsapp",
|
|
@@ -798,16 +799,20 @@ class AsyncMessage(object):
|
|
|
798
799
|
"reaction": {"message_id": self.id, "emoji": emoji},
|
|
799
800
|
}
|
|
800
801
|
logging.info(f"Reacting to {self.id}")
|
|
802
|
+
|
|
801
803
|
async def call():
|
|
802
804
|
async with aiohttp.ClientSession() as session:
|
|
803
|
-
async with session.post(
|
|
805
|
+
async with session.post(
|
|
806
|
+
self.url, headers=self.headers, json=data
|
|
807
|
+
) as response:
|
|
804
808
|
if response.status == 200:
|
|
805
809
|
logging.info(f"Reaction sent to {self.to}")
|
|
806
810
|
return await response.json()
|
|
807
811
|
logging.info(f"Reaction not sent to {self.to}")
|
|
808
812
|
logging.info(f"Status code: {response.status}")
|
|
809
|
-
logging.debug(
|
|
813
|
+
logging.debug("Request failed (status=%s)", response.status)
|
|
810
814
|
return await response.json()
|
|
815
|
+
|
|
811
816
|
f = asyncio.ensure_future(call())
|
|
812
|
-
await asyncio.sleep(.001)
|
|
817
|
+
await asyncio.sleep(0.001)
|
|
813
818
|
return f
|