odoo-addon-mail-gateway-whatsapp 16.0.1.0.0.2__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.
Files changed (37) hide show
  1. odoo/addons/mail_gateway_whatsapp/README.rst +127 -0
  2. odoo/addons/mail_gateway_whatsapp/__init__.py +4 -0
  3. odoo/addons/mail_gateway_whatsapp/__manifest__.py +28 -0
  4. odoo/addons/mail_gateway_whatsapp/i18n/mail_gateway_whatsapp.pot +537 -0
  5. odoo/addons/mail_gateway_whatsapp/models/__init__.py +5 -0
  6. odoo/addons/mail_gateway_whatsapp/models/mail_channel.py +25 -0
  7. odoo/addons/mail_gateway_whatsapp/models/mail_gateway.py +15 -0
  8. odoo/addons/mail_gateway_whatsapp/models/mail_gateway_whatsapp.py +383 -0
  9. odoo/addons/mail_gateway_whatsapp/models/mail_thread.py +65 -0
  10. odoo/addons/mail_gateway_whatsapp/models/res_partner.py +20 -0
  11. odoo/addons/mail_gateway_whatsapp/readme/CONFIGURE.rst +30 -0
  12. odoo/addons/mail_gateway_whatsapp/readme/CONTRIBUTORS.rst +2 -0
  13. odoo/addons/mail_gateway_whatsapp/readme/CREDITS.rst +1 -0
  14. odoo/addons/mail_gateway_whatsapp/readme/DESCRIPTION.rst +4 -0
  15. odoo/addons/mail_gateway_whatsapp/readme/USAGE.rst +3 -0
  16. odoo/addons/mail_gateway_whatsapp/security/ir.model.access.csv +2 -0
  17. odoo/addons/mail_gateway_whatsapp/static/description/icon.png +0 -0
  18. odoo/addons/mail_gateway_whatsapp/static/description/icon.svg +48 -0
  19. odoo/addons/mail_gateway_whatsapp/static/description/index.html +478 -0
  20. odoo/addons/mail_gateway_whatsapp/static/src/components/message/message.xml +11 -0
  21. odoo/addons/mail_gateway_whatsapp/static/src/components/phone_field/phone_field.esm.js +25 -0
  22. odoo/addons/mail_gateway_whatsapp/static/src/components/phone_field/phone_field.xml +20 -0
  23. odoo/addons/mail_gateway_whatsapp/static/src/components/send_whatsapp_button/send_whatsapp_button.esm.js +44 -0
  24. odoo/addons/mail_gateway_whatsapp/static/src/components/send_whatsapp_button/send_whatsapp_button.xml +13 -0
  25. odoo/addons/mail_gateway_whatsapp/static/src/models/message.esm.js +21 -0
  26. odoo/addons/mail_gateway_whatsapp/static/src/models/message_view.esm.js +25 -0
  27. odoo/addons/mail_gateway_whatsapp/static/src/models/notification.esm.js +25 -0
  28. odoo/addons/mail_gateway_whatsapp/tests/__init__.py +1 -0
  29. odoo/addons/mail_gateway_whatsapp/tests/test_mail_gateway_whatsapp.py +308 -0
  30. odoo/addons/mail_gateway_whatsapp/views/mail_gateway.xml +103 -0
  31. odoo/addons/mail_gateway_whatsapp/wizards/__init__.py +1 -0
  32. odoo/addons/mail_gateway_whatsapp/wizards/whatsapp_composer.py +59 -0
  33. odoo/addons/mail_gateway_whatsapp/wizards/whatsapp_composer.xml +50 -0
  34. odoo_addon_mail_gateway_whatsapp-16.0.1.0.0.2.dist-info/METADATA +147 -0
  35. odoo_addon_mail_gateway_whatsapp-16.0.1.0.0.2.dist-info/RECORD +37 -0
  36. odoo_addon_mail_gateway_whatsapp-16.0.1.0.0.2.dist-info/WHEEL +5 -0
  37. odoo_addon_mail_gateway_whatsapp-16.0.1.0.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,5 @@
