whatsapp-python 3.1.5__py3-none-any.whl → 3.3.5.dev0__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 CHANGED
@@ -1,275 +1,386 @@
1
- """
2
- Unofficial Python wrapper for the WhatsApp Cloud API.
3
- """
4
- from __future__ import annotations
5
-
6
- import requests
7
- import logging
8
- from fastapi import FastAPI, HTTPException, Request
9
- from uvicorn import run as _run
10
- from .constants import VERSION
11
- from .ext._property import authorized
12
- from .ext._send_others import send_custom_json, send_contacts
13
- from .ext._message import send_template
14
- from .ext._send_media import send_image, send_video, send_audio, send_location, send_sticker, send_document
15
- from .ext._media import upload_media, query_media_url, download_media, delete_media
16
- from .ext._buttons import send_button, create_button, send_reply_button
17
- 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
- import json
19
-
20
-
21
- class WhatsApp(object):
22
- def __init__(self, token: str = "", phone_number_id: str = "", logger: bool = True, update_check: bool = True, verify_token: str = "", debug: bool = True):
23
- """
24
- Initialize the WhatsApp Object
25
-
26
- Args:
27
- token[str]: Token for the WhatsApp cloud API obtained from the Facebook developer portal
28
- phone_number_id[str]: Phone number id for the WhatsApp cloud API obtained from the developer portal
29
- logger[bool]: Whether to enable logging or not (default: True)
30
- """
31
-
32
- # Check if the version is up to date
33
- logging.getLogger(__name__).addHandler(logging.NullHandler())
34
-
35
- self.VERSION = VERSION
36
- if update_check is True:
37
- latest = str(requests.get(
38
- "https://pypi.org/pypi/whatsapp-python/json").json()["info"]["version"])
39
- if self.VERSION != latest:
40
- try:
41
- version_int = int(self.VERSION.replace(".", ""))
42
- except:
43
- version_int = 0
44
- latest_int = int(latest.replace(".", ""))
45
- # 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)
46
- if version_int < latest_int:
47
- if version_int == 0:
48
- logging.critical(
49
- f"There was an error while checking for updates, please update the package manually or report the issue on GitHub.")
50
- else:
51
- logging.critical(
52
- 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.")
53
-
54
- if token == "":
55
- logging.error("Token not provided")
56
- raise ValueError("Token not provided but required")
57
- if phone_number_id == "":
58
- logging.error("Phone number ID not provided")
59
- raise ValueError(
60
- "Phone number ID not provided but required")
61
- self.token = token
62
- self.phone_number_id = phone_number_id
63
- self.base_url = "https://graph.facebook.com/v18.0"
64
- self.url = f"{self.base_url}/{phone_number_id}/messages"
65
- self.verify_token = verify_token
66
-
67
- async def base(*args):
68
- pass
69
- self.message_handler = base
70
- self.other_handler = base
71
- self.verification_handler = base
72
- self.headers = {
73
- "Content-Type": "application/json",
74
- "Authorization": f"Bearer {self.token}"
75
- }
76
- if logger is False:
77
- logging.disable(logging.INFO)
78
- logging.disable(logging.ERROR)
79
- if debug is False:
80
- logging.disable(logging.DEBUG)
81
- logging.disable(logging.ERROR)
82
-
83
- self.app = FastAPI()
84
-
85
- # Verification handler has 1 argument: challenge (str | bool): str if verification is successful, False if not
86
-
87
- @self.app.get("/")
88
- async def verify_endpoint(r: Request):
89
- if r.query_params.get("hub.verify_token") == self.verify_token:
90
- logging.debug("Webhook verified successfully")
91
- challenge = r.query_params.get("hub.challenge")
92
- self.verification_handler(challenge)
93
- self.other_handler(challenge)
94
- return int(challenge)
95
- logging.error("Webhook Verification failed - token mismatch")
96
- await self.verification_handler(False)
97
- await self.other_handler(False)
98
- return {"success": False}
99
-
100
- @self.app.post("/")
101
- async def hook(r: Request):
102
- try:
103
- # Handle Webhook Subscriptions
104
- data = await r.json()
105
- if data is None:
106
- return {"success": False}
107
- data_str = json.dumps(data, indent=4)
108
- # log the data received only if the log level is debug
109
- logging.debug(f"Received webhook data: {data_str}")
110
-
111
- changed_field = self.changed_field(data)
112
- if changed_field == "messages":
113
- new_message = self.is_message(data)
114
- if new_message:
115
- msg = Message(instance=self, data=data)
116
- await self.message_handler(msg)
117
- await self.other_handler(msg)
118
- return {"success": True}
119
- except Exception as e:
120
- logging.error(f"Error parsing message: {e}")
121
- raise HTTPException(status_code=500, detail={
122
- "success": False,
123
- "error": str(e)
124
- })
125
-
126
- # all the files starting with _ are imported here, and should not be imported directly.
127
-
128
- is_message = staticmethod(is_message)
129
- get_mobile = staticmethod(get_mobile)
130
- get_name = staticmethod(get_name)
131
- get_message = staticmethod(get_message)
132
- get_message_id = staticmethod(get_message_id)
133
- get_message_type = staticmethod(get_message_type)
134
- get_message_timestamp = staticmethod(get_message_timestamp)
135
- get_audio = staticmethod(get_audio)
136
- get_delivery = staticmethod(get_delivery)
137
- get_document = staticmethod(get_document)
138
- get_image = staticmethod(get_image)
139
- get_sticker = staticmethod(get_sticker)
140
- get_interactive_response = staticmethod(get_interactive_response)
141
- get_location = staticmethod(get_location)
142
- get_video = staticmethod(get_video)
143
- changed_field = staticmethod(changed_field)
144
- get_author = staticmethod(get_author)
145
-
146
- send_button = send_button
147
- create_button = create_button
148
- send_reply_button = send_reply_button
149
- send_image = send_image
150
- send_video = send_video
151
- send_audio = send_audio
152
- send_location = send_location
153
- send_sticker = send_sticker
154
- send_document = send_document
155
- upload_media = upload_media
156
- query_media_url = query_media_url
157
- download_media = download_media
158
- delete_media = delete_media
159
- send_template = send_template
160
- send_custom_json = send_custom_json
161
- send_contacts = send_contacts
162
-
163
- authorized = property(authorized)
164
-
165
- def create_message(self, **kwargs) -> Message:
166
- """
167
- Create a message object
168
-
169
- Args:
170
- data[dict]: The message data
171
- content[str]: The message content
172
- to[str]: The recipient
173
- rec_type[str]: The recipient type (individual/group)
174
- """
175
- return Message(**kwargs, instance=self)
176
-
177
- def on_message(self, handler: function):
178
- """
179
- Set the handler for incoming messages
180
-
181
- Args:
182
- handler[function]: The handler function
183
- """
184
- self.message_handler = handler
185
-
186
- def on_event(self, handler: function):
187
- """
188
- Set the handler for other events
189
-
190
- Args:
191
- handler[function]: The handler function
192
- """
193
- self.other_handler = handler
194
-
195
- def on_verification(self, handler: function):
196
- """
197
- Set the handler for verification
198
-
199
- Args:
200
- handler[function]: The handler function
201
- """
202
- self.verification_handler = handler
203
-
204
- def run(self, host: str = "localhost", port: int = 5000, **options):
205
- _run(self.app, host=host, port=port, **options)
206
-
207
-
208
- class Message(object):
209
- def __init__(self, id: int = None, data: dict = {}, instance: WhatsApp = None, content: str = "", to: str = "", rec_type: str = "individual"): # type: ignore
210
- self.instance = instance
211
- self.url = self.instance.url
212
- self.headers = self.instance.headers
213
-
214
- try:
215
- self.id = instance.get_message_id(data)
216
- except:
217
- self.id = id
218
- try:
219
- self.type = self.instance.get_message_type(data)
220
- except:
221
- self.type = "text"
222
- self.data = data
223
- self.rec = rec_type
224
- self.to = to
225
- try:
226
- self.content = content if content != "" else self.instance.get_message(
227
- data)
228
- except:
229
- self.content = content
230
- try:
231
- self.sender = self.instance.get_mobile(data)
232
- except:
233
- self.sender = None
234
- try:
235
- self.name = self.instance.get_name(data)
236
- except:
237
- self.name = None
238
-
239
- if self.type == "image":
240
- try:
241
- self.image = self.instance.get_image(data)
242
- except:
243
- self.image = None
244
- if self.type == "sticker":
245
- try:
246
- self.sticker = self.instance.get_sticker(data)
247
- except:
248
- self.sticker = None
249
- elif self.type == "video":
250
- try:
251
- self.video = self.instance.get_video(data)
252
- except:
253
- self.video = None
254
- elif self.type == "audio":
255
- try:
256
- self.audio = self.instance.get_audio(data)
257
- except:
258
- pass
259
- elif self.type == "document":
260
- try:
261
- self.document = self.instance.get_document(data)
262
- except:
263
- pass
264
- elif self.type == "location":
265
- try:
266
- self.location = self.instance.get_location(data)
267
- except:
268
- pass
269
- elif self.type == "interactive":
270
- try:
271
- self.interactive = self.instance.get_interactive_response(data)
272
- except:
273
- pass
274
-
275
- from .ext._message import send, reply, mark_as_read
1
+ """
2
+ Unofficial Python wrapper for the WhatsApp Cloud API.
3
+ """
4
+ from __future__ import annotations
5
+
6
+ import requests
7
+ import logging
8
+ from typing import Union
9
+ from fastapi import FastAPI, HTTPException, Request
10
+ from uvicorn import run as _run
11
+ from .constants import VERSION
12
+ from .ext._property import authorized
13
+ from .ext._send_others import send_custom_json, send_contacts
14
+ from .ext._message import send_template
15
+ from .ext._send_media import send_image, send_video, send_audio, send_location, send_sticker, send_document
16
+ from .ext._media import upload_media, query_media_url, download_media, delete_media
17
+ from .ext._buttons import send_button, create_button, send_reply_button
18
+ 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
19
+ import json
20
+
21
+
22
+ class WhatsApp(object):
23
+ def __init__(self, token: str = "", phone_number_id: Union[str, dict] = "", logger: bool = True, update_check: bool = True, verify_token: str = "", debug: bool = True):
24
+ """
25
+ Initialize the WhatsApp Object
26
+
27
+ Args:
28
+ token[str]: Token for the WhatsApp cloud API obtained from the Facebook developer portal
29
+ phone_number_id[str]: Phone number id for the WhatsApp cloud API obtained from the developer portal
30
+ logger[bool]: Whether to enable logging or not (default: True)
31
+ """
32
+
33
+ # Check if the version is up to date
34
+ logging.getLogger(__name__).addHandler(logging.NullHandler())
35
+
36
+ self.l = phone_number_id
37
+ print(self.l)
38
+
39
+ if isinstance(phone_number_id, dict):
40
+ # use first phone number id as default
41
+ phone_number_id = phone_number_id[list(phone_number_id.keys())[0]]
42
+ print(phone_number_id)
43
+
44
+ elif phone_number_id == "":
45
+ logging.error("Phone number ID not provided")
46
+ raise ValueError(
47
+ "Phone number ID not provided but required")
48
+ elif isinstance(phone_number_id, str):
49
+ logging.critical(
50
+ "The phone number ID should be a dictionary of phone numbers and their IDs. Using strings is deprecated and will be removed in the next version.")
51
+ print(phone_number_id)
52
+ else:
53
+ pass
54
+
55
+ self.VERSION = VERSION
56
+ if update_check is True:
57
+ latest = str(requests.get(
58
+ "https://pypi.org/pypi/whatsapp-python/json").json()["info"]["version"])
59
+ if self.VERSION != latest:
60
+ try:
61
+ version_int = int(self.VERSION.replace(".", ""))
62
+ except:
63
+ version_int = 0
64
+ latest_int = int(latest.replace(".", ""))
65
+ # 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)
66
+ if version_int < latest_int:
67
+ if version_int == 0:
68
+ logging.critical(
69
+ f"There was an error while checking for updates, please update the package manually or report the issue on GitHub.")
70
+ else:
71
+ logging.critical(
72
+ 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.")
73
+
74
+ if token == "":
75
+ logging.error("Token not provided")
76
+ raise ValueError("Token not provided but required")
77
+ if phone_number_id == "":
78
+ logging.error("Phone number ID not provided")
79
+ raise ValueError(
80
+ "Phone number ID not provided but required")
81
+ self.token = token
82
+ self.phone_number_id = phone_number_id
83
+ self.base_url = "https://graph.facebook.com/v18.0"
84
+ self.url = f"{self.base_url}/{phone_number_id}/messages"
85
+ self.verify_token = verify_token
86
+
87
+ async def base(*args):
88
+ pass
89
+ self.message_handler = base
90
+ self.other_handler = base
91
+ self.verification_handler = base
92
+ self.headers = {
93
+ "Content-Type": "application/json",
94
+ "Authorization": f"Bearer {self.token}"
95
+ }
96
+ if logger is False:
97
+ logging.disable(logging.INFO)
98
+ logging.disable(logging.ERROR)
99
+ if debug is False:
100
+ logging.disable(logging.DEBUG)
101
+ logging.disable(logging.ERROR)
102
+
103
+ self.app = FastAPI()
104
+
105
+ # Verification handler has 1 argument: challenge (str | bool): str if verification is successful, False if not
106
+
107
+ @self.app.get("/")
108
+ async def verify_endpoint(r: Request):
109
+ if r.query_params.get("hub.verify_token") == self.verify_token:
110
+ logging.debug("Webhook verified successfully")
111
+ challenge = r.query_params.get("hub.challenge")
112
+ self.verification_handler(challenge)
113
+ self.other_handler(challenge)
114
+ return int(challenge)
115
+ logging.error("Webhook Verification failed - token mismatch")
116
+ await self.verification_handler(False)
117
+ await self.other_handler(False)
118
+ return {"success": False}
119
+
120
+ @self.app.post("/")
121
+ async def hook(r: Request):
122
+ try:
123
+ # Handle Webhook Subscriptions
124
+ data = await r.json()
125
+ if data is None:
126
+ return {"success": False}
127
+ data_str = json.dumps(data, indent=4)
128
+ # log the data received only if the log level is debug
129
+ logging.debug(f"Received webhook data: {data_str}")
130
+
131
+ changed_field = self.changed_field(data)
132
+ if changed_field == "messages":
133
+ new_message = self.is_message(data)
134
+ if new_message:
135
+ msg = Message(instance=self, data=data)
136
+ await self.message_handler(msg)
137
+ await self.other_handler(msg)
138
+ return {"success": True}
139
+ except Exception as e:
140
+ logging.error(f"Error parsing message: {e}")
141
+ raise HTTPException(status_code=500, detail={
142
+ "success": False,
143
+ "error": str(e)
144
+ })
145
+
146
+ # all the files starting with _ are imported here, and should not be imported directly.
147
+
148
+ is_message = staticmethod(is_message)
149
+ get_mobile = staticmethod(get_mobile)
150
+ get_name = staticmethod(get_name)
151
+ get_message = staticmethod(get_message)
152
+ get_message_id = staticmethod(get_message_id)
153
+ get_message_type = staticmethod(get_message_type)
154
+ get_message_timestamp = staticmethod(get_message_timestamp)
155
+ get_audio = staticmethod(get_audio)
156
+ get_delivery = staticmethod(get_delivery)
157
+ get_document = staticmethod(get_document)
158
+ get_image = staticmethod(get_image)
159
+ get_sticker = staticmethod(get_sticker)
160
+ get_interactive_response = staticmethod(get_interactive_response)
161
+ get_location = staticmethod(get_location)
162
+ get_video = staticmethod(get_video)
163
+ changed_field = staticmethod(changed_field)
164
+ get_author = staticmethod(get_author)
165
+
166
+ send_button = send_button
167
+ create_button = create_button
168
+ send_reply_button = send_reply_button
169
+ send_image = send_image
170
+ send_video = send_video
171
+ send_audio = send_audio
172
+ send_location = send_location
173
+ send_sticker = send_sticker
174
+ send_document = send_document
175
+ upload_media = upload_media
176
+ query_media_url = query_media_url
177
+ download_media = download_media
178
+ delete_media = delete_media
179
+ send_template = send_template
180
+ send_custom_json = send_custom_json
181
+ send_contacts = send_contacts
182
+
183
+ authorized = property(authorized)
184
+
185
+ def create_message(self, **kwargs) -> Message:
186
+ """
187
+ Create a message object
188
+
189
+ Args:
190
+ data[dict]: The message data
191
+ content[str]: The message content
192
+ to[str]: The recipient
193
+ rec_type[str]: The recipient type (individual/group)
194
+ """
195
+ return Message(**kwargs, instance=self)
196
+
197
+ def on_message(self, handler: function):
198
+ """
199
+ Set the handler for incoming messages
200
+
201
+ Args:
202
+ handler[function]: The handler function
203
+ """
204
+ self.message_handler = handler
205
+
206
+ def on_event(self, handler: function):
207
+ """
208
+ Set the handler for other events
209
+
210
+ Args:
211
+ handler[function]: The handler function
212
+ """
213
+ self.other_handler = handler
214
+
215
+ def on_verification(self, handler: function):
216
+ """
217
+ Set the handler for verification
218
+
219
+ Args:
220
+ handler[function]: The handler function
221
+ """
222
+ self.verification_handler = handler
223
+
224
+ def run(self, host: str = "localhost", port: int = 5000, **options):
225
+ _run(self.app, host=host, port=port, **options)
226
+
227
+
228
+ class Message(object):
229
+ # type: ignore
230
+ def __init__(self, id: int = None, data: dict = {}, instance: WhatsApp = None, content: str = "", to: str = "", rec_type: str = "individual"):
231
+ self.instance = instance
232
+ self.url = self.instance.url
233
+ self.headers = self.instance.headers
234
+
235
+
236
+ try:
237
+ self.id = instance.get_message_id(data)
238
+ except:
239
+ self.id = id
240
+ try:
241
+ self.type = self.instance.get_message_type(data)
242
+ except:
243
+ self.type = "text"
244
+ self.data = data
245
+ self.rec = rec_type
246
+ self.to = to
247
+ try:
248
+ self.content = content if content != "" else self.instance.get_message(
249
+ data)
250
+ except:
251
+ self.content = content
252
+ try:
253
+ self.name = self.instance.get_name(data)
254
+ except:
255
+ self.name = None
256
+
257
+ if self.type == "image":
258
+ try:
259
+ self.image = self.instance.get_image(data)
260
+ except:
261
+ self.image = None
262
+ if self.type == "sticker":
263
+ try:
264
+ self.sticker = self.instance.get_sticker(data)
265
+ except:
266
+ self.sticker = None
267
+ elif self.type == "video":
268
+ try:
269
+ self.video = self.instance.get_video(data)
270
+ except:
271
+ self.video = None
272
+ elif self.type == "audio":
273
+ try:
274
+ self.audio = self.instance.get_audio(data)
275
+ except:
276
+ pass
277
+ elif self.type == "document":
278
+ try:
279
+ self.document = self.instance.get_document(data)
280
+ except:
281
+ pass
282
+ elif self.type == "location":
283
+ try:
284
+ self.location = self.instance.get_location(data)
285
+ except:
286
+ pass
287
+ elif self.type == "interactive":
288
+ try:
289
+ self.interactive = self.instance.get_interactive_response(data)
290
+ except:
291
+ pass
292
+
293
+ def reply(self, reply_text: str = "", preview_url: bool = True) -> dict:
294
+ if self.data == {}:
295
+ return {"error": "No data provided"}
296
+ author = self.instance.get_author(self.data)
297
+ payload = {
298
+ "messaging_product": "whatsapp",
299
+ "recipient_type": "individual",
300
+ "to": str(author),
301
+ "type": "text",
302
+ "context": {"message_id": self.id},
303
+ "text": {"preview_url": preview_url, "body": reply_text},
304
+ }
305
+ logging.info(f"Replying to {self.id}")
306
+ r = requests.post(self.url, headers=self.headers, json=payload)
307
+ if r.status_code == 200:
308
+ logging.info(f"Message sent to {
309
+ self.instance.get_author(self.data)}")
310
+ return r.json()
311
+ logging.info(f"Message not sent to {
312
+ self.instance.get_author(self.data)}")
313
+ logging.info(f"Status code: {r.status_code}")
314
+ logging.error(f"Response: {r.json()}")
315
+ return r.json()
316
+
317
+ def mark_as_read(self) -> dict:
318
+
319
+ payload = {
320
+ "messaging_product": "whatsapp",
321
+ "status": "read",
322
+ "message_id": self.id,
323
+ }
324
+
325
+ response = requests.post(
326
+ f"{self.instance.url}", headers=self.instance.headers, json=payload)
327
+ if response.status_code == 200:
328
+ logging.info(response.json())
329
+ return response.json()
330
+ else:
331
+ logging.error(response.json())
332
+ return response.json()
333
+
334
+ def send(self,sender = None, preview_url: bool = True) -> dict:
335
+
336
+ try:
337
+ sender = dict(self.instance.l)[sender]
338
+ print(self.instance.l)
339
+ print(sender)
340
+
341
+
342
+ print(12)
343
+
344
+ except:
345
+ print(22)
346
+ sender = self.instance.phone_number_id
347
+
348
+ if sender == None:
349
+ sender = self.instance.phone_number_id
350
+
351
+ url = f"https://graph.facebook.com/v18.0/{sender}/messages"
352
+ data = {
353
+ "messaging_product": "whatsapp",
354
+ "recipient_type": self.rec,
355
+ "to": self.to,
356
+ "type": "text",
357
+ "text": {"preview_url": preview_url, "body": self.content},
358
+ }
359
+ logging.info(f"Sending message to {self.to}")
360
+ print(url)
361
+ r = requests.post(url, headers=self.headers, json=data)
362
+ if r.status_code == 200:
363
+ logging.info(f"Message sent to {self.to}")
364
+ return r.json()
365
+ logging.info(f"Message not sent to {self.to}")
366
+ logging.info(f"Status code: {r.status_code}")
367
+ logging.error(f"Response: {r.json()}")
368
+ return r.json()
369
+
370
+ def react(self, emoji: str) -> dict:
371
+ data = {
372
+ "messaging_product": "whatsapp",
373
+ "recipient_type": "individual",
374
+ "to": self.to,
375
+ "type": "reaction",
376
+ "reaction": {"message_id": self.id, "emoji": emoji},
377
+ }
378
+ logging.info(f"Reacting to {self.id}")
379
+ r = requests.post(self.url, headers=self.headers, json=data)
380
+ if r.status_code == 200:
381
+ logging.info(f"Reaction sent to {self.to}")
382
+ return r.json()
383
+ logging.info(f"Reaction not sent to {self.to}")
384
+ logging.info(f"Status code: {r.status_code}")
385
+ logging.debug(f"Response: {r.json()}")
386
+ return r.json()