1
+ from . import mail_gateway
2
+ from . import mail_thread
3
+ from . import mail_gateway_whatsapp
4
+ from . import mail_channel
5
+ from . import res_partner
@@ -0,0 +1,25 @@
1
+ # Copyright 2024 Dixmit
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3
+
4
+ from odoo import models
5
+ from odoo.modules.module import get_resource_path
6
+
7
+ from odoo.addons.base.models.avatar_mixin import get_hsl_from_seed
8
+
9
+
10
+ class MailChannel(models.Model):
11
+
12
+ _inherit = "mail.channel"
13
+
14
+ def _generate_avatar_gateway(self):
15
+ if self.gateway_id.gateway_type == "whatsapp":
16
+ path = get_resource_path(
17
+ "mail_gateway_whatsapp", "static/description", "icon.svg"
18
+ )
19
+ with open(path, "r") as f:
20
+ avatar = f.read()
21
+
22
+ bgcolor = get_hsl_from_seed(self.uuid)
23
+ avatar = avatar.replace("fill:#875a7b", f"fill:{bgcolor}")
24
+ return avatar
25
+ return super()._generate_avatar_gateway()
@@ -0,0 +1,15 @@
1
+ # Copyright 2022 Creu Blanca
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3
+
4
+ from odoo import fields, models
5
+
6
+
7
+ class MailGateway(models.Model):
8
+ _inherit = "mail.gateway"
9
+
10
+ whatsapp_security_key = fields.Char()
11
+ gateway_type = fields.Selection(
12
+ selection_add=[("whatsapp", "WhatsApp")], ondelete={"whatsapp": "cascade"}
13
+ )
14
+ whatsapp_from_phone = fields.Char()
15
+ whatsapp_version = fields.Char(default="15.0")
@@ -0,0 +1,383 @@
1
+ # Copyright 2024 Dixmit
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
3
+ import hashlib
4
+ import hmac
5
+ import logging
6
+ import mimetypes
7
+ import traceback
8
+ from datetime import datetime
9
+ from io import StringIO
10
+
11
+ import requests
12
+ import requests_toolbelt
13
+
14
+ from odoo import _, models
15
+ from odoo.exceptions import UserError
16
+ from odoo.http import request
17
+ from odoo.tools import html2plaintext
18
+
19
+ from odoo.addons.base.models.ir_mail_server import MailDeliveryException
20
+
21
+ _logger = logging.getLogger(__name__)
22
+
23
+
24
+ class MailGatewayWhatsappService(models.AbstractModel):
25
+ _inherit = "mail.gateway.abstract"
26
+ _name = "mail.gateway.whatsapp"
27
+ _description = "Whatsapp Gateway services"
28
+
29
+ def _receive_get_update(self, bot_data, req, **kwargs):
30
+ self._verify_update(bot_data, {})
31
+ gateway = self.env["mail.gateway"].browse(bot_data["id"])
32
+ if kwargs.get("hub.verify_token") != gateway.whatsapp_security_key:
33
+ return None
34
+ gateway.sudo().integrated_webhook_state = "integrated"
35
+ response = request.make_response(kwargs.get("hub.challenge"))
36
+ response.status_code = 200
37
+ return response
38
+
39
+ def _set_webhook(self, gateway):
40
+ gateway.integrated_webhook_state = "pending"
41
+
42
+ def _verify_update(self, bot_data, kwargs):
43
+ signature = request.httprequest.headers.get("x-hub-signature-256")
44
+ if not signature:
45
+ return False
46
+ if (
47
+ "sha256=%s"
48
+ % hmac.new(
49
+ bot_data["webhook_secret"].encode(),
50
+ request.httprequest.data,
51
+ hashlib.sha256,
52
+ ).hexdigest()
53
+ != signature
54
+ ):
55
+ return False
56
+ return True
57
+
58
+ def _get_channel_vals(self, gateway, token, update):
59
+ result = super()._get_channel_vals(gateway, token, update)
60
+ for contact in update.get("contacts", []):
61
+ if contact["wa_id"] == token:
62
+ result["name"] = contact["profile"]["name"]
63
+ continue
64
+ return result
65
+
66
+ def _receive_update(self, gateway, update):
67
+ if update:
68
+ for entry in update["entry"]:
69
+ for change in entry["changes"]:
70
+ if change["field"] != "messages":
71
+ continue
72
+ for message in change["value"].get("messages", []):
73
+ chat = self._get_channel(
74
+ gateway, message["from"], change["value"], force_create=True
75
+ )
76
+ if not chat:
77
+ continue
78
+ self._process_update(chat, message, change["value"])
79
+
80
+ def _process_update(self, chat, message, value):
81
+ chat.ensure_one()
82
+ body = ""
83
+ attachments = []
84
+ if message.get("text"):
85
+ body = message.get("text").get("body")
86
+ for key in ["image", "audio", "video", "document", "sticker"]:
87
+ if message.get(key):
88
+ image_id = message.get(key).get("id")
89
+ if image_id:
90
+ image_info_request = requests.get(
91
+ "https://graph.facebook.com/v%s/%s"
92
+ % (
93
+ chat.gateway_id.whatsapp_version,
94
+ image_id,
95
+ ),
96
+ headers={
97
+ "Authorization": "Bearer %s" % chat.gateway_id.token,
98
+ },
99
+ timeout=10,
100
+ proxies=self._get_proxies(),
101
+ )
102
+ image_info_request.raise_for_status()
103
+ image_info = image_info_request.json()
104
+ image_url = image_info["url"]
105
+ else:
106
+ image_url = message.get(key).get("url")
107
+ if not image_url:
108
+ continue
109
+ image_request = requests.get(
110
+ image_url,
111
+ headers={
112
+ "Authorization": "Bearer %s" % chat.gateway_id.token,
113
+ },
114
+ timeout=10,
115
+ proxies=self._get_proxies(),
116
+ )
117
+ image_request.raise_for_status()
118
+ attachments.append(
119
+ (
120
+ "{}{}".format(
121
+ image_id,
122
+ mimetypes.guess_extension(image_info["mime_type"]),
123
+ ),
124
+ image_request.content,
125
+ )
126
+ )
127
+ if message.get("location"):
128
+ body += (
129
+ '<a target="_blank" href="https://www.google.com/'
130
+ 'maps/search/?api=1&query=%s,%s">Location</a>'
131
+ % (
132
+ message["location"]["latitude"],
133
+ message["location"]["longitude"],
134
+ )
135
+ )
136
+ if message.get("contacts"):
137
+ pass
138
+ if len(body) > 0 or attachments:
139
+ author = self._get_author(chat.gateway_id, value)
140
+ new_message = chat.message_post(
141
+ body=body,
142
+ author_id=author and author._name == "res.partner" and author.id,
143
+ gateway_type="whatsapp",
144
+ date=datetime.fromtimestamp(int(message["timestamp"])),
145
+ # message_id=update.message.message_id,
146
+ subtype_xmlid="mail.mt_comment",
147
+ message_type="comment",
148
+ attachments=attachments,
149
+ )
150
+ self._post_process_message(new_message, chat)
151
+ related_message_id = message.get("context", {}).get("id", False)
152
+ if related_message_id:
153
+ related_message = (
154
+ self.env["mail.notification"]
155
+ .search(
156
+ [
157
+ ("gateway_channel_id", "=", chat.id),
158
+ ("gateway_message_id", "=", related_message_id),
159
+ ]
160
+ )
161
+ .mail_message_id
162
+ )
163
+ if related_message and related_message.gateway_message_id:
164
+ new_related_message = (
165
+ self.env[related_message.gateway_message_id.model]
166
+ .browse(related_message.gateway_message_id.res_id)
167
+ .message_post(
168
+ body=body,
169
+ author_id=author
170
+ and author._name == "res.partner"
171
+ and author.id,
172
+ gateway_type="whatsapp",
173
+ date=datetime.fromtimestamp(int(message["timestamp"])),
174
+ # message_id=update.message.message_id,
175
+ subtype_xmlid="mail.mt_comment",
176
+ message_type="comment",
177
+ attachments=attachments,
178
+ )
179
+ )
180
+ self._post_process_reply(related_message)
181
+ new_message.gateway_message_id = new_related_message
182
+
183
+ def _send(
184
+ self,
185
+ gateway,
186
+ record,
187
+ auto_commit=False,
188
+ raise_exception=False,
189
+ parse_mode=False,
190
+ ):
191
+ message = False
192
+ try:
193
+ attachment_mimetype_map = self._get_whatsapp_mimetype_kind()
194
+ for attachment in record.mail_message_id.attachment_ids:
195
+ if attachment.mimetype not in attachment_mimetype_map:
196
+ raise UserError(_("Mimetype is not valid"))
197
+ attachment_type = attachment_mimetype_map[attachment.mimetype]
198
+ m = requests_toolbelt.multipart.encoder.MultipartEncoder(
199
+ fields={
200
+ "file": (
201
+ attachment.name,
202
+ attachment.raw,
203
+ attachment.mimetype,
204
+ ),
205
+ "messaging_product": "whatsapp",
206
+ # "type": attachment_type
207
+ },
208
+ )
209
+
210
+ response = requests.post(
211
+ "https://graph.facebook.com/v%s/%s/media"
212
+ % (
213
+ gateway.whatsapp_version,
214
+ gateway.whatsapp_from_phone,
215
+ ),
216
+ headers={
217
+ "Authorization": "Bearer %s" % gateway.token,
218
+ "content-type": m.content_type,
219
+ },
220
+ data=m,
221
+ timeout=10,
222
+ proxies=self._get_proxies(),
223
+ )
224
+ response.raise_for_status()
225
+ response = requests.post(
226
+ "https://graph.facebook.com/v%s/%s/messages"
227
+ % (
228
+ gateway.whatsapp_version,
229
+ gateway.whatsapp_from_phone,
230
+ ),
231
+ headers={"Authorization": "Bearer %s" % gateway.token},
232
+ json=self._send_payload(
233
+ record.gateway_channel_id,
234
+ media_id=response.json()["id"],
235
+ media_type=attachment_type,
236
+ media_name=attachment.name,
237
+ ),
238
+ timeout=10,
239
+ proxies=self._get_proxies(),
240
+ )
241
+ response.raise_for_status()
242
+ message = response.json()
243
+ body = self._get_message_body(record)
244
+ if body:
245
+ response = requests.post(
246
+ "https://graph.facebook.com/v%s/%s/messages"
247
+ % (
248
+ gateway.whatsapp_version,
249
+ gateway.whatsapp_from_phone,
250
+ ),
251
+ headers={"Authorization": "Bearer %s" % gateway.token},
252
+ json=self._send_payload(record.gateway_channel_id, body=body),
253
+ timeout=10,
254
+ proxies=self._get_proxies(),
255
+ )
256
+ response.raise_for_status()
257
+ message = response.json()
258
+ except Exception as exc:
259
+ buff = StringIO()
260
+ traceback.print_exc(file=buff)
261
+ _logger.error(buff.getvalue())
262
+ if raise_exception:
263
+ raise MailDeliveryException(
264
+ _("Unable to send the whatsapp message")
265
+ ) from exc
266
+ else:
267
+ _logger.warning(
268
+ "Issue sending message with id {}: {}".format(record.id, exc)
269
+ )
270
+ record.sudo().write(
271
+ {"notification_status": "exception", "failure_reason": exc}
272
+ )
273
+ if message:
274
+ record.sudo().write(
275
+ {
276
+ "notification_status": "sent",
277
+ "failure_reason": False,
278
+ "gateway_message_id": message["messages"][0]["id"],
279
+ }
280
+ )
281
+ if auto_commit is True:
282
+ # pylint: disable=invalid-commit
283
+ self.env.cr.commit()
284
+
285
+ def _send_payload(
286
+ self, channel, body=False, media_id=False, media_type=False, media_name=False
287
+ ):
288
+ if body:
289
+ return {
290
+ "messaging_product": "whatsapp",
291
+ "recipient_type": "individual",
292
+ "to": channel.gateway_channel_token,
293
+ "type": "text",
294
+ "text": {"preview_url": False, "body": html2plaintext(body)},
295
+ }
296
+ if media_id:
297
+ media_data = {"id": media_id}
298
+ if media_type == "document":
299
+ media_data["filename"] = media_name
300
+ return {
301
+ "messaging_product": "whatsapp",
302
+ "recipient_type": "individual",
303
+ "to": channel.gateway_channel_token,
304
+ "type": media_type,
305
+ media_type: media_data,
306
+ }
307
+
308
+ def _get_whatsapp_mimetype_kind(self):
309
+ return {
310
+ "text/plain": "document",
311
+ "application/pdf": "document",
312
+ "application/vnd.ms-powerpoint": "document",
313
+ "application/msword": "document",
314
+ "application/vnd.ms-excel": "document",
315
+ "application/vnd.openxmlformats-officedocument."
316
+ "wordprocessingml.document": "document",
317
+ "application/vnd.openxmlformats-officedocument."
318
+ "presentationml.presentation": "document",
319
+ "application/vnd.openxmlformats-officedocument."
320
+ "spreadsheetml.sheet": "document",
321
+ "audio/aac": "audio",
322
+ "audio/mp4": "audio",
323
+ "audio/mpeg": "audio",
324
+ "audio/amr": "audio",
325
+ "audio/ogg": "audio",
326
+ "image/jpeg": "image",
327
+ "image/png": "image",
328
+ "video/mp4": "video",
329
+ "video/3gp": "video",
330
+ "image/webp": "sticker",
331
+ }
332
+
333
+ def _get_author(self, gateway, update):
334
+ author_id = update.get("messages")[0].get("from")
335
+ if author_id:
336
+ gateway_partner = self.env["res.partner.gateway.channel"].search(
337
+ [
338
+ ("gateway_id", "=", gateway.id),
339
+ ("gateway_token", "=", str(author_id)),
340
+ ]
341
+ )
342
+ if gateway_partner:
343
+ return gateway_partner.partner_id
344
+ partner = self.env["res.partner"].search(
345
+ [("phone_sanitized", "=", "+" + str(author_id))]
346
+ )
347
+ if partner:
348
+ self.env["res.partner.gateway.channel"].create(
349
+ {
350
+ "name": gateway.name,
351
+ "partner_id": partner.id,
352
+ "gateway_id": gateway.id,
353
+ "gateway_token": str(author_id),
354
+ }
355
+ )
356
+ return partner
357
+ guest = self.env["mail.guest"].search(
358
+ [
359
+ ("gateway_id", "=", gateway.id),
360
+ ("gateway_token", "=", str(author_id)),
361
+ ]
362
+ )
363
+ if guest:
364
+ return guest
365
+ author_vals = self._get_author_vals(gateway, author_id, update)
366
+ if author_vals:
367
+ return self.env["mail.guest"].create(author_vals)
368
+
369
+ return False
370
+
371
+ def _get_author_vals(self, gateway, author_id, update):
372
+ for contact in update.get("contacts", []):
373
+ if contact["wa_id"] == author_id:
374
+ return {
375
+ "name": contact.get("profile", {}).get("name", "Anonymous"),
376
+ "gateway_id": gateway.id,
377
+ "gateway_token": str(author_id),
378
+ }
379
+
380
+ def _get_proxies(self):
381
+ # This hook has been created in order to add a proxy if needed.
382
+ # By default, it does nothing.
383
+ return {}
@@ -0,0 +1,65 @@
1
+ # Copyright 2022 CreuBlanca
2
+ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3
+
4
+ from odoo import _, models
5
+ from odoo.exceptions import UserError
6
+
7
+ from odoo.addons.phone_validation.tools import phone_validation
8
+
9
+
10
+ class MailThread(models.AbstractModel):
11
+
12
+ _inherit = "mail.thread"
13
+
14
+ def _get_whatsapp_channel_vals(self, token, gateway, partner):
15
+ result = {
16
+ "gateway_channel_token": token,
17
+ "gateway_id": gateway.id,
18
+ }
19
+ if partner:
20
+ result["partner_id"] = partner.id
21
+ result["name"] = partner.display_name
22
+ return result
23
+
24
+ def _whatsapp_get_channel(self, field_name, gateway):
25
+ phone = self[field_name]
26
+ sanitize_res = phone_validation.phone_sanitize_numbers_w_record([phone], self)
27
+ sanitized_number = sanitize_res[phone].get("sanitized")
28
+ if not sanitized_number:
29
+ raise UserError(_("Phone cannot be sanitized"))
30
+ sanitized_number = sanitized_number[1:]
31
+ partner = self._whatsapp_get_partner()
32
+ if not self.env["res.partner.gateway.channel"].search(
33
+ [
34
+ ("partner_id", "=", partner.id),
35
+ ("gateway_id", "=", gateway.id),
36
+ ("gateway_token", "=", sanitized_number),
37
+ ]
38
+ ):
39
+ self.env["res.partner.gateway.channel"].create(
40
+ {
41
+ "name": gateway.name,
42
+ "partner_id": partner.id,
43
+ "gateway_id": gateway.id,
44
+ "gateway_token": sanitized_number,
45
+ }
46
+ )
47
+ return self.env["mail.gateway.whatsapp"]._get_channel(
48
+ gateway,
49
+ sanitized_number,
50
+ {
51
+ "contacts": [
52
+ {
53
+ "wa_id": sanitized_number,
54
+ "profile": {"name": partner.display_name},
55
+ }
56
+ ],
57
+ "messages": [{"from": sanitized_number}],
58
+ },
59
+ force_create=True,
60
+ )
61
+
62
+ def _whatsapp_get_partner(self):
63
+ if "partner_id" in self._fields:
64
+ return self.partner_id
65
+ return None
@@ -0,0 +1,20 @@
1
+ # Copyright 2024 Dixmit
2
+ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
3
+
4
+ from odoo import models
5
+
6
+
7
+ class ResPartner(models.Model):
8
+ _name = "res.partner"
9
+ _inherit = ["mail.thread.phone", "res.partner"]
10
+
11
+ def _whatsapp_get_partner(self):
12
+ return self
13
+
14
+ def _phone_get_number_fields(self):
15
+ """This method returns the fields to use to find the number to use to
16
+ send an SMS on a record."""
17
+ result = set(super()._phone_get_number_fields())
18
+ result.add("mobile")
19
+ result.add("phone")
20
+ return list(result)
@@ -0,0 +1,30 @@
1
+ First steps
2
+ ~~~~~~~~~~~
3
+
4
+ You need to create a WhatsApp Business Account (WABA), a Meta App and define a phone number.
5
+ You can follow this `steps <https://developers.facebook.com/micro_site/url/?click_from_context_menu=true&country=ES&destination=https%3A%2F%2Fwww.facebook.com%2Fbusiness%2Fhelp%2F2087193751603668&event_type=click&last_nav_impression_id=0m3TRxrxOlly1eRmB&max_percent_page_viewed=22&max_viewport_height_px=1326&max_viewport_width_px=2560&orig_http_referrer=https%3A%2F%2Fdevelopers.facebook.com%2Fdocs%2Fwhatsapp%2Fcloud-api%2Fget-started-for-bsps%3Flocale%3Den_US&orig_request_uri=https%3A%2F%2Fdevelopers.facebook.com%2Fajax%2Fpagelet%2Fgeneric.php%2FDeveloperNotificationsPayloadPagelet%3Ffb_dtsg_ag%3D--sanitized--%26data%3D%257B%2522businessUserID%2522%253Anull%252C%2522cursor%2522%253Anull%252C%2522length%2522%253A15%252C%2522clientRequestID%2522%253A%2522js_k6%2522%257D%26__usid%3D6-Trd7hi4itpm%253APrd7ifiub2tvy%253A0-Ard7g9twdm0p1-RV%253D6%253AF%253D%26locale%3Den_US%26jazoest%3D24920&region=emea&scrolled=false&session_id=1jLoVJNU6iVMaw3ml&site=developers>`_.
6
+
7
+ If you create a test Business Account, passwords will change every 24 hours.
8
+
9
+ In order to make the webhook accessible, the system must be public.
10
+
11
+ Configure the gateway
12
+ ~~~~~~~~~~~~~~~~~~~~~
13
+
14
+ Once you have created the Meta App, you need to add the gateway and webhook.
15
+ In order to make it you must follow this steps:
16
+
17
+ * Access `Settings > Emails > Mail Gateway`
18
+ * Create a Gateway of type `WhatsApp`
19
+
20
+ * Use the Meta App authentication key as `Token` field
21
+ * Use the Meta App Phone Number ID as `Whatsapp from Phone` field
22
+ * Write your own `Webhook key`
23
+ * Use the Application Secret Key on `Whatsapp Security Key`. It will be used in order to validate the data
24
+ * Press the `Integrate Webhook Key`. In this case, it will not integrate it, we need to make it manually
25
+ * Copy the webhook URL
26
+
27
+ * Access `Facebook Apps website <https://developers.facebook.com/apps/>`_
28
+ * Access your App then `Whatsapp > Configuration`
29
+ * Create your webhook using your URL and put the Whatsapp Security Key as validation Key
30
+ * Administer the Webhook and activate the messages webhook
@@ -0,0 +1,2 @@
1
+ * Olga Marco <olga.marco@creublanca.es>
2
+ * Enric Tobella <etobella@creublanca.es>
@@ -0,0 +1 @@
1
+ This work has been funded by AEOdoo (Asociación Española de Odoo - https://www.aeodoo.org)
@@ -0,0 +1,4 @@
1
+ This module allows to respond whatsapp chats.
2
+
3
+ This way, a group of users can respond customers or any other set
4
+ of partners in an integrated way.
@@ -0,0 +1,3 @@
1
+ 1. Access `Gateway`
2
+ 2. Wait until someone starts a conversation.
3
+ 3. Now you will be able to respond and receive messages to this person.
@@ -0,0 +1,2 @@
1
+ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2
+ access_whatsapp_composer,access.whatsapp.composer,model_whatsapp_composer,base.group_user,1,1,1,0
@@ -0,0 +1,48 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ viewBox="0 0 240 240"
4
+ version="1.1"
5
+ id="svg4"
6
+ sodipodi:docname="whatsapp.svg"
7
+ inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
8
+ width="240"
9
+ height="240"
10
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ xmlns:svg="http://www.w3.org/2000/svg">
14
+ <defs
15
+ id="defs8" />
16
+ <sodipodi:namedview
17
+ id="namedview6"
18
+ pagecolor="#ffffff"
19
+ bordercolor="#666666"
20
+ borderopacity="1.0"
21
+ inkscape:pageshadow="2"
22
+ inkscape:pageopacity="0.0"
23
+ inkscape:pagecheckerboard="0"
24
+ showgrid="false"
25
+ inkscape:zoom="2.8085937"
26
+ inkscape:cx="103.25452"
27
+ inkscape:cy="127.2879"
28
+ inkscape:window-width="1858"
29
+ inkscape:window-height="1016"
30
+ inkscape:window-x="0"
31
+ inkscape:window-y="0"
32
+ inkscape:window-maximized="1"
33
+ inkscape:current-layer="svg4"
34
+ units="px"
35
+ width="240px" />
36
+ <!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.-->
37
+ <rect
38
+ style="fill:#875a7b"
39
+ id="rect965"
40
+ width="240"
41
+ height="240"
42
+ x="0"
43
+ y="0" />
44
+ <path
45
+ d="M 176.0428,63.25121 C 161.07774,48.25043 141.14813,40 119.96845,40 76.25189,40 40.6786,75.57328 40.6786,119.28985 c 0,13.96501 3.64306,27.60858 10.57199,39.64492 L 40,200.00834 82.0379,188.97205 c 11.572034,6.32176 24.60843,9.64336 37.89484,9.64336 h 0.0357 c 43.68085,0 80.03989,-35.57328 80.03989,-79.28985 0,-21.17967 -9.00047,-41.07357 -23.96554,-56.07435 z m -56.07435,122.00636 c -11.85776,0 -23.465496,-3.17873 -33.573176,-9.17905 l -2.392984,-1.42864 -24.92987,6.53605 6.64321,-24.3227 -1.57151,-2.50012 c -6.60749,-10.50055 -10.07196,-22.60832 -10.07196,-35.07326 0,-36.32332 29.57297,-65.89629 65.93201,-65.89629 17.60806,0 34.14464,6.8575 46.57385,19.32243 12.42923,12.46494 20.07248,29.00151 20.03677,46.60957 0,36.35904 -30.32301,65.93201 -66.64634,65.93201 z m 36.14474,-49.35971 c -1.96438,-1.00005 -11.71489,-5.78602 -13.53641,-6.42891 -1.82153,-0.67861 -3.14303,-1.00005 -4.46453,1.00005 -1.32149,2.00011 -5.1074,6.42891 -6.28603,7.78612 -1.14292,1.3215 -2.32155,1.50008 -4.28594,0.50003 -11.64347,-5.82173 -19.28673,-10.3934 -26.9657,-23.57266 -2.03582,-3.50018 2.03583,-3.25017 5.82174,-10.82199 0.64289,-1.3215 0.32144,-2.46441 -0.17858,-3.46447 -0.50003,-1.00005 -4.46452,-10.75056 -6.10747,-14.71505 -1.60722,-3.85734 -3.25016,-3.3216 -4.464506,-3.39304 -1.14292,-0.0714 -2.46442,-0.0714 -3.78592,-0.0714 -1.32149,0 -3.46446,0.50003 -5.28598,2.46442 -1.821534,2.0001 -6.928944,6.78607 -6.928944,16.53657 0,9.75051 7.107514,19.17957 8.071854,20.50107 1.00005,1.3215 13.965006,21.32254 33.858906,29.93014 12.57209,5.42885 17.50091,5.89316 23.78695,4.96454 3.82163,-0.57146 11.7149,-4.78597 13.35784,-9.42907 1.64294,-4.6431 1.64294,-8.60758 1.14292,-9.42906 -0.46431,-0.8929 -1.78581,-1.39293 -3.7502,-2.35726 z"
46
+ id="path2"
47
+ style="fill:#ffffff;fill-opacity:1;stroke-width:0.357162" />
48
+ </svg>