├── .gitignore ├── MANIFEST.in ├── README.md ├── frappe_meta_integration ├── __init__.py ├── config │ ├── __init__.py │ ├── desktop.py │ └── docs.py ├── fixtures │ ├── custom_field.json │ ├── property_setter.json │ ├── role.json │ └── whatsapp_message_template.json ├── frappe_meta_integration │ └── __init__.py ├── hooks.py ├── modules.txt ├── patches.txt ├── patches │ ├── __init__.py │ └── v1_0 │ │ ├── __init__.py │ │ └── remove_erpnext_dependency.py ├── public │ ├── __init__.py │ ├── build.json │ └── js │ │ └── toolbar.js ├── templates │ ├── __init__.py │ └── pages │ │ └── __init__.py └── whatsapp │ ├── __init__.py │ ├── api │ └── webhook.py │ ├── custom │ └── contact.json │ ├── docevents.py │ ├── doctype │ ├── __init__.py │ ├── contact_whatsapp │ │ ├── __init__.py │ │ ├── contact_whatsapp.json │ │ └── contact_whatsapp.py │ ├── whatsapp_campaign │ │ ├── __init__.py │ │ ├── test_whatsapp_campaign.py │ │ ├── whatsapp_campaign.js │ │ ├── whatsapp_campaign.json │ │ └── whatsapp_campaign.py │ ├── whatsapp_campaign_recipient │ │ ├── __init__.py │ │ ├── whatsapp_campaign_recipient.json │ │ └── whatsapp_campaign_recipient.py │ ├── whatsapp_cloud_api_settings │ │ ├── __init__.py │ │ ├── test_whatsapp_cloud_api_settings.py │ │ ├── whatsapp_cloud_api_settings.js │ │ ├── whatsapp_cloud_api_settings.json │ │ └── whatsapp_cloud_api_settings.py │ ├── whatsapp_communication │ │ ├── __init__.py │ │ ├── test_whatsapp_communication.py │ │ ├── whatsapp_communication.js │ │ ├── whatsapp_communication.json │ │ └── whatsapp_communication.py │ ├── whatsapp_message_template │ │ ├── __init__.py │ │ ├── test_whatsapp_message_template.py │ │ ├── whatsapp_message_template.js │ │ ├── whatsapp_message_template.json │ │ └── whatsapp_message_template.py │ ├── whatsapp_message_template_item │ │ ├── __init__.py │ │ ├── whatsapp_message_template_item.json │ │ └── whatsapp_message_template_item.py │ └── whatsapp_webhook_log │ │ ├── __init__.py │ │ ├── test_whatsapp_webhook_log.py │ │ ├── whatsapp_webhook_log.js │ │ ├── whatsapp_webhook_log.json │ │ └── whatsapp_webhook_log.py │ ├── overrides │ └── notification.py │ ├── pdf_utils.py │ ├── public │ └── js │ │ ├── notification.js │ │ └── user.js │ ├── utils.py │ └── workspace │ └── meta_integration │ └── meta_integration.json ├── license.txt ├── pull_request_template.md ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.egg-info 4 | *.swp 5 | tags 6 | frappe_meta_integration/docs/current -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MANIFEST.in 2 | include requirements.txt 3 | include *.json 4 | include *.md 5 | include *.py 6 | include *.txt 7 | recursive-include frappe_meta_integration *.css 8 | recursive-include frappe_meta_integration *.csv 9 | recursive-include frappe_meta_integration *.html 10 | recursive-include frappe_meta_integration *.ico 11 | recursive-include frappe_meta_integration *.js 12 | recursive-include frappe_meta_integration *.json 13 | recursive-include frappe_meta_integration *.md 14 | recursive-include frappe_meta_integration *.png 15 | recursive-include frappe_meta_integration *.py 16 | recursive-include frappe_meta_integration *.svg 17 | recursive-include frappe_meta_integration *.txt 18 | recursive-exclude frappe_meta_integration *.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Frappe Meta Integration 2 | 3 | Meta Cloud API Integration for frappe framework 4 | 5 | --- 6 | 7 | ### Main Features 8 | 9 | 1. Supports Frappe Version 14 10 | 2. Supports Text, Media and Template Based WhatsApp Messaging. 11 | 3. Sends a Welcome Message on User Creation. 12 | 13 | 14 | ### Screenshots 15 | 16 | 1. Workspace 17 | 18 | ![Workspace](https://user-images.githubusercontent.com/91651425/201308548-63279562-6152-4cd3-94a4-29bf6df173a0.png) 19 | 20 | 21 | 2. WhatsApp Cloud API Settings 22 | 23 | ![WhatsApp Cloud API Settings](https://user-images.githubusercontent.com/95274912/201276165-3039a7d4-44f3-4bf2-9a04-dd27a05a0e84.png) 24 | 25 | ![token verification](https://user-images.githubusercontent.com/95274912/201276360-21a41b58-6e97-4168-a592-cea02b8fbbe4.png) 26 | 27 | 28 | 2.1 Token Verification 29 | 30 | - Using your Facebook developer account, create the WhatsApp Business API. 31 | 32 | - The WhatsApp Business API is used to set the Access Token, Phone Number ID, and WhatsApp Business Account ID. 33 | 34 | - These Data is passed to WhatsApp Cloud API Settings and then saved. 35 | 36 | - Verify the token using the "Verify Token" button. A pop-up window appears on the screen. 37 | 38 | - Add the Whatsapp number and submit. 39 | 40 | 41 | 3. WhatsApp Communication 42 | 43 | ![whatsup communication](https://user-images.githubusercontent.com/95274912/201277603-d4e79b63-4e13-492f-8aa1-4172b3396cad.png) 44 | 45 | - There are three message types available in WhatsApp communication: text, document, and template. 46 | 47 | 3.1 Message Type : Text 48 | - A text field for simple text is provided here. 49 | 50 | 3.2 Message Type : Document 51 | - Here are some fields where media information can be entered. 52 | 53 | 3.3 Message Type : Template 54 | - Here message templates information can be added. 55 | 56 | --- 57 | 58 | ### How to Install 59 | 60 | 1. `bench get-app https://github.com/efeone/frappe_meta_integration.git` 61 | 2. `bench setup requirements` 62 | 3. `bench build --app frappe_meta_integration` 63 | 4. `bench --site [your.site.name] install-app frappe_meta_integration` 64 | 5. `bench --site [your.site.name] migrate` 65 | 66 | --- 67 | 68 | ### Prerequisites: 69 | 70 | - Meta for Developers Account 71 | - WhatsApp configured in the Meta Developer Account 72 | - Verified Business on Meta 73 | - Verified WhatsApp Number and a Permanent Token 74 | 75 | --- 76 | 77 | 78 | ### Dependencies: 79 | 80 | - [Frappe](https://github.com/frappe/frappe) 81 | 82 | --- 83 | 84 | ### Contributing 85 | 86 | Will be using the same guidelines from ERPNext 87 | 88 | 1. [Issue Guidelines](https://github.com/frappe/erpnext/wiki/Issue-Guidelines) 89 | 2. [Pull Request Requirements](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines) 90 | 91 | --- 92 | 93 | #### License 94 | 95 | GNU/General Public License (see [license.txt](https://github.com/efeone/frappe_meta_integration/blob/master/license.txt)) 96 | Frappe Meta Integration code is licensed as GNU General Public License (v3) 97 | -------------------------------------------------------------------------------- /frappe_meta_integration/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | __version__ = '0.0.1' 3 | 4 | -------------------------------------------------------------------------------- /frappe_meta_integration/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/efeone/frappe_meta_integration/9e4a95c8eeb46e76b51a77a24391b7d0582e6674/frappe_meta_integration/config/__init__.py -------------------------------------------------------------------------------- /frappe_meta_integration/config/desktop.py: -------------------------------------------------------------------------------- 1 | from frappe import _ 2 | 3 | def get_data(): 4 | return [ 5 | { 6 | "module_name": "Frappe Meta Integration", 7 | "color": "grey", 8 | "icon": "octicon octicon-file-directory", 9 | "type": "module", 10 | "label": _("Frappe Meta Integration") 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /frappe_meta_integration/config/docs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Configuration for docs 3 | """ 4 | 5 | # source_link = "https://github.com/[org_name]/frappe_meta_integration" 6 | # docs_base_url = "https://[org_name].github.io/frappe_meta_integration" 7 | # headline = "App that does everything" 8 | # sub_heading = "Yes, you got that right the first time, everything" 9 | 10 | def get_context(context): 11 | context.brand_html = "Frappe Meta Integration" 12 | -------------------------------------------------------------------------------- /frappe_meta_integration/fixtures/custom_field.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "allow_in_quick_entry": 1, 4 | "allow_on_submit": 0, 5 | "bold": 0, 6 | "collapsible": 0, 7 | "collapsible_depends_on": null, 8 | "columns": 0, 9 | "default": null, 10 | "depends_on": "", 11 | "description": "WhatsApp Number with country code.\neg : 919876543210", 12 | "docstatus": 0, 13 | "doctype": "Custom Field", 14 | "dt": "User", 15 | "fetch_from": null, 16 | "fetch_if_empty": 0, 17 | "fieldname": "user_whatsapp_number", 18 | "fieldtype": "Data", 19 | "hidden": 0, 20 | "hide_border": 0, 21 | "hide_days": 0, 22 | "hide_seconds": 0, 23 | "ignore_user_permissions": 0, 24 | "ignore_xss_filter": 0, 25 | "in_global_search": 0, 26 | "in_list_view": 0, 27 | "in_preview": 0, 28 | "in_standard_filter": 0, 29 | "insert_after": "mobile_no", 30 | "is_system_generated": 0, 31 | "is_virtual": 0, 32 | "label": "Whatsapp Number", 33 | "length": 0, 34 | "mandatory_depends_on": null, 35 | "modified": "2022-11-04 10:15:48.843446", 36 | "module": "WhatsApp", 37 | "name": "User-user_whatsapp_number", 38 | "no_copy": 0, 39 | "non_negative": 0, 40 | "options": "Phone", 41 | "permlevel": 0, 42 | "precision": "", 43 | "print_hide": 0, 44 | "print_hide_if_no_value": 0, 45 | "print_width": null, 46 | "read_only": 0, 47 | "read_only_depends_on": null, 48 | "report_hide": 0, 49 | "reqd": 0, 50 | "search_index": 0, 51 | "translatable": 0, 52 | "unique": 0, 53 | "width": null 54 | }, 55 | { 56 | "allow_in_quick_entry": 0, 57 | "allow_on_submit": 0, 58 | "bold": 0, 59 | "collapsible": 0, 60 | "collapsible_depends_on": null, 61 | "columns": 0, 62 | "default": null, 63 | "depends_on": "0", 64 | "description": null, 65 | "docstatus": 0, 66 | "doctype": "Custom Field", 67 | "dt": "User", 68 | "fetch_from": null, 69 | "fetch_if_empty": 0, 70 | "fieldname": "whatsapp_2f_authenticated", 71 | "fieldtype": "Check", 72 | "hidden": 1, 73 | "hide_border": 0, 74 | "hide_days": 0, 75 | "hide_seconds": 0, 76 | "ignore_user_permissions": 0, 77 | "ignore_xss_filter": 0, 78 | "in_global_search": 0, 79 | "in_list_view": 0, 80 | "in_preview": 0, 81 | "in_standard_filter": 0, 82 | "insert_after": "user_whatsapp_number", 83 | "is_system_generated": 0, 84 | "is_virtual": 0, 85 | "label": "Whatsapp 2F Authenticated", 86 | "length": 0, 87 | "mandatory_depends_on": null, 88 | "modified": "2022-11-04 10:14:47.962246", 89 | "module": "WhatsApp", 90 | "name": "User-whatsapp_2f_authenticated", 91 | "no_copy": 0, 92 | "non_negative": 0, 93 | "options": null, 94 | "permlevel": 0, 95 | "precision": "", 96 | "print_hide": 0, 97 | "print_hide_if_no_value": 0, 98 | "print_width": null, 99 | "read_only": 1, 100 | "read_only_depends_on": null, 101 | "report_hide": 0, 102 | "reqd": 0, 103 | "search_index": 0, 104 | "translatable": 0, 105 | "unique": 0, 106 | "width": null 107 | } 108 | ] 109 | -------------------------------------------------------------------------------- /frappe_meta_integration/fixtures/property_setter.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "default_value": null, 4 | "doc_type": "Notification", 5 | "docstatus": 0, 6 | "doctype": "Property Setter", 7 | "doctype_or_field": "DocField", 8 | "field_name": "channel", 9 | "modified": "2022-07-06 15:43:38.505231", 10 | "name": "Notification-channel-options", 11 | "parent": null, 12 | "parentfield": null, 13 | "parenttype": null, 14 | "property": "options", 15 | "property_type": "Select", 16 | "row_name": null, 17 | "value": "Email\nSlack\nSystem Notification\nSMS\nWhatsApp" 18 | } 19 | ] 20 | -------------------------------------------------------------------------------- /frappe_meta_integration/fixtures/role.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "bulk_actions": 1, 4 | "dashboard": 1, 5 | "desk_access": 1, 6 | "disabled": 0, 7 | "docstatus": 0, 8 | "doctype": "Role", 9 | "form_sidebar": 1, 10 | "home_page": null, 11 | "is_custom": 0, 12 | "list_sidebar": 1, 13 | "modified": "2022-11-09 20:23:31.534419", 14 | "name": "Meta User", 15 | "notifications": 1, 16 | "restrict_to_domain": null, 17 | "role_name": "Meta User", 18 | "search_bar": 1, 19 | "timeline": 1, 20 | "two_factor_auth": 0, 21 | "view_switcher": 1 22 | }, 23 | { 24 | "bulk_actions": 1, 25 | "dashboard": 1, 26 | "desk_access": 1, 27 | "disabled": 0, 28 | "docstatus": 0, 29 | "doctype": "Role", 30 | "form_sidebar": 1, 31 | "home_page": null, 32 | "is_custom": 0, 33 | "list_sidebar": 1, 34 | "modified": "2022-11-09 20:23:51.113711", 35 | "name": "Meta Manager", 36 | "notifications": 1, 37 | "restrict_to_domain": null, 38 | "role_name": "Meta Manager", 39 | "search_bar": 1, 40 | "timeline": 1, 41 | "two_factor_auth": 0, 42 | "view_switcher": 1 43 | } 44 | ] -------------------------------------------------------------------------------- /frappe_meta_integration/fixtures/whatsapp_message_template.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "amended_from": null, 4 | "category": "MARKETING", 5 | "docstatus": 0, 6 | "doctype": "WhatsApp Message Template", 7 | "footer": null, 8 | "header": null, 9 | "id": null, 10 | "language": "en", 11 | "language_code": "en", 12 | "modified": "2022-11-10 23:16:51.973551", 13 | "name": "welcome_message", 14 | "parameter_count": 1, 15 | "parameters": [ 16 | { 17 | "parameter": "user", 18 | "parent": "welcome_message", 19 | "parentfield": "parameters", 20 | "parenttype": "WhatsApp Message Template", 21 | "value": null 22 | } 23 | ], 24 | "template": "Hi {{1}},\n\nYou are added as new user. Do you like to get notified here ?", 25 | "template_name": "welcome_message" 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /frappe_meta_integration/frappe_meta_integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/efeone/frappe_meta_integration/9e4a95c8eeb46e76b51a77a24391b7d0582e6674/frappe_meta_integration/frappe_meta_integration/__init__.py -------------------------------------------------------------------------------- /frappe_meta_integration/hooks.py: -------------------------------------------------------------------------------- 1 | from . import __version__ as app_version 2 | 3 | app_name = "frappe_meta_integration" 4 | app_title = "Frappe Meta Integration" 5 | app_publisher = "efeone Pvt. Ltd." 6 | app_description = "Meta Cloud API Integration for frappe framework" 7 | app_icon = "octicon octicon-file-directory" 8 | app_color = "grey" 9 | app_email = "info@efeone.com" 10 | app_license = "MIT" 11 | 12 | # Includes in 13 | # ------------------ 14 | 15 | # include js, css files in header of desk.html 16 | # app_include_css = "/assets/frappe_meta_integration/css/frappe_meta_integration.css" 17 | app_include_js = "/assets/frappe_meta_integration/js/toolbar.js" 18 | 19 | # include js, css files in header of web template 20 | # web_include_css = "/assets/frappe_meta_integration/css/frappe_meta_integration.css" 21 | # web_include_js = "/assets/frappe_meta_integration/js/frappe_meta_integration.js" 22 | 23 | # include custom scss in every website theme (without file extension ".scss") 24 | # website_theme_scss = "frappe_meta_integration/public/scss/website" 25 | 26 | # include js, css files in header of web form 27 | # webform_include_js = {"doctype": "public/js/doctype.js"} 28 | # webform_include_css = {"doctype": "public/css/doctype.css"} 29 | 30 | # include js in page 31 | # page_js = {"page" : "public/js/file.js"} 32 | 33 | # include js in doctype views 34 | doctype_js = { 35 | "Notification" : "whatsapp/public/js/notification.js", 36 | "User" : "whatsapp/public/js/user.js" 37 | } 38 | # doctype_list_js = {"doctype" : "public/js/doctype_list.js"} 39 | # doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} 40 | # doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} 41 | 42 | # Home Pages 43 | # ---------- 44 | 45 | # application home page (will override Website Settings) 46 | # home_page = "login" 47 | 48 | # website user home page (by Role) 49 | # role_home_page = { 50 | # "Role": "home_page" 51 | # } 52 | 53 | # Generators 54 | # ---------- 55 | 56 | # automatically create page for each record of this doctype 57 | # website_generators = ["Web Page"] 58 | 59 | # Installation 60 | # ------------ 61 | 62 | # before_install = "frappe_meta_integration.install.before_install" 63 | # after_install = "frappe_meta_integration.install.after_install" 64 | 65 | # Uninstallation 66 | # ------------ 67 | 68 | # before_uninstall = "frappe_meta_integration.uninstall.before_uninstall" 69 | # after_uninstall = "frappe_meta_integration.uninstall.after_uninstall" 70 | 71 | # Desk Notifications 72 | # ------------------ 73 | # See frappe.core.notifications.get_notification_config 74 | 75 | # notification_config = "frappe_meta_integration.notifications.get_notification_config" 76 | 77 | # Permissions 78 | # ----------- 79 | # Permissions evaluated in scripted ways 80 | 81 | # permission_query_conditions = { 82 | # "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", 83 | # } 84 | # 85 | # has_permission = { 86 | # "Event": "frappe.desk.doctype.event.event.has_permission", 87 | # } 88 | 89 | # DocType Class 90 | # --------------- 91 | # Override standard doctype classes 92 | 93 | override_doctype_class = { 94 | "Notification": "frappe_meta_integration.whatsapp.overrides.notification.SendNotification" 95 | } 96 | 97 | # Document Events 98 | # --------------- 99 | # Hook on document methods and events 100 | doc_events = { 101 | "Contact": { 102 | "validate": "frappe_meta_integration.whatsapp.docevents.contact_validate", 103 | }, 104 | "User": { 105 | "after_insert" : "frappe_meta_integration.whatsapp.docevents.user_after_insert" 106 | } 107 | } 108 | 109 | 110 | fixtures = ["Property Setter"] 111 | 112 | # Scheduled Tasks 113 | # --------------- 114 | 115 | # scheduler_events = { 116 | # "all": [ 117 | # "frappe_meta_integration.tasks.all" 118 | # ], 119 | # "daily": [ 120 | # "frappe_meta_integration.tasks.daily" 121 | # ], 122 | # "hourly": [ 123 | # "frappe_meta_integration.tasks.hourly" 124 | # ], 125 | # "weekly": [ 126 | # "frappe_meta_integration.tasks.weekly" 127 | # ] 128 | # "monthly": [ 129 | # "frappe_meta_integration.tasks.monthly" 130 | # ] 131 | # } 132 | 133 | # Testing 134 | # ------- 135 | 136 | # before_tests = "frappe_meta_integration.install.before_tests" 137 | 138 | # Overriding Methods 139 | # ------------------------------ 140 | # 141 | # override_whitelisted_methods = { 142 | # "frappe.desk.doctype.event.event.get_events": "frappe_meta_integration.event.get_events" 143 | # } 144 | # 145 | # each overriding function accepts a `data` argument; 146 | # generated from the base implementation of the doctype dashboard, 147 | # along with any modifications made in other Frappe apps 148 | # override_doctype_dashboards = { 149 | # "Task": "frappe_meta_integration.task.get_dashboard_data" 150 | # } 151 | 152 | # exempt linked doctypes from being automatically cancelled 153 | # 154 | # auto_cancel_exempted_doctypes = ["Auto Repeat"] 155 | 156 | 157 | # User Data Protection 158 | # -------------------- 159 | 160 | user_data_fields = [ 161 | { 162 | "doctype": "{doctype_1}", 163 | "filter_by": "{filter_by}", 164 | "redact_fields": ["{field_1}", "{field_2}"], 165 | "partial": 1, 166 | }, 167 | { 168 | "doctype": "{doctype_2}", 169 | "filter_by": "{filter_by}", 170 | "partial": 1, 171 | }, 172 | { 173 | "doctype": "{doctype_3}", 174 | "strict": False, 175 | }, 176 | { 177 | "doctype": "{doctype_4}" 178 | } 179 | ] 180 | 181 | # Authentication and authorization 182 | # -------------------------------- 183 | 184 | # auth_hooks = [ 185 | # "frappe_meta_integration.auth.validate" 186 | # ] 187 | 188 | # Translation 189 | # -------------------------------- 190 | 191 | # Make link fields search translated document names for these DocTypes 192 | # Recommended only for DocTypes which have limited documents with untranslated names 193 | # For example: Role, Gender, etc. 194 | # translated_search_doctypes = [] 195 | -------------------------------------------------------------------------------- /frappe_meta_integration/modules.txt: -------------------------------------------------------------------------------- 1 | Frappe Meta Integration 2 | WhatsApp -------------------------------------------------------------------------------- /frappe_meta_integration/patches.txt: -------------------------------------------------------------------------------- 1 | frappe_meta_integration.patches.v1_0.remove_erpnext_dependency #28-07-2022 2 | -------------------------------------------------------------------------------- /frappe_meta_integration/patches/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | -------------------------------------------------------------------------------- /frappe_meta_integration/patches/v1_0/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | -------------------------------------------------------------------------------- /frappe_meta_integration/patches/v1_0/remove_erpnext_dependency.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import frappe 3 | 4 | def execute(): 5 | frappe.enqueue(remove_custom_fields, queue='long') 6 | 7 | def remove_custom_fields(): 8 | if frappe.db.exists("Custom Field", {"fieldname": "whatsapp_number"}): 9 | custom_field_list = frappe.db.get_list("Custom Field", filters={"fieldname": "whatsapp_number"}) 10 | for custom_field in custom_field_list: 11 | custom_field_doc = frappe.get_doc("Custom Field", custom_field.name) 12 | custom_field_doc.dt in ['Patient', 'Customer', 'Lab Test'] 13 | custom_field_doc.delete() 14 | frappe.db.commit() 15 | -------------------------------------------------------------------------------- /frappe_meta_integration/public/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/efeone/frappe_meta_integration/9e4a95c8eeb46e76b51a77a24391b7d0582e6674/frappe_meta_integration/public/__init__.py -------------------------------------------------------------------------------- /frappe_meta_integration/public/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "js/desk.min.js": [ 3 | "public/js/toolbar.js", 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /frappe_meta_integration/public/js/toolbar.js: -------------------------------------------------------------------------------- 1 | frappe.provide('frappe.ui.form'); 2 | frappe.provide('frappe.model.docinfo'); 3 | 4 | $(document).ready(function (){ 5 | frappe.ui.form.Controller = Class.extend({ 6 | init: function(opts) { 7 | $.extend(this, opts); 8 | let ignored_doctype_list = ["DocType", "Customize Form"] 9 | frappe.ui.form.on(this.frm.doctype, { 10 | refresh(frm) { 11 | if(!ignored_doctype_list.includes(frm.doc.doctype)){ 12 | frm.page.add_menu_item(__('Send via WhatsApp'), function() { send_sms(frm); }); 13 | } 14 | } 15 | }); 16 | } 17 | }); 18 | }); 19 | 20 | function send_sms(frm){ 21 | if(frm.is_dirty()){ 22 | frappe.throw(__('You have unsaved changes. Save before send.')) 23 | } 24 | else { 25 | create_recipients_dailog(frm); 26 | } 27 | } 28 | 29 | function create_recipients_dailog(frm){ 30 | let d = new frappe.ui.Dialog({ 31 | title: frm.doc.doctype + " : " + frm.doc.name, 32 | fields: [ 33 | { 34 | label: __("To"), 35 | fieldtype: "MultiSelect", 36 | reqd: 1, 37 | fieldname: "recipients", 38 | default: frm.doc.whatsapp_number ? frm.doc.whatsapp_number : "", 39 | }, 40 | { 41 | label: __("Message"), 42 | fieldtype: "Small Text", 43 | fieldname: "message", 44 | length: 700 45 | }, 46 | { 47 | label: __("Attach Document Print"), 48 | fieldtype: "Check", 49 | fieldname: "attach_document_print" 50 | }, 51 | { 52 | label: __("Select Print Format"), 53 | fieldtype: "Link", 54 | fieldname: "print_format", 55 | options: "Print Format", 56 | get_query: function () { 57 | return{ 58 | filters: { 59 | 'doc_type': frm.doc.doctype, 60 | 'disabled': 0 61 | } 62 | } 63 | } 64 | } 65 | ], 66 | primary_action_label: __("Send"), 67 | primary_action(values) { 68 | dialog_primary_action(frm, values) 69 | d.hide(); 70 | }, 71 | secondary_action_label: __("Discard"), 72 | secondary_action() { 73 | d.hide(); 74 | }, 75 | size: 'large', 76 | minimizable: true 77 | }); 78 | d.show(); 79 | get_whatsapp_number_list(d) 80 | } 81 | 82 | function get_whatsapp_number_list(d){ 83 | d.fields_dict["recipients"].get_data = () => { 84 | const data = d.fields_dict["recipients"].get_value(); 85 | const txt = data.match(/[^,\s*]*$/)[0] || ''; 86 | frappe.call({ 87 | method: "frappe_meta_integration.whatsapp.utils.get_contact_list", 88 | args: {txt}, 89 | callback: (r) => { 90 | d.fields_dict["recipients"].set_data(r.message); 91 | } 92 | }); 93 | }; 94 | } 95 | 96 | function dialog_primary_action(frm, values){ 97 | frappe.call({ 98 | method: "frappe_meta_integration.whatsapp.utils.send_whatsapp_msg", 99 | args: { 100 | "doctype": frm.doc.doctype, 101 | "docname": frm.doc.name, 102 | "args": values 103 | }, 104 | freeze: true, 105 | freeze_message: ('Sending WhatsApp Message.!!') 106 | }); 107 | } 108 | -------------------------------------------------------------------------------- /frappe_meta_integration/templates/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/efeone/frappe_meta_integration/9e4a95c8eeb46e76b51a77a24391b7d0582e6674/frappe_meta_integration/templates/__init__.py -------------------------------------------------------------------------------- /frappe_meta_integration/templates/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/efeone/frappe_meta_integration/9e4a95c8eeb46e76b51a77a24391b7d0582e6674/frappe_meta_integration/templates/pages/__init__.py -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/efeone/frappe_meta_integration/9e4a95c8eeb46e76b51a77a24391b7d0582e6674/frappe_meta_integration/whatsapp/__init__.py -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/api/webhook.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | from werkzeug.wrappers import Response 3 | from frappe_meta_integration.whatsapp.doctype.whatsapp_communication.whatsapp_communication import ( 4 | create_incoming_whatsapp_message, 5 | update_message_status 6 | ) 7 | 8 | @frappe.whitelist(allow_guest=True) 9 | def handle(): 10 | if frappe.request.method == "GET": 11 | return verify_token_and_fulfill_challenge() 12 | 13 | try: 14 | form_dict = frappe.local.form_dict 15 | messages = form_dict["entry"][0]["changes"][0]["value"].get("messages", []) 16 | statuses = form_dict["entry"][0]["changes"][0]["value"].get("statuses", []) 17 | 18 | for status in statuses: 19 | update_message_status(status) 20 | 21 | for message in messages: 22 | create_incoming_whatsapp_message(message) 23 | 24 | frappe.get_doc( 25 | {"doctype": "WhatsApp Webhook Log", "payload": frappe.as_json(form_dict)} 26 | ).insert(ignore_permissions=True) 27 | frappe.db.commit() 28 | except Exception: 29 | frappe.log_error("WhatsApp Webhook Log Error", frappe.get_traceback()) 30 | 31 | def verify_token_and_fulfill_challenge(): 32 | meta_challenge = frappe.form_dict.get("hub.challenge") 33 | expected_token = frappe.db.get_single_value("WhatsApp Cloud API Settings", "webhook_verify_token") 34 | 35 | if frappe.form_dict.get("hub.verify_token") != expected_token: 36 | frappe.throw("Verify token does not match") 37 | 38 | return Response(meta_challenge, status=200) 39 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/custom/contact.json: -------------------------------------------------------------------------------- 1 | { 2 | "custom_fields": [ 3 | { 4 | "_assign": null, 5 | "_comments": null, 6 | "_liked_by": null, 7 | "_user_tags": null, 8 | "allow_in_quick_entry": 0, 9 | "allow_on_submit": 0, 10 | "bold": 0, 11 | "collapsible": 0, 12 | "collapsible_depends_on": null, 13 | "columns": 0, 14 | "creation": "2022-07-30 12:53:16.037356", 15 | "default": null, 16 | "depends_on": null, 17 | "description": null, 18 | "docstatus": 0, 19 | "dt": "Contact", 20 | "fetch_from": null, 21 | "fetch_if_empty": 0, 22 | "fieldname": "whatsapp_numbers", 23 | "fieldtype": "Table", 24 | "hidden": 0, 25 | "hide_border": 0, 26 | "hide_days": 0, 27 | "hide_seconds": 0, 28 | "idx": 27, 29 | "ignore_user_permissions": 0, 30 | "ignore_xss_filter": 0, 31 | "in_global_search": 0, 32 | "in_list_view": 0, 33 | "in_preview": 0, 34 | "in_standard_filter": 0, 35 | "insert_after": "phone_nos", 36 | "label": "WhatsApp Numbers", 37 | "length": 0, 38 | "mandatory_depends_on": null, 39 | "modified": "2022-07-30 12:53:16.037356", 40 | "modified_by": "Administrator", 41 | "name": "Contact-whatsapp_numbers", 42 | "no_copy": 0, 43 | "non_negative": 0, 44 | "options": "Contact WhatsApp", 45 | "owner": "Administrator", 46 | "parent": null, 47 | "parentfield": null, 48 | "parenttype": null, 49 | "permlevel": 0, 50 | "precision": "", 51 | "print_hide": 0, 52 | "print_hide_if_no_value": 0, 53 | "print_width": null, 54 | "read_only": 0, 55 | "read_only_depends_on": null, 56 | "report_hide": 0, 57 | "reqd": 0, 58 | "search_index": 0, 59 | "translatable": 0, 60 | "unique": 0, 61 | "width": null 62 | }, 63 | { 64 | "_assign": null, 65 | "_comments": null, 66 | "_liked_by": null, 67 | "_user_tags": null, 68 | "allow_in_quick_entry": 0, 69 | "allow_on_submit": 0, 70 | "bold": 0, 71 | "collapsible": 0, 72 | "collapsible_depends_on": null, 73 | "columns": 0, 74 | "creation": "2022-07-30 12:57:20.006856", 75 | "default": null, 76 | "depends_on": null, 77 | "description": null, 78 | "docstatus": 0, 79 | "dt": "Contact", 80 | "fetch_from": null, 81 | "fetch_if_empty": 0, 82 | "fieldname": "whatsapp_number", 83 | "fieldtype": "Data", 84 | "hidden": 0, 85 | "hide_border": 0, 86 | "hide_days": 0, 87 | "hide_seconds": 0, 88 | "idx": 16, 89 | "ignore_user_permissions": 0, 90 | "ignore_xss_filter": 0, 91 | "in_global_search": 0, 92 | "in_list_view": 0, 93 | "in_preview": 0, 94 | "in_standard_filter": 0, 95 | "insert_after": "mobile_no", 96 | "label": "WhatsApp Number", 97 | "length": 0, 98 | "mandatory_depends_on": null, 99 | "modified": "2022-07-30 12:57:20.006856", 100 | "modified_by": "Administrator", 101 | "name": "Contact-whatsapp_number", 102 | "no_copy": 0, 103 | "non_negative": 0, 104 | "options": "Phone", 105 | "owner": "Administrator", 106 | "parent": null, 107 | "parentfield": null, 108 | "parenttype": null, 109 | "permlevel": 0, 110 | "precision": "", 111 | "print_hide": 0, 112 | "print_hide_if_no_value": 0, 113 | "print_width": null, 114 | "read_only": 1, 115 | "read_only_depends_on": null, 116 | "report_hide": 0, 117 | "reqd": 0, 118 | "search_index": 0, 119 | "translatable": 0, 120 | "unique": 0, 121 | "width": null 122 | } 123 | ], 124 | "custom_perms": [], 125 | "doctype": "Contact", 126 | "property_setters": [], 127 | "sync_on_migrate": 1 128 | } 129 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/docevents.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import frappe 4 | from frappe import _ 5 | from frappe_meta_integration.whatsapp.utils import * 6 | 7 | @frappe.whitelist() 8 | def contact_validate(doc, method): 9 | ''' 10 | Validate WhatsApp Number and Setting Primary Number. 11 | ''' 12 | is_primary = [whatsapp_no.whatsapp_number for whatsapp_no in doc.whatsapp_numbers if whatsapp_no.is_primary] 13 | 14 | if len(is_primary) > 1: 15 | frappe.throw( 16 | _("Only one {0} can be set as primary.").format(frappe.bold("WhatsApp Number")) 17 | ) 18 | 19 | primary_whatsapp_number_exists = False 20 | for whatsapp_no in doc.whatsapp_numbers: 21 | if whatsapp_no.whatsapp_number: 22 | validate_whatsapp_number(whatsapp_no.whatsapp_number) 23 | if whatsapp_no.is_primary == 1: 24 | primary_whatsapp_number_exists = True 25 | doc.whatsapp_number = whatsapp_no.whatsapp_number 26 | 27 | if not primary_whatsapp_number_exists: 28 | doc.whatsapp_number = "" 29 | 30 | @frappe.whitelist() 31 | def user_after_insert(doc, method): 32 | ''' 33 | WhatsApp 2 Factor Authentication 34 | ''' 35 | welcome_message_template = frappe.db.get_single_value("WhatsApp Cloud API Settings", "welcome_message_template") 36 | if doc.user_whatsapp_number and welcome_message_template: 37 | template_doc = frappe.get_doc('WhatsApp Message Template', welcome_message_template) 38 | whatsapp_communication_doc = frappe.new_doc("WhatsApp Communication") 39 | whatsapp_communication_doc.to = doc.user_whatsapp_number 40 | whatsapp_communication_doc.message_type = 'Template' 41 | whatsapp_communication_doc.whatsapp_message_template = welcome_message_template 42 | whatsapp_communication_doc.reference_dt = 'User' 43 | whatsapp_communication_doc.reference_dn = doc.name 44 | for template_parameter in template_doc.parameters: 45 | row = whatsapp_communication_doc.append('parameters') 46 | row.parameter = template_parameter.parameter 47 | if template_parameter.parameter == 'user': 48 | row.value = doc.full_name 49 | whatsapp_communication_doc.save() 50 | whatsapp_communication_doc.send_message() 51 | frappe.db.set_value('User', doc.name, 'whatsapp_2f_authenticated', 1) 52 | frappe.db.commit() 53 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/efeone/frappe_meta_integration/9e4a95c8eeb46e76b51a77a24391b7d0582e6674/frappe_meta_integration/whatsapp/doctype/__init__.py -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/contact_whatsapp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/efeone/frappe_meta_integration/9e4a95c8eeb46e76b51a77a24391b7d0582e6674/frappe_meta_integration/whatsapp/doctype/contact_whatsapp/__init__.py -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/contact_whatsapp/contact_whatsapp.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2022-07-30 12:50:24.047165", 5 | "doctype": "DocType", 6 | "editable_grid": 1, 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "whatsapp_number", 10 | "is_primary" 11 | ], 12 | "fields": [ 13 | { 14 | "description": "Whats App Number with country code", 15 | "fieldname": "whatsapp_number", 16 | "fieldtype": "Data", 17 | "in_list_view": 1, 18 | "label": "WhatsApp Number with country code", 19 | "options": "Phone", 20 | "reqd": 1 21 | }, 22 | { 23 | "default": "0", 24 | "fieldname": "is_primary", 25 | "fieldtype": "Check", 26 | "in_list_view": 1, 27 | "label": "Is Primary" 28 | } 29 | ], 30 | "index_web_pages_for_search": 1, 31 | "istable": 1, 32 | "links": [], 33 | "modified": "2022-07-30 12:54:42.018781", 34 | "modified_by": "Administrator", 35 | "module": "WhatsApp", 36 | "name": "Contact WhatsApp", 37 | "owner": "Administrator", 38 | "permissions": [], 39 | "sort_field": "modified", 40 | "sort_order": "DESC" 41 | } -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/contact_whatsapp/contact_whatsapp.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, efeone Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | class ContactWhatsApp(Document): 8 | pass 9 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_campaign/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/efeone/frappe_meta_integration/9e4a95c8eeb46e76b51a77a24391b7d0582e6674/frappe_meta_integration/whatsapp/doctype/whatsapp_campaign/__init__.py -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_campaign/test_whatsapp_campaign.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, efeone Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests.utils import FrappeTestCase 6 | 7 | 8 | class TestWhatsAppCampaign(FrappeTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_campaign/whatsapp_campaign.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, efeone Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on('WhatsApp Campaign', { 5 | setup: function(frm){ 6 | frm.get_field("parameters").grid.cannot_add_rows = true; 7 | frm.refresh_field("parameters"); 8 | frm.set_query('whatsapp_message_template', () => { 9 | return { 10 | filters: { 11 | docstatus: 1 12 | } 13 | } 14 | }); 15 | }, 16 | whatsapp_message_template: function(frm){ 17 | set_template_parameters(frm); 18 | } 19 | }); 20 | 21 | frappe.ui.form.on('WhatsApp Message Template Item', { 22 | parameters_remove: function(frm, cdt, cdn){ 23 | frappe.show_alert({ 24 | message:__('You are not allowed to add/remove parameter Manually!. Changes will be reverted.'), 25 | indicator:'red' 26 | }, 5); 27 | set_template_parameters(frm); 28 | }, 29 | parameters_add: function(frm, cdt, cdn){ 30 | frappe.show_alert({ 31 | message:__('You are not allowed to add/remove parameter Manually!. Changes will be reverted.'), 32 | indicator:'red' 33 | }, 5); 34 | set_template_parameters(frm); 35 | } 36 | }); 37 | 38 | function set_template_parameters(frm){ 39 | if(frm.doc.whatsapp_message_template){ 40 | frappe.call({ 41 | method: 'frappe_meta_integration.whatsapp.doctype.whatsapp_message_template.whatsapp_message_template.set_template_parameters', 42 | args: { 43 | "whatsapp_message_template": frm.doc.whatsapp_message_template 44 | }, 45 | callback: function(r) { 46 | frm.clear_table('parameters'); 47 | for(var i=0; i< r.message.length; i++){ 48 | let row =frm.add_child('parameters',{ 49 | parameter: r.message[i].parameter 50 | }); 51 | } 52 | frm.save() 53 | } 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_campaign/whatsapp_campaign.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "autoname": "naming_series:", 5 | "creation": "2023-02-20 20:59:27.165572", 6 | "default_view": "List", 7 | "doctype": "DocType", 8 | "editable_grid": 1, 9 | "engine": "InnoDB", 10 | "field_order": [ 11 | "naming_series", 12 | "campaign_name", 13 | "campaign_date", 14 | "message_type", 15 | "column_break_dzbea", 16 | "message_body", 17 | "media_information_section", 18 | "media_filename", 19 | "media_caption", 20 | "media_file", 21 | "media_image", 22 | "message_template_information_section", 23 | "whatsapp_message_template", 24 | "parameter_count", 25 | "parameters", 26 | "recipients_section", 27 | "recipients", 28 | "amended_from" 29 | ], 30 | "fields": [ 31 | { 32 | "fieldname": "campaign_name", 33 | "fieldtype": "Data", 34 | "label": "Campaign Name" 35 | }, 36 | { 37 | "fieldname": "campaign_date", 38 | "fieldtype": "Date", 39 | "label": "Campaign Date", 40 | "options": "Today" 41 | }, 42 | { 43 | "fieldname": "message_type", 44 | "fieldtype": "Select", 45 | "label": "message_type", 46 | "options": "Text\nImage\nAudio\nVideo\nDocument\nTemplate" 47 | }, 48 | { 49 | "fieldname": "whatsapp_message_template", 50 | "fieldtype": "Link", 51 | "label": "WhatsApp Message Template", 52 | "options": "WhatsApp Message Template" 53 | }, 54 | { 55 | "default": "WA-CMP-.YYYY.-", 56 | "fieldname": "naming_series", 57 | "fieldtype": "Select", 58 | "hidden": 1, 59 | "label": "naming_series", 60 | "options": "WA-CMP-.YYYY.-" 61 | }, 62 | { 63 | "fieldname": "amended_from", 64 | "fieldtype": "Link", 65 | "label": "Amended From", 66 | "no_copy": 1, 67 | "options": "WhatsApp Campaign", 68 | "print_hide": 1, 69 | "read_only": 1 70 | }, 71 | { 72 | "fieldname": "recipients_section", 73 | "fieldtype": "Section Break", 74 | "label": "Recipients" 75 | }, 76 | { 77 | "fieldname": "recipients", 78 | "fieldtype": "Table", 79 | "label": "Recipients", 80 | "options": "WhatsApp Campaign Recipient", 81 | "reqd": 1 82 | }, 83 | { 84 | "depends_on": "eval:doc.message_type==\"Template\"", 85 | "fieldname": "message_template_information_section", 86 | "fieldtype": "Section Break", 87 | "label": "Message Template Information" 88 | }, 89 | { 90 | "fieldname": "parameter_count", 91 | "fieldtype": "Int", 92 | "hidden": 1, 93 | "in_list_view": 1, 94 | "label": "Parameter Count" 95 | }, 96 | { 97 | "fieldname": "parameters", 98 | "fieldtype": "Table", 99 | "label": "Parameters", 100 | "options": "WhatsApp Message Template Item" 101 | }, 102 | { 103 | "depends_on": "eval:doc.message_type===\"Text\"", 104 | "fieldname": "message_body", 105 | "fieldtype": "Markdown Editor", 106 | "label": "Message Body" 107 | }, 108 | { 109 | "depends_on": "eval:(doc.message_type!=\"Text\" && doc.message_type!=\"Template\")", 110 | "fieldname": "media_information_section", 111 | "fieldtype": "Section Break", 112 | "label": "Media Information" 113 | }, 114 | { 115 | "depends_on": "eval:(doc.message_type == 'Document')", 116 | "fieldname": "media_filename", 117 | "fieldtype": "Data", 118 | "label": "Media Filename" 119 | }, 120 | { 121 | "depends_on": "eval:(doc.message_type == 'Document' || doc.message_type == 'Image')", 122 | "fieldname": "media_caption", 123 | "fieldtype": "Data", 124 | "label": "Media Caption" 125 | }, 126 | { 127 | "fieldname": "media_file", 128 | "fieldtype": "Attach", 129 | "label": "Media File" 130 | }, 131 | { 132 | "depends_on": "eval:doc.type==\"Outgoing\"&&doc.message_type==\"Image\"", 133 | "fieldname": "media_image", 134 | "fieldtype": "Attach Image", 135 | "label": "Media Image" 136 | }, 137 | { 138 | "fieldname": "column_break_dzbea", 139 | "fieldtype": "Column Break" 140 | } 141 | ], 142 | "index_web_pages_for_search": 1, 143 | "is_submittable": 1, 144 | "links": [], 145 | "modified": "2023-03-01 21:40:04.114908", 146 | "modified_by": "Administrator", 147 | "module": "WhatsApp", 148 | "name": "WhatsApp Campaign", 149 | "naming_rule": "By \"Naming Series\" field", 150 | "owner": "Administrator", 151 | "permissions": [ 152 | { 153 | "create": 1, 154 | "delete": 1, 155 | "email": 1, 156 | "export": 1, 157 | "print": 1, 158 | "read": 1, 159 | "report": 1, 160 | "role": "System Manager", 161 | "share": 1, 162 | "submit": 1, 163 | "write": 1 164 | }, 165 | { 166 | "create": 1, 167 | "delete": 1, 168 | "email": 1, 169 | "export": 1, 170 | "print": 1, 171 | "read": 1, 172 | "report": 1, 173 | "role": "Meta Manager", 174 | "share": 1, 175 | "submit": 1, 176 | "write": 1 177 | }, 178 | { 179 | "create": 1, 180 | "email": 1, 181 | "export": 1, 182 | "if_owner": 1, 183 | "print": 1, 184 | "read": 1, 185 | "report": 1, 186 | "role": "Meta User", 187 | "share": 1, 188 | "submit": 1, 189 | "write": 1 190 | } 191 | ], 192 | "sort_field": "modified", 193 | "sort_order": "DESC", 194 | "states": [] 195 | } -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_campaign/whatsapp_campaign.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, efeone Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | from frappe.model.document import Document 6 | 7 | class WhatsAppCampaign(Document): 8 | def validate(self): 9 | self.validate_recepients() 10 | 11 | def on_submit(self): 12 | self.validate_recepients() 13 | self.send_message() 14 | 15 | @frappe.whitelist() 16 | def validate_recepients(self): 17 | if self.recipients: 18 | for recepient in self.recipients: 19 | if not recepient.whatsapp_number: 20 | frappe.throw("Recepient is required in Row {} to send messages".format(recepient.idx)) 21 | else: 22 | frappe.throw("Recepient is required to send messages".format(recepient.idx)) 23 | 24 | @frappe.whitelist() 25 | def send_message(self): 26 | if self.recipients: 27 | created = 0 28 | for recepient in self.recipients: 29 | whatsapp_communication = frappe.new_doc('WhatsApp Communication') 30 | whatsapp_communication.to = recepient.whatsapp_number 31 | whatsapp_communication.message_type = self.message_type 32 | whatsapp_communication.message_body = self.message_body 33 | whatsapp_communication.media_filename = self.media_filename 34 | whatsapp_communication.media_caption = self.media_caption 35 | whatsapp_communication.media_file = self.media_file 36 | whatsapp_communication.media_image = self.media_image 37 | whatsapp_communication.whatsapp_message_template = self.whatsapp_message_template 38 | whatsapp_communication.parameters = self.parameters 39 | whatsapp_communication.reference_dt = self.doctype 40 | whatsapp_communication.reference_dn = self.name 41 | whatsapp_communication.save(ignore_permissions=True) 42 | whatsapp_communication.send_message() 43 | frappe.db.set_value('WhatsApp Campaign Recipient', recepient.name, 'whatsapp_communication', whatsapp_communication.name) 44 | frappe.db.set_value('WhatsApp Campaign Recipient', recepient.name, 'status', whatsapp_communication.status) 45 | created = 1 46 | if created: 47 | frappe.db.commit() 48 | frappe.msgprint("WhatsApp Communications created", alert=True, indicator="green") 49 | self.reload() 50 | else: 51 | frappe.throw("Recepient is required to send messages".format(recepient.idx)) 52 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_campaign_recipient/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/efeone/frappe_meta_integration/9e4a95c8eeb46e76b51a77a24391b7d0582e6674/frappe_meta_integration/whatsapp/doctype/whatsapp_campaign_recipient/__init__.py -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_campaign_recipient/whatsapp_campaign_recipient.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2023-02-20 21:17:12.950291", 5 | "doctype": "DocType", 6 | "editable_grid": 1, 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "whatsapp_number", 10 | "person_name", 11 | "whatsapp_communication", 12 | "status" 13 | ], 14 | "fields": [ 15 | { 16 | "fieldname": "whatsapp_number", 17 | "fieldtype": "Data", 18 | "in_list_view": 1, 19 | "label": "WhatsApp Number", 20 | "options": "Phone", 21 | "reqd": 1 22 | }, 23 | { 24 | "fieldname": "person_name", 25 | "fieldtype": "Data", 26 | "in_list_view": 1, 27 | "label": "Person Name" 28 | }, 29 | { 30 | "allow_on_submit": 1, 31 | "fieldname": "whatsapp_communication", 32 | "fieldtype": "Link", 33 | "in_list_view": 1, 34 | "label": "WhatsApp Communication", 35 | "options": "WhatsApp Communication", 36 | "read_only": 1 37 | }, 38 | { 39 | "allow_on_submit": 1, 40 | "depends_on": "whatsapp_communication", 41 | "fetch_from": "whatsapp_communication.status", 42 | "fieldname": "status", 43 | "fieldtype": "Data", 44 | "in_list_view": 1, 45 | "label": "Status", 46 | "read_only": 1 47 | } 48 | ], 49 | "index_web_pages_for_search": 1, 50 | "istable": 1, 51 | "links": [], 52 | "modified": "2023-02-24 07:37:39.507310", 53 | "modified_by": "Administrator", 54 | "module": "WhatsApp", 55 | "name": "WhatsApp Campaign Recipient", 56 | "owner": "Administrator", 57 | "permissions": [], 58 | "sort_field": "modified", 59 | "sort_order": "DESC", 60 | "states": [] 61 | } -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_campaign_recipient/whatsapp_campaign_recipient.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, efeone Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | class WhatsAppCampaignRecipient(Document): 8 | pass 9 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_cloud_api_settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/efeone/frappe_meta_integration/9e4a95c8eeb46e76b51a77a24391b7d0582e6674/frappe_meta_integration/whatsapp/doctype/whatsapp_cloud_api_settings/__init__.py -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_cloud_api_settings/test_whatsapp_cloud_api_settings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, efeone Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | import unittest 6 | 7 | class TestWhatsAppCloudAPISettings(unittest.TestCase): 8 | pass 9 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_cloud_api_settings/whatsapp_cloud_api_settings.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, efeone Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on('WhatsApp Cloud API Settings', { 5 | refresh: function(frm) { 6 | if(frm.doc.access_token && frm.doc.phone_number_id){ 7 | frm.add_custom_button('Verify Token', () => { 8 | set_primary_action(frm); 9 | }).addClass("btn-primary"); 10 | } 11 | frm.set_query('welcome_message_template', () => { 12 | return { 13 | filters: { 14 | docstatus: 1, 15 | parameter_count: 1 16 | } 17 | } 18 | }) 19 | } 20 | }); 21 | 22 | function set_primary_action(frm){ 23 | frappe.prompt({ 24 | label: 'WhatsApp Number', 25 | fieldname: 'phone_number', 26 | fieldtype: 'Data', 27 | description: 'WhatsApp number with country code(eg: 91)', 28 | reqd: 1 29 | }, (values) => { 30 | send_verify_message(frm, values.phone_number) 31 | }) 32 | } 33 | 34 | function send_verify_message(frm, phone_number){ 35 | frappe.call({ 36 | method: 'frappe_meta_integration.whatsapp.doctype.whatsapp_cloud_api_settings.whatsapp_cloud_api_settings.send_test_message', 37 | args: { 38 | "phone_number": phone_number 39 | }, 40 | callback: function(r) { 41 | if(r && r.message){ 42 | frappe.msgprint({ 43 | title: __('Success'), 44 | message: __("WhatsApp Account successfully configured.
Message ID : {0}",[r.message]) 45 | }); 46 | } 47 | }, 48 | freeze: true, 49 | freeze_message: ('Sending WhatsApp test Message.!!') 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_cloud_api_settings/whatsapp_cloud_api_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2022-06-17 14:44:35.657847", 5 | "doctype": "DocType", 6 | "editable_grid": 1, 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "access_token", 10 | "business_account_id", 11 | "column_break_6", 12 | "phone_number_id", 13 | "webhook_verify_token", 14 | "general_configuration_section", 15 | "welcome_message_template" 16 | ], 17 | "fields": [ 18 | { 19 | "fieldname": "access_token", 20 | "fieldtype": "Password", 21 | "in_list_view": 1, 22 | "label": "Access Token", 23 | "length": 300, 24 | "reqd": 1 25 | }, 26 | { 27 | "fieldname": "business_account_id", 28 | "fieldtype": "Data", 29 | "label": "Business Account ID" 30 | }, 31 | { 32 | "fieldname": "column_break_6", 33 | "fieldtype": "Column Break" 34 | }, 35 | { 36 | "fieldname": "phone_number_id", 37 | "fieldtype": "Data", 38 | "in_list_view": 1, 39 | "label": "Phone Number ID", 40 | "reqd": 1 41 | }, 42 | { 43 | "fieldname": "webhook_verify_token", 44 | "fieldtype": "Data", 45 | "label": "Webhook Verify Token" 46 | }, 47 | { 48 | "collapsible": 1, 49 | "fieldname": "general_configuration_section", 50 | "fieldtype": "Section Break", 51 | "label": "General Configuration" 52 | }, 53 | { 54 | "fieldname": "welcome_message_template", 55 | "fieldtype": "Link", 56 | "label": "Welcome Message Template", 57 | "options": "WhatsApp Message Template" 58 | } 59 | ], 60 | "index_web_pages_for_search": 1, 61 | "issingle": 1, 62 | "links": [], 63 | "modified": "2023-03-01 21:40:41.715380", 64 | "modified_by": "Administrator", 65 | "module": "WhatsApp", 66 | "name": "WhatsApp Cloud API Settings", 67 | "owner": "Administrator", 68 | "permissions": [ 69 | { 70 | "create": 1, 71 | "delete": 1, 72 | "email": 1, 73 | "print": 1, 74 | "read": 1, 75 | "role": "System Manager", 76 | "share": 1, 77 | "write": 1 78 | }, 79 | { 80 | "create": 1, 81 | "email": 1, 82 | "print": 1, 83 | "read": 1, 84 | "role": "Meta Manager", 85 | "share": 1, 86 | "write": 1 87 | }, 88 | { 89 | "email": 1, 90 | "print": 1, 91 | "read": 1, 92 | "role": "Meta User", 93 | "share": 1 94 | } 95 | ], 96 | "sort_field": "modified", 97 | "sort_order": "DESC", 98 | "states": [] 99 | } -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_cloud_api_settings/whatsapp_cloud_api_settings.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, efeone Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | import requests 6 | from frappe.model.document import Document 7 | 8 | class WhatsAppCloudAPISettings(Document): 9 | pass 10 | 11 | def get_access_token(): 12 | return frappe.utils.password.get_decrypted_password( 13 | "WhatsApp Cloud API Settings", "WhatsApp Cloud API Settings", "access_token" 14 | ) 15 | 16 | @frappe.whitelist() 17 | def send_test_message(phone_number): 18 | access_token = get_access_token() 19 | api_base_url = "https://graph.facebook.com/v13.0" 20 | phone_number_id = frappe.db.get_single_value("WhatsApp Cloud API Settings", "phone_number_id") 21 | 22 | endpoint = f"{api_base_url}/{phone_number_id}/messages" 23 | 24 | response_data = { 25 | "messaging_product": "whatsapp", 26 | "recipient_type": "individual", 27 | "to": phone_number, 28 | "type": 'template', 29 | } 30 | response_data["template"] = {"name": 'hello_world', "language": { "code": "en_US" } } 31 | response = requests.post( 32 | endpoint, 33 | json=response_data, 34 | headers={ 35 | "Authorization": "Bearer " + access_token, 36 | "Content-Type": "application/json", 37 | }, 38 | ) 39 | 40 | if response.ok: 41 | return response.json().get("messages")[0]["id"] 42 | else: 43 | frappe.throw(response.json().get("error").get("message")) 44 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_communication/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/efeone/frappe_meta_integration/9e4a95c8eeb46e76b51a77a24391b7d0582e6674/frappe_meta_integration/whatsapp/doctype/whatsapp_communication/__init__.py -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_communication/test_whatsapp_communication.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, efeone Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | import unittest 6 | 7 | class TestWhatsAppCommunication(unittest.TestCase): 8 | pass 9 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_communication/whatsapp_communication.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, efeone Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on('WhatsApp Communication', { 5 | setup: function(frm){ 6 | frm.get_field("parameters").grid.cannot_add_rows = true; 7 | frm.refresh_field("parameters"); 8 | frm.set_query('whatsapp_message_template', () => { 9 | return { 10 | filters: { 11 | docstatus: 1 12 | } 13 | } 14 | }) 15 | if(frm.doc.type == 'Incoming'){ 16 | frm.disable_form(); 17 | } 18 | }, 19 | refresh: function(frm) { 20 | if(!frm.is_new()){ 21 | make_custom_buttons(frm); 22 | } 23 | if (frm.doc.preview_html) { 24 | let wrapper = frm.get_field("preview_html_rendered").$wrapper; 25 | wrapper.html(frm.doc.preview_html); 26 | } 27 | if(frm.doc.type == 'Incoming'){ 28 | frm.disable_form(); 29 | } 30 | }, 31 | whatsapp_message_template: function(frm){ 32 | set_template_parameters(frm); 33 | } 34 | }); 35 | 36 | frappe.ui.form.on('WhatsApp Message Template Item', { 37 | parameters_remove: function(frm, cdt, cdn){ 38 | frappe.show_alert({ 39 | message:__('You are not allowed to add/remove parameter Manually!. Changes will be reverted.'), 40 | indicator:'red' 41 | }, 5); 42 | set_template_parameters(frm); 43 | }, 44 | parameters_add: function(frm, cdt, cdn){ 45 | frappe.show_alert({ 46 | message:__('You are not allowed to add/remove parameter Manually!. Changes will be reverted.'), 47 | indicator:'red' 48 | }, 5); 49 | set_template_parameters(frm); 50 | } 51 | }); 52 | 53 | function make_custom_buttons(frm){ 54 | create_send_msg_button(frm); 55 | create_upload_media_button(frm); 56 | create_download_media_button(frm); 57 | } 58 | 59 | function create_send_msg_button(frm){ 60 | if (!frm.doc.message_id && frm.doc.type =='Outgoing') { 61 | const btn = frm.add_custom_button("Send Message", () => { 62 | frm.call({ 63 | doc: frm.doc, 64 | method: "send_message", 65 | btn, 66 | }) 67 | .then((m) => frm.refresh()); 68 | }); 69 | } 70 | } 71 | 72 | function create_upload_media_button(frm){ 73 | if (frm.doc.type === "Outgoing" && frm.doc.media_file && !frm.doc.media_uploaded) { 74 | const btn = frm.add_custom_button("Upload Attachment File", () => { 75 | frm.call({ 76 | doc: frm.doc, 77 | method: "upload_media", 78 | btn, 79 | }) 80 | .then(() => { 81 | frm.refresh(); 82 | frappe.msgprint({ 83 | title: "Attachment uploaded successfully.", 84 | message: "You can send this message now!", 85 | indicator: "green", 86 | }); 87 | }); 88 | }); 89 | } 90 | } 91 | 92 | function set_template_parameters(frm){ 93 | if(frm.doc.whatsapp_message_template){ 94 | frappe.call({ 95 | method: 'frappe_meta_integration.whatsapp.doctype.whatsapp_message_template.whatsapp_message_template.set_template_parameters', 96 | args: { 97 | "whatsapp_message_template": frm.doc.whatsapp_message_template 98 | }, 99 | callback: function(r) { 100 | frm.clear_table('parameters'); 101 | for(var i=0; i< r.message.length; i++){ 102 | let row =frm.add_child('parameters',{ 103 | parameter: r.message[i].parameter 104 | }); 105 | } 106 | frm.save() 107 | } 108 | }); 109 | } 110 | } 111 | 112 | function create_download_media_button(frm){ 113 | if ( 114 | frm.doc.type === "Incoming" && 115 | ["Image", "Video", "Audio", "Document"].includes(frm.doc.message_type) && 116 | !frm.doc.media_file 117 | ) { 118 | const btn = frm.add_custom_button("Download Attachment File", () => { 119 | frm 120 | .call({ 121 | doc: frm.doc, 122 | method: "download_media", 123 | btn, 124 | }) 125 | .then((data) => { 126 | const file = data.message; 127 | frm.refresh(); 128 | frappe.msgprint({ 129 | title: "Attachment downloaded successfully.", 130 | message: `Attachment File: ${file.file_name}`, 131 | indicator: "green", 132 | }); 133 | }); 134 | }); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_communication/whatsapp_communication.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "autoname": "naming_series:", 4 | "creation": "2022-06-17 14:47:49.008557", 5 | "doctype": "DocType", 6 | "editable_grid": 1, 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "image_preview", 10 | "preview_html_rendered", 11 | "preview_html", 12 | "section_break_3", 13 | "status", 14 | "from_no", 15 | "to", 16 | "message_type", 17 | "column_break_3", 18 | "type", 19 | "message_id", 20 | "message_body", 21 | "media_information_section", 22 | "media_id", 23 | "media_hash", 24 | "media_mime_type", 25 | "column_break_9", 26 | "media_filename", 27 | "media_caption", 28 | "media_file", 29 | "media_uploaded", 30 | "media_image", 31 | "naming_series", 32 | "message_template_information_section", 33 | "whatsapp_message_template", 34 | "template_language", 35 | "parameter_count", 36 | "is_welcome_message", 37 | "header_has_media", 38 | "media_type", 39 | "header_media", 40 | "parameters", 41 | "references_section", 42 | "reference_dt", 43 | "reference_dn" 44 | ], 45 | "fields": [ 46 | { 47 | "depends_on": "eval:(doc.message_type==\"Image\"&&doc.media_file)", 48 | "fieldname": "image_preview", 49 | "fieldtype": "Image", 50 | "label": "Image Preview", 51 | "options": "media_image" 52 | }, 53 | { 54 | "depends_on": "eval:doc.message_type==\"Audio\"||doc.message_type==\"Video\"", 55 | "fieldname": "preview_html_rendered", 56 | "fieldtype": "HTML", 57 | "label": "Preview HTML Rendered" 58 | }, 59 | { 60 | "fieldname": "preview_html", 61 | "fieldtype": "HTML Editor", 62 | "hidden": 1, 63 | "label": "Preview HTML" 64 | }, 65 | { 66 | "fieldname": "section_break_3", 67 | "fieldtype": "Section Break" 68 | }, 69 | { 70 | "default": "Pending", 71 | "fieldname": "status", 72 | "fieldtype": "Select", 73 | "in_list_view": 1, 74 | "label": "Status", 75 | "options": "Pending\nSent\nDelivered\nRead\nReceived\nMarked As Seen", 76 | "read_only": 1 77 | }, 78 | { 79 | "depends_on": "eval:doc.type===\"Incoming\"", 80 | "fieldname": "from_no", 81 | "fieldtype": "Data", 82 | "label": "From" 83 | }, 84 | { 85 | "depends_on": "eval:doc.type===\"Outgoing\"", 86 | "fieldname": "to", 87 | "fieldtype": "Data", 88 | "label": "To", 89 | "read_only_depends_on": "message_id" 90 | }, 91 | { 92 | "fieldname": "message_type", 93 | "fieldtype": "Select", 94 | "in_list_view": 1, 95 | "label": "Message Type", 96 | "options": "Text\nImage\nAudio\nVideo\nDocument\nTemplate", 97 | "read_only_depends_on": "message_id" 98 | }, 99 | { 100 | "fieldname": "column_break_3", 101 | "fieldtype": "Column Break" 102 | }, 103 | { 104 | "default": "Outgoing", 105 | "fieldname": "type", 106 | "fieldtype": "Select", 107 | "label": "Type", 108 | "options": "Incoming\nOutgoing", 109 | "read_only": 1 110 | }, 111 | { 112 | "fieldname": "message_id", 113 | "fieldtype": "Data", 114 | "in_list_view": 1, 115 | "label": "Message ID", 116 | "read_only": 1, 117 | "unique": 1 118 | }, 119 | { 120 | "depends_on": "eval:doc.message_type===\"Text\"", 121 | "fieldname": "message_body", 122 | "fieldtype": "Markdown Editor", 123 | "label": "Message Body", 124 | "read_only_depends_on": "message_id" 125 | }, 126 | { 127 | "depends_on": "eval:(doc.message_type!=\"Text\" && doc.message_type!=\"Template\")", 128 | "fieldname": "media_information_section", 129 | "fieldtype": "Section Break", 130 | "label": "Media Information" 131 | }, 132 | { 133 | "fieldname": "media_id", 134 | "fieldtype": "Data", 135 | "label": "Media ID", 136 | "read_only": 1 137 | }, 138 | { 139 | "fieldname": "media_hash", 140 | "fieldtype": "Data", 141 | "label": "Media Hash", 142 | "read_only": 1 143 | }, 144 | { 145 | "fieldname": "media_mime_type", 146 | "fieldtype": "Data", 147 | "label": "Media MIME Type", 148 | "read_only": 1 149 | }, 150 | { 151 | "fieldname": "column_break_9", 152 | "fieldtype": "Column Break" 153 | }, 154 | { 155 | "depends_on": "eval:(doc.message_type == 'Document')", 156 | "fieldname": "media_filename", 157 | "fieldtype": "Data", 158 | "label": "Media Filename", 159 | "read_only_depends_on": "message_id" 160 | }, 161 | { 162 | "depends_on": "eval:(doc.message_type == 'Document' || doc.message_type == 'Image')", 163 | "fieldname": "media_caption", 164 | "fieldtype": "Data", 165 | "label": "Media Caption", 166 | "read_only_depends_on": "message_id" 167 | }, 168 | { 169 | "depends_on": "eval:!(doc.type==\"Outgoing\"&&doc.message_type==\"Image\")", 170 | "fieldname": "media_file", 171 | "fieldtype": "Attach", 172 | "label": "Media File" 173 | }, 174 | { 175 | "depends_on": "eval: (doc.message_type==\"Image\")", 176 | "fieldname": "media_image", 177 | "fieldtype": "Attach Image", 178 | "label": "Media Image", 179 | "read_only_depends_on": "media_uploaded" 180 | }, 181 | { 182 | "default": "WA-MSG-.YYYY.-", 183 | "fieldname": "naming_series", 184 | "fieldtype": "Select", 185 | "hidden": 1, 186 | "label": "Series", 187 | "options": "WA-MSG-.YYYY.-", 188 | "read_only": 1, 189 | "reqd": 1 190 | }, 191 | { 192 | "fieldname": "whatsapp_message_template", 193 | "fieldtype": "Link", 194 | "label": "WhatsApp Message Template", 195 | "options": "WhatsApp Message Template", 196 | "read_only_depends_on": "message_id" 197 | }, 198 | { 199 | "depends_on": "eval:doc.message_type==\"Template\"", 200 | "fieldname": "message_template_information_section", 201 | "fieldtype": "Section Break", 202 | "label": "Message Template Information" 203 | }, 204 | { 205 | "fetch_from": "whatsapp_message_template.language_code", 206 | "fieldname": "template_language", 207 | "fieldtype": "Data", 208 | "label": "Template Language", 209 | "read_only": 1 210 | }, 211 | { 212 | "depends_on": "parameter_count", 213 | "fieldname": "parameters", 214 | "fieldtype": "Table", 215 | "label": "Parameters", 216 | "options": "WhatsApp Message Template Item", 217 | "read_only_depends_on": "message_id" 218 | }, 219 | { 220 | "fetch_from": "whatsapp_message_template.parameter_count", 221 | "fieldname": "parameter_count", 222 | "fieldtype": "Int", 223 | "hidden": 1, 224 | "label": "Parameter Count", 225 | "read_only": 1 226 | }, 227 | { 228 | "default": "0", 229 | "fieldname": "is_welcome_message", 230 | "fieldtype": "Check", 231 | "hidden": 1, 232 | "label": "Is Welcome Message", 233 | "read_only": 1 234 | }, 235 | { 236 | "collapsible": 1, 237 | "fieldname": "references_section", 238 | "fieldtype": "Section Break", 239 | "label": "References" 240 | }, 241 | { 242 | "fieldname": "reference_dt", 243 | "fieldtype": "Link", 244 | "label": "Reference Dt", 245 | "options": "DocType", 246 | "read_only": 1 247 | }, 248 | { 249 | "depends_on": "reference_dt", 250 | "fieldname": "reference_dn", 251 | "fieldtype": "Dynamic Link", 252 | "label": "Reference Dn", 253 | "options": "reference_dt", 254 | "read_only": 1 255 | }, 256 | { 257 | "default": "0", 258 | "fetch_from": "whatsapp_message_template.header_has_media", 259 | "fieldname": "header_has_media", 260 | "fieldtype": "Check", 261 | "hidden": 1, 262 | "label": "Header Has Media", 263 | "read_only": 1 264 | }, 265 | { 266 | "depends_on": "header_has_media", 267 | "fetch_from": "whatsapp_message_template.media_type", 268 | "fieldname": "media_type", 269 | "fieldtype": "Select", 270 | "label": "Media Type", 271 | "options": "document\nimage\nvideo" 272 | }, 273 | { 274 | "depends_on": "header_has_media", 275 | "description": "This attachment should be in the above Media Type format", 276 | "fieldname": "header_media", 277 | "fieldtype": "Attach", 278 | "label": "Header Media Attachment" 279 | }, 280 | { 281 | "default": "0", 282 | "fieldname": "media_uploaded", 283 | "fieldtype": "Check", 284 | "hidden": 1, 285 | "label": "Media Uploaded", 286 | "read_only": 1 287 | } 288 | ], 289 | "image_field": "media_image", 290 | "index_web_pages_for_search": 1, 291 | "links": [], 292 | "make_attachments_public": 1, 293 | "modified": "2023-03-18 21:46:25.554995", 294 | "modified_by": "Administrator", 295 | "module": "WhatsApp", 296 | "name": "WhatsApp Communication", 297 | "naming_rule": "By \"Naming Series\" field", 298 | "owner": "Administrator", 299 | "permissions": [ 300 | { 301 | "create": 1, 302 | "delete": 1, 303 | "email": 1, 304 | "export": 1, 305 | "print": 1, 306 | "read": 1, 307 | "report": 1, 308 | "role": "System Manager", 309 | "share": 1, 310 | "write": 1 311 | }, 312 | { 313 | "create": 1, 314 | "delete": 1, 315 | "email": 1, 316 | "export": 1, 317 | "print": 1, 318 | "read": 1, 319 | "report": 1, 320 | "role": "Meta Manager", 321 | "share": 1, 322 | "write": 1 323 | }, 324 | { 325 | "create": 1, 326 | "email": 1, 327 | "export": 1, 328 | "if_owner": 1, 329 | "print": 1, 330 | "read": 1, 331 | "report": 1, 332 | "role": "Meta User", 333 | "share": 1, 334 | "write": 1 335 | } 336 | ], 337 | "sort_field": "modified", 338 | "sort_order": "DESC", 339 | "states": [] 340 | } 341 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_communication/whatsapp_communication.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, efeone Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | import requests 6 | import mimetypes 7 | from typing import Dict 8 | from six import string_types 9 | 10 | from frappe.model.document import Document 11 | 12 | class WhatsAppCommunication(Document): 13 | def validate(self): 14 | self.validate_image_attachment() 15 | self.validate_mandatory() 16 | self.validate_template() 17 | 18 | if self.message_type == "Audio" and self.media_file: 19 | self.preview_html = f""" 20 | 24 | """ 25 | 26 | if self.message_type == "Video" and self.media_file: 27 | self.preview_html = f""" 28 | 32 | """ 33 | 34 | def validate_image_attachment(self): 35 | if self.media_image: 36 | self.media_file = self.media_image 37 | if self.media_file and self.message_type == "Image": 38 | self.media_image = self.media_file 39 | 40 | def validate_mandatory(self): 41 | if self.message_type == "Text" and not self.message_body: 42 | frappe.throw("Message Body is required for type Text.") 43 | if self.message_type == "Template" and not self.whatsapp_message_template: 44 | frappe.throw("Message Template is required for type Template.") 45 | 46 | def validate_parameters(self): 47 | if self.message_type == "Template": 48 | for parameter in self.parameters: 49 | if not parameter.value: 50 | frappe.throw('Parameter Value is Missing for {0} at row : {1}.'.format(parameter.parameter, parameter.idx)) 51 | 52 | def validate_template(self): 53 | if self.message_type == "Template": 54 | if self.parameter_count: 55 | if self.parameter_count != len(self.parameters): 56 | frappe.throw("Parameter count and given number of parameters doesn't match!") 57 | 58 | def get_access_token(self): 59 | return frappe.utils.password.get_decrypted_password( 60 | "WhatsApp Cloud API Settings", "WhatsApp Cloud API Settings", "access_token" 61 | ) 62 | 63 | def validate_header_media(self): 64 | if self.message_type == "Template" and self.whatsapp_message_template: 65 | if self.header_has_media: 66 | if not self.header_media: 67 | frappe.throw("`header_media` is required in selected Template.") 68 | 69 | @frappe.whitelist() 70 | def send_message(self): 71 | if not self.to: 72 | frappe.throw("Recepient (`to`) is required to send message.") 73 | 74 | access_token = self.get_access_token() 75 | 76 | api_base_url = "https://graph.facebook.com/v13.0" 77 | phone_number_id = frappe.db.get_single_value("WhatsApp Cloud API Settings", "phone_number_id") 78 | 79 | endpoint = f"{api_base_url}/{phone_number_id}/messages" 80 | 81 | response_data = { 82 | "messaging_product": "whatsapp", 83 | "recipient_type": "individual", 84 | "to": self.to, 85 | "type": self.message_type.lower(), 86 | } 87 | 88 | if self.message_type == "Text": 89 | response_data["text"] = {"preview_url": False, "body": self.message_body} 90 | 91 | if self.message_type in ("Audio", "Image", "Video", "Document"): 92 | if not self.media_id: 93 | frappe.throw("Please attach and upload the media before sending this message.") 94 | 95 | response_data[self.message_type.lower()] = { 96 | "id": self.media_id, 97 | } 98 | 99 | if self.message_type == "Image": 100 | response_data[self.message_type.lower()]["caption"] = self.media_caption 101 | 102 | if self.message_type == "Document": 103 | response_data[self.message_type.lower()]["filename"] = self.media_filename 104 | response_data[self.message_type.lower()]["caption"] = self.media_caption 105 | 106 | if self.message_type == "Template": 107 | self.validate_parameters() 108 | self.validate_header_media() 109 | body_parameters = [] 110 | for parameter in self.parameters: 111 | body_parameters.append({ 112 | "type": "text", 113 | "text": parameter.value 114 | }) 115 | if self.header_has_media: 116 | if self.header_media: 117 | media_file_path = frappe.utils.get_url() 118 | media_file_path += self.header_media 119 | headers_parameters = [] 120 | if self.media_type == 'image': 121 | headers_parameters.append({ 122 | "type": "image", 123 | "image": { 124 | "link": media_file_path 125 | } 126 | }) 127 | if self.media_type == 'document': 128 | headers_parameters.append({ 129 | "type": "document", 130 | "document": { 131 | "link": media_file_path 132 | } 133 | }) 134 | if self.media_type == 'video': 135 | headers_parameters.append({ 136 | "type": "video", 137 | "video": { 138 | "link": media_file_path 139 | } 140 | }) 141 | components_dict = [ 142 | { 143 | "type": "header", 144 | "parameters": headers_parameters 145 | }, 146 | { 147 | "type": "body", 148 | "parameters": body_parameters 149 | } 150 | ] 151 | else: 152 | components_dict = [ 153 | { 154 | "type": "body", 155 | "parameters": body_parameters 156 | } 157 | ] 158 | response_data["template"] = {"name": self.whatsapp_message_template, "language": { "code": self.template_language }, "components":components_dict } 159 | 160 | response = requests.post( 161 | endpoint, 162 | json=response_data, 163 | headers={ 164 | "Authorization": "Bearer " + access_token, 165 | "Content-Type": "application/json", 166 | }, 167 | ) 168 | 169 | if response.ok: 170 | self.message_id = response.json().get("messages")[0]["id"] 171 | self.status = "Sent" 172 | self.save(ignore_permissions=True) 173 | if self.is_welcome_message: 174 | frappe.msgprint(("Welcome Message sent to {0} ").format(self.to), alert=True, indicator="green") 175 | else: 176 | if self.message_type not in ("Audio", "Image", "Video", "Document"): 177 | frappe.msgprint(("WhatsApp Message sent to {0} ").format(self.to), alert=True, indicator="green") 178 | else: 179 | frappe.msgprint(("Attachment sent to {0} ").format(self.to), alert=True, indicator="green") 180 | return response.json() 181 | else: 182 | frappe.throw(response.json().get("error").get("message")) 183 | 184 | @frappe.whitelist() 185 | def upload_media(self): 186 | if not self.media_file: 187 | frappe.throw("`media_file` is required to upload media.") 188 | 189 | media_file_path = frappe.get_doc("File", {"file_url": self.media_file}).get_full_path() 190 | access_token = self.get_access_token() 191 | api_base_url = "https://graph.facebook.com/v13.0" 192 | phone_number_id = frappe.db.get_single_value("WhatsApp Cloud API Settings", "phone_number_id") 193 | 194 | if not self.media_mime_type: 195 | self.media_mime_type = mimetypes.guess_type(self.media_file)[0] 196 | 197 | # Way to send multi-part form data 198 | # Ref: https://stackoverflow.com/a/35974071 199 | form_data = { 200 | "file": ( 201 | "file", 202 | open(media_file_path, "rb"), 203 | self.media_mime_type, 204 | ), 205 | "messaging_product": (None, "whatsapp"), 206 | "type": (None, self.media_mime_type), 207 | } 208 | response = requests.post( 209 | f"{api_base_url}/{phone_number_id}/media", 210 | files=form_data, 211 | headers={ 212 | "Authorization": "Bearer " + access_token, 213 | }, 214 | ) 215 | 216 | if response.ok: 217 | self.media_id = response.json().get("id") 218 | self.media_uploaded = True 219 | self.save(ignore_permissions=True) 220 | else: 221 | frappe.throw(response.json().get("error").get("message")) 222 | 223 | @classmethod 224 | def send_whatsapp_message(self, receiver_list, message, doctype, docname, media=None, file_name=None): 225 | if isinstance(receiver_list, string_types): 226 | if not isinstance(receiver_list, list): 227 | receiver_list = [receiver_list] 228 | 229 | for rec in receiver_list: 230 | """ 231 | Iterate receiver_list and send message to each recepient 232 | """ 233 | self.create_whatsapp_message(rec, message, doctype, docname) #For Text Message or Caption for documents 234 | if media and file_name: 235 | self.create_whatsapp_message(rec, message, doctype, docname, media, file_name) #For Document 236 | 237 | def create_whatsapp_message(to, message, doctype=None, docname=None, media=None, file_name=None): 238 | """ 239 | Create WhatsApp Communication with given data. 240 | """ 241 | wa_msg = frappe.new_doc('WhatsApp Communication') 242 | wa_msg.to = to 243 | wa_msg.reference_dt = doctype 244 | wa_msg.reference_dn = docname 245 | if media: 246 | wa_msg.message_type = "Document" 247 | wa_msg.media_filename = file_name 248 | wa_msg.media_file = media 249 | else: 250 | wa_msg.message_type = "Text" 251 | wa_msg.message_body = message 252 | wa_msg.save(ignore_permissions=True) 253 | if media and file_name: 254 | wa_msg.upload_media() #Upload Attachment 255 | wa_msg.send_message() #Send Attachment/Text Message 256 | 257 | def get_media_url(self): 258 | if not self.media_id: 259 | frappe.throw("`media_id` is missing.") 260 | 261 | api_base = "https://graph.facebook.com/v13.0" 262 | access_token = self.get_access_token() 263 | response = requests.get( 264 | f"{api_base}/{self.media_id}", 265 | headers={ 266 | "Authorization": "Bearer " + access_token, 267 | }, 268 | ) 269 | 270 | if not response.ok: 271 | frappe.throw("Error fetching media URL") 272 | 273 | return response.json().get("url") 274 | 275 | @frappe.whitelist() 276 | def download_media(self) -> Dict: 277 | url = self.get_media_url() 278 | access_token = self.get_access_token() 279 | response = requests.get( 280 | url, 281 | headers={ 282 | "Authorization": "Bearer " + access_token, 283 | }, 284 | ) 285 | 286 | file_name = get_media_extention(self, response.headers.get("Content-Type")) 287 | file_doc = frappe.get_doc( 288 | { 289 | "doctype": "File", 290 | "file_name": file_name, 291 | "content": response.content, 292 | "attached_to_doctype": "WhatsApp Communication", 293 | "attached_to_name": self.name, 294 | "attached_to_field": "media_file", 295 | } 296 | ).insert(ignore_permissions=True) 297 | frappe.db.commit() 298 | 299 | self.set("media_file", file_doc.file_url) 300 | 301 | # Will be used to display image preview 302 | if self.message_type == "Image": 303 | self.set("media_image", file_doc.file_url) 304 | 305 | self.save() 306 | 307 | return file_doc.as_dict() 308 | 309 | @frappe.whitelist() 310 | def update_message_status(status: Dict): 311 | ''' Method to updtae status of Message ''' 312 | message_id = status.get("id") 313 | status = status.get("status") 314 | 315 | if frappe.db.exists('WhatsApp Communication', {"message_id": message_id}): 316 | frappe.db.set_value( 317 | "WhatsApp Communication", {"message_id": message_id}, "status", status.title() 318 | ) 319 | frappe.db.commit() 320 | 321 | @frappe.whitelist() 322 | def create_incoming_whatsapp_message(message: Dict): 323 | ''' Method to create Incoming messages via webhook ''' 324 | MEDIA_TYPES = ("image", "sticker", "document", "audio", "video") 325 | message_type = message.get("type") 326 | message_data = frappe._dict( 327 | { 328 | "doctype": "WhatsApp Communication", 329 | "type": "Incoming", 330 | "status": "Received", 331 | "from_no": message.get("from"), 332 | "message_id": message.get("id"), 333 | "message_type": message_type.title(), 334 | } 335 | ) 336 | 337 | if message_type == "text": 338 | message_data["message_body"] = message.get("text").get("body") 339 | elif message_type in MEDIA_TYPES: 340 | message_data["media_id"] = message.get(message_type).get("id") 341 | message_data["media_mime_type"] = message.get(message_type).get("mime_type") 342 | message_data["media_hash"] = message.get(message_type).get("sha256") 343 | 344 | if message_type == "document": 345 | message_data["media_filename"] = message.get("document").get("filename") 346 | message_data["media_caption"] = message.get("document").get("caption") 347 | 348 | if message_type == "image" or message_type == "video": 349 | message_data["media_caption"] = message.get(message_type).get("caption") 350 | 351 | message_doc = frappe.get_doc(message_data).insert(ignore_permissions=True) 352 | frappe.db.commit() 353 | 354 | def get_media_extention(message_doc, content_type): 355 | return message_doc.media_filename or ( 356 | "attachment_." + content_type.split(";")[0].split("/")[1] 357 | ) 358 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_message_template/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/efeone/frappe_meta_integration/9e4a95c8eeb46e76b51a77a24391b7d0582e6674/frappe_meta_integration/whatsapp/doctype/whatsapp_message_template/__init__.py -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_message_template/test_whatsapp_message_template.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, efeone Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | import unittest 6 | 7 | class TestWhatsAppMessageTemplate(unittest.TestCase): 8 | pass 9 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_message_template/whatsapp_message_template.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, efeone Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on('WhatsApp Message Template', { 5 | refresh: function(frm) { 6 | if(frm.doc.name == 'welcome_message'){ 7 | make_feilds_readonly(frm); 8 | } 9 | if(frm.doc.docstatus==1){ 10 | $(".btn-default").hide(); 11 | } 12 | }, 13 | add_variable: function(frm){ 14 | add_variable_popup(frm); 15 | } 16 | }); 17 | 18 | let make_feilds_readonly=function(frm){ 19 | $(".btn-default").hide(); 20 | frm.set_df_property('category', 'read_only', 1); 21 | frm.set_df_property('parameters', 'read_only', 1); 22 | frm.refresh_fields() 23 | } 24 | 25 | let add_variable_popup = function(frm){ 26 | let add_variable_dialog = new frappe.ui.Dialog({ 27 | title: 'Add variable', 28 | fields: [ 29 | { 30 | label: 'Variable Name', 31 | fieldname: 'variable_name', 32 | fieldtype: 'Data', 33 | reqd: '1' 34 | } 35 | ], 36 | primary_action_label: 'Submit', 37 | primary_action(res) { 38 | frm.set_value('parameter_count', frm.doc.parameter_count + 1 ) 39 | frm.set_value('template', frm.doc.template + ' {{'+ frm.doc.parameter_count +'}} ') 40 | let row =frm.add_child('parameters',{ 41 | parameter: res.variable_name 42 | }); 43 | frm.refresh_fields() 44 | add_variable_dialog.hide(); 45 | } 46 | }); 47 | add_variable_dialog.show(); 48 | } 49 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_message_template/whatsapp_message_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "autoname": "field:template_name", 4 | "creation": "2022-06-30 11:23:25.726343", 5 | "doctype": "DocType", 6 | "editable_grid": 1, 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "template_name", 10 | "id", 11 | "template", 12 | "add_variable", 13 | "parameter_count", 14 | "column_break_7", 15 | "language", 16 | "language_code", 17 | "category", 18 | "header_has_media", 19 | "media_type", 20 | "header", 21 | "footer", 22 | "is_existing_template", 23 | "parameters_section", 24 | "parameters", 25 | "amended_from" 26 | ], 27 | "fields": [ 28 | { 29 | "description": "For Existing Template you should need to keep name as it is in Whats App Cloud API", 30 | "fieldname": "template_name", 31 | "fieldtype": "Data", 32 | "in_list_view": 1, 33 | "in_standard_filter": 1, 34 | "label": "Template Name", 35 | "reqd": 1, 36 | "unique": 1 37 | }, 38 | { 39 | "depends_on": "parameter_count", 40 | "fieldname": "parameters", 41 | "fieldtype": "Table", 42 | "label": "Parameters", 43 | "mandatory_depends_on": "parameter_count", 44 | "options": "WhatsApp Message Template Item" 45 | }, 46 | { 47 | "fieldname": "language_code", 48 | "fieldtype": "Data", 49 | "in_list_view": 1, 50 | "label": "Language Code", 51 | "read_only": 1 52 | }, 53 | { 54 | "default": "0", 55 | "fieldname": "parameter_count", 56 | "fieldtype": "Int", 57 | "label": "Parameter Count", 58 | "read_only": 1 59 | }, 60 | { 61 | "fieldname": "column_break_7", 62 | "fieldtype": "Column Break" 63 | }, 64 | { 65 | "fieldname": "parameters_section", 66 | "fieldtype": "Section Break", 67 | "label": "Parameters" 68 | }, 69 | { 70 | "fieldname": "amended_from", 71 | "fieldtype": "Link", 72 | "label": "Amended From", 73 | "no_copy": 1, 74 | "options": "WhatsApp Message Template", 75 | "print_hide": 1, 76 | "read_only": 1 77 | }, 78 | { 79 | "allow_on_submit": 1, 80 | "fieldname": "id", 81 | "fieldtype": "Data", 82 | "label": "ID", 83 | "read_only": 1 84 | }, 85 | { 86 | "fieldname": "template", 87 | "fieldtype": "Code", 88 | "label": "Template", 89 | "options": "HTML" 90 | }, 91 | { 92 | "fieldname": "language", 93 | "fieldtype": "Link", 94 | "label": "Language", 95 | "options": "Language", 96 | "reqd": 1 97 | }, 98 | { 99 | "fieldname": "category", 100 | "fieldtype": "Select", 101 | "label": "Category", 102 | "options": "\nUTILITY\nMARKETING\nAUTHENTICATION", 103 | "reqd": 1 104 | }, 105 | { 106 | "depends_on": "eval: doc.header_has_media == 0", 107 | "fieldname": "header", 108 | "fieldtype": "Data", 109 | "label": "Header" 110 | }, 111 | { 112 | "fieldname": "footer", 113 | "fieldtype": "Data", 114 | "label": "Footer" 115 | }, 116 | { 117 | "fieldname": "add_variable", 118 | "fieldtype": "Button", 119 | "label": "Add Variable" 120 | }, 121 | { 122 | "default": "0", 123 | "description": "For Existing Template in WhatsApp Cloud API", 124 | "fieldname": "is_existing_template", 125 | "fieldtype": "Check", 126 | "label": "Is Existing Template" 127 | }, 128 | { 129 | "depends_on": "header_has_media", 130 | "fieldname": "media_type", 131 | "fieldtype": "Select", 132 | "label": "Media Type", 133 | "mandatory_depends_on": "header_has_media", 134 | "options": "document\nimage\nvideo" 135 | }, 136 | { 137 | "default": "0", 138 | "fieldname": "header_has_media", 139 | "fieldtype": "Check", 140 | "label": "Header Has Media" 141 | } 142 | ], 143 | "index_web_pages_for_search": 1, 144 | "is_submittable": 1, 145 | "links": [], 146 | "modified": "2024-01-16 12:09:14.455727", 147 | "modified_by": "Administrator", 148 | "module": "WhatsApp", 149 | "name": "WhatsApp Message Template", 150 | "naming_rule": "By fieldname", 151 | "owner": "Administrator", 152 | "permissions": [ 153 | { 154 | "cancel": 1, 155 | "create": 1, 156 | "delete": 1, 157 | "email": 1, 158 | "export": 1, 159 | "print": 1, 160 | "read": 1, 161 | "report": 1, 162 | "role": "System Manager", 163 | "share": 1, 164 | "submit": 1, 165 | "write": 1 166 | }, 167 | { 168 | "email": 1, 169 | "export": 1, 170 | "print": 1, 171 | "report": 1, 172 | "role": "Meta User", 173 | "select": 1, 174 | "share": 1 175 | }, 176 | { 177 | "create": 1, 178 | "delete": 1, 179 | "email": 1, 180 | "export": 1, 181 | "print": 1, 182 | "read": 1, 183 | "report": 1, 184 | "role": "Meta Manager", 185 | "select": 1, 186 | "share": 1, 187 | "submit": 1, 188 | "write": 1 189 | } 190 | ], 191 | "sort_field": "modified", 192 | "sort_order": "DESC", 193 | "states": [], 194 | "title_field": "template_name" 195 | } -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_message_template/whatsapp_message_template.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, efeone Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | import frappe 5 | import requests 6 | from frappe.model.document import Document 7 | from frappe_meta_integration.whatsapp.utils import* 8 | 9 | class WhatsAppMessageTemplate(Document): 10 | def set_template_name_and_codes(self): 11 | """Set template code.""" 12 | self.template_name = self.template_name.lower().replace(' ', '_') 13 | self.language_code = frappe.db.get_value( 14 | "Language", self.language 15 | ).replace('-', '_') 16 | 17 | def autoname(self): 18 | self.set_template_name_and_codes() 19 | self.name = self.template_name 20 | 21 | def before_save(self): 22 | self.set_template_name_and_codes() 23 | 24 | def validate(self): 25 | self.set_template_name_and_codes() 26 | if self.parameter_count: 27 | if self.parameter_count != len(self.parameters): 28 | frappe.throw("Parameter count and given number of parameters doesn't match!") 29 | 30 | def on_submit(self): 31 | if not self.is_existing_template: 32 | self.set_template_name_and_codes() 33 | access_token = get_access_token() 34 | response_data = { 35 | "name": self.template_name, 36 | "language": self.language_code, 37 | "category": self.category, 38 | "components": [{ 39 | "type": "BODY", 40 | "text": self.template 41 | }] 42 | } 43 | if self.header: 44 | response_data['components'].append({ 45 | "type": "HEADER", 46 | "format": "TEXT", 47 | "text": self.header 48 | }) 49 | if self.footer: 50 | response_data['components'].append({ 51 | "type": "FOOTER", 52 | "text": self.footer 53 | }) 54 | 55 | #Setting Action Button for quick replay 56 | if self.template_name and self.template_name=='welcome_message': 57 | response_data['components'].append({ 58 | "type" : "BUTTONS", 59 | "buttons": [{ 60 | "type": "QUICK_REPLY", 61 | "text": "Yes" 62 | }] 63 | }) 64 | 65 | api_base_url = "https://graph.facebook.com/v13.0" 66 | business_account_id = frappe.db.get_single_value("WhatsApp Cloud API Settings", "business_account_id") 67 | endpoint = f"{api_base_url}/{business_account_id}/message_templates" 68 | response = requests.post( 69 | endpoint, 70 | json=response_data, 71 | headers={ 72 | "Authorization": "Bearer " + access_token, 73 | "Content-Type": "application/json", 74 | }, 75 | ) 76 | if response.ok: 77 | frappe.db.set_value("WhatsApp Message Template", self.name, "id", response.json().get("id")) 78 | frappe.db.commit() 79 | self.reload() 80 | else: 81 | frappe.throw(response.json().get("error").get("message")) 82 | 83 | def on_trash(self): 84 | if self.template_name=='welcome_message': 85 | frappe.throw(msg="You're not authorised to Delete standard Templates", title="Not Permitted!") 86 | 87 | @frappe.whitelist() 88 | def set_template_parameters(whatsapp_message_template): 89 | template_doc = frappe.get_doc('WhatsApp Message Template', whatsapp_message_template) 90 | return template_doc.parameters 91 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_message_template_item/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/efeone/frappe_meta_integration/9e4a95c8eeb46e76b51a77a24391b7d0582e6674/frappe_meta_integration/whatsapp/doctype/whatsapp_message_template_item/__init__.py -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_message_template_item/whatsapp_message_template_item.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_rename": 1, 4 | "creation": "2022-06-30 11:52:05.833349", 5 | "doctype": "DocType", 6 | "editable_grid": 1, 7 | "engine": "InnoDB", 8 | "field_order": [ 9 | "parameter", 10 | "value" 11 | ], 12 | "fields": [ 13 | { 14 | "fieldname": "parameter", 15 | "fieldtype": "Data", 16 | "in_list_view": 1, 17 | "label": "Parameter", 18 | "reqd": 1 19 | }, 20 | { 21 | "fieldname": "value", 22 | "fieldtype": "Data", 23 | "in_list_view": 1, 24 | "label": "Value" 25 | } 26 | ], 27 | "index_web_pages_for_search": 1, 28 | "istable": 1, 29 | "links": [], 30 | "modified": "2022-06-30 12:39:20.015467", 31 | "modified_by": "Administrator", 32 | "module": "WhatsApp", 33 | "name": "WhatsApp Message Template Item", 34 | "owner": "Administrator", 35 | "permissions": [], 36 | "sort_field": "modified", 37 | "sort_order": "DESC" 38 | } -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_message_template_item/whatsapp_message_template_item.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022, efeone Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | class WhatsAppMessageTemplateItem(Document): 8 | pass 9 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_webhook_log/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/efeone/frappe_meta_integration/9e4a95c8eeb46e76b51a77a24391b7d0582e6674/frappe_meta_integration/whatsapp/doctype/whatsapp_webhook_log/__init__.py -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_webhook_log/test_whatsapp_webhook_log.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, efeone Pvt. Ltd. and Contributors 2 | # See license.txt 3 | 4 | # import frappe 5 | from frappe.tests.utils import FrappeTestCase 6 | 7 | 8 | class TestWhatsAppWebhookLog(FrappeTestCase): 9 | pass 10 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_webhook_log/whatsapp_webhook_log.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2023, efeone Pvt. Ltd. and contributors 2 | // For license information, please see license.txt 3 | 4 | frappe.ui.form.on('WhatsApp Webhook Log', { 5 | // refresh: function(frm) { 6 | 7 | // } 8 | }); 9 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_webhook_log/whatsapp_webhook_log.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": [], 3 | "allow_copy": 1, 4 | "autoname": "hash", 5 | "creation": "2023-06-10 11:16:54.060320", 6 | "default_view": "List", 7 | "doctype": "DocType", 8 | "editable_grid": 1, 9 | "engine": "InnoDB", 10 | "field_order": [ 11 | "payload" 12 | ], 13 | "fields": [ 14 | { 15 | "fieldname": "payload", 16 | "fieldtype": "JSON", 17 | "label": "Payload", 18 | "read_only": 1 19 | } 20 | ], 21 | "in_create": 1, 22 | "index_web_pages_for_search": 1, 23 | "links": [], 24 | "modified": "2023-06-10 12:44:07.995294", 25 | "modified_by": "Administrator", 26 | "module": "WhatsApp", 27 | "name": "WhatsApp Webhook Log", 28 | "naming_rule": "Random", 29 | "owner": "Administrator", 30 | "permissions": [ 31 | { 32 | "create": 1, 33 | "delete": 1, 34 | "email": 1, 35 | "export": 1, 36 | "print": 1, 37 | "read": 1, 38 | "report": 1, 39 | "role": "System Manager", 40 | "share": 1, 41 | "write": 1 42 | }, 43 | { 44 | "create": 1, 45 | "email": 1, 46 | "export": 1, 47 | "print": 1, 48 | "read": 1, 49 | "report": 1, 50 | "role": "Meta Manager", 51 | "share": 1, 52 | "write": 1 53 | } 54 | ], 55 | "sort_field": "modified", 56 | "sort_order": "DESC", 57 | "states": [] 58 | } -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/doctype/whatsapp_webhook_log/whatsapp_webhook_log.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2023, efeone Pvt. Ltd. and contributors 2 | # For license information, please see license.txt 3 | 4 | # import frappe 5 | from frappe.model.document import Document 6 | 7 | class WhatsAppWebhookLog(Document): 8 | pass 9 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/overrides/notification.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | from frappe import _ 3 | from frappe.email.doctype.notification.notification import Notification, get_context, json 4 | from frappe_meta_integration.whatsapp.doctype.whatsapp_communication.whatsapp_communication import WhatsAppCommunication 5 | from frappe.utils.print_format import download_pdf 6 | from frappe_meta_integration.whatsapp.pdf_utils import * 7 | 8 | class SendNotification(Notification): 9 | def send(self, doc): 10 | """ 11 | Overrided current send method 12 | """ 13 | context = get_context(doc) 14 | context = {"doc": doc, "alert": self, "comments": None} 15 | if doc.get("_comments"): 16 | context["comments"] = json.loads(doc.get("_comments")) 17 | 18 | if self.is_standard: 19 | self.load_standard_properties(context) 20 | 21 | try: 22 | if self.channel == 'WhatsApp': 23 | self.send_whatsapp_msg(doc, context) 24 | except: 25 | frappe.log_error(title='Failed to send notification', message=frappe.get_traceback()) 26 | 27 | super(SendNotification, self).send(doc) 28 | 29 | def send_whatsapp_msg(self, doc, context): 30 | """ 31 | Generate mediaif exists and send message 32 | """ 33 | pdf_link = None 34 | file_name = None 35 | attachments = self.get_attachment(doc) 36 | if attachments: 37 | doctype = attachments[0]['doctype'] 38 | docname = attachments[0]['name'] 39 | title = attachments[0]['name'] 40 | print_format = attachments[0]['print_format'] 41 | doctype_folder = create_folder(_(doctype), "Home") 42 | title_folder = create_folder(title, doctype_folder) 43 | pdf_data = get_pdf_data(doctype, docname, print_format) 44 | file_ref = save_and_attach(pdf_data, doctype, docname, title_folder) 45 | pdf_link = file_ref.file_url 46 | file_name = file_ref.file_name 47 | 48 | WhatsAppCommunication.send_whatsapp_message( 49 | receiver_list=self.get_receiver_list(doc, context), 50 | message=frappe.render_template(self.message, context), 51 | doctype = self.doctype, 52 | docname = self.name, 53 | media = pdf_link, 54 | file_name = file_name 55 | ) 56 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/pdf_utils.py: -------------------------------------------------------------------------------- 1 | import frappe 2 | 3 | from frappe import _ 4 | from frappe import publish_progress 5 | from frappe.utils.file_manager import save_file 6 | 7 | @frappe.whitelist() 8 | def create_new_folder(file_name, folder): 9 | """create new folder under current parent folder""" 10 | file = frappe.new_doc("File") 11 | file.file_name = file_name 12 | file.is_folder = 1 13 | file.folder = folder 14 | file.insert(ignore_if_duplicate=True) 15 | return file 16 | 17 | def attach_pdf(doc, event=None, print_format=None): 18 | fallback_language = "en" 19 | args = { 20 | "doctype": doc.doctype, 21 | "name": doc.name, 22 | "title": doc.get_title(), 23 | "lang": getattr(doc, "language", fallback_language), 24 | "show_progress":1, 25 | "print_format" : print_format 26 | } 27 | 28 | execute(**args) 29 | 30 | def execute(doctype, name, title, lang=None, show_progress=True, print_format=None): 31 | """ 32 | Queue calls this method, when it's ready. 33 | 1. Create necessary folders 34 | 2. Get raw PDF data 35 | 3. Save PDF file and attach it to the document 36 | """ 37 | progress = frappe._dict(title=_("Creating PDF ..."), percent=0, doctype=doctype, docname=name) 38 | 39 | if lang: 40 | frappe.local.lang = lang 41 | 42 | if show_progress: 43 | publish_progress(**progress) 44 | 45 | doctype_folder = create_folder(_(doctype), "Home") 46 | title_folder = create_folder(title, doctype_folder) 47 | 48 | if show_progress: 49 | progress.percent = 33 50 | publish_progress(**progress) 51 | 52 | pdf_data = get_pdf_data(doctype, name, print_format) 53 | 54 | if show_progress: 55 | progress.percent = 66 56 | publish_progress(**progress) 57 | 58 | save_and_attach(pdf_data, doctype, name, title_folder) 59 | 60 | if show_progress: 61 | progress.percent = 100 62 | publish_progress(**progress) 63 | 64 | def create_folder(folder, parent): 65 | """Make sure the folder exists and return it's name.""" 66 | new_folder_name = "/".join([parent, folder]) 67 | 68 | if not frappe.db.exists("File", new_folder_name): 69 | create_new_folder(folder, parent) 70 | 71 | return new_folder_name 72 | 73 | 74 | def get_pdf_data(doctype, name, print_format=None): 75 | """Document -> HTML -> PDF.""" 76 | if print_format: 77 | html = frappe.get_print(doctype, name, print_format) 78 | else: 79 | html = frappe.get_print(doctype, name) 80 | return frappe.utils.pdf.get_pdf(html) 81 | 82 | 83 | def save_and_attach(content, to_doctype, to_name, folder): 84 | """ 85 | Save content to disk and create a File document. 86 | File document is linked to another document. 87 | """ 88 | file_name = "{}.pdf".format(to_name.replace(" ", "-").replace("/", "-")) 89 | file_url = save_file(file_name, content, to_doctype, to_name, folder=folder, is_private=1) 90 | return file_url 91 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/public/js/notification.js: -------------------------------------------------------------------------------- 1 | frappe.ui.form.on('Notification', { 2 | refresh: function(frm) { 3 | frm.events.setup_whatsapp_template(frm); 4 | }, 5 | channel: function(frm) { 6 | frm.events.setup_whatsapp_template(frm); 7 | }, 8 | setup_whatsapp_template: function(frm) { 9 | let template = ''; 10 | if (frm.doc.channel === 'WhatsApp') { 11 | template = ` 12 |
Message Example
13 | 14 |
15 | Your appointment is coming up on {{ doc.date }} at {{ doc.time }}
16 | 
`; 17 | } 18 | if (template) { 19 | frm.set_df_property('message_examples', 'options', template); 20 | } 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/public/js/user.js: -------------------------------------------------------------------------------- 1 | frappe.ui.form.on('User', { 2 | refresh(frm) { 3 | if(!frm.doc.whatsapp_2f_authenticated && frm.doc.user_whatsapp_number){ 4 | frm.add_custom_button('Whatsapp 2FA', () => { 5 | set_primary_action(frm); 6 | }); 7 | } 8 | } 9 | }); 10 | 11 | function set_primary_action(frm){ 12 | var feild_dicts = []; 13 | frappe.db.get_single_value("WhatsApp Cloud API Settings", "welcome_message_template").then( whatsapp_message_template => { 14 | frappe.call({ 15 | method: 'frappe_meta_integration.whatsapp.doctype.whatsapp_message_template.whatsapp_message_template.set_template_parameters', 16 | args: { 17 | "whatsapp_message_template": whatsapp_message_template 18 | }, 19 | callback: function(r) { 20 | for(var i=0; i< r.message.length; i++){ 21 | const parameter = r.message[i].parameter 22 | var feild_dict = { 23 | label: parameter.charAt(0).toUpperCase() + parameter.slice(1), 24 | fieldname: parameter, 25 | fieldtype: 'Data', 26 | reqd: 1, 27 | description: 'Parameter required for Template', 28 | } 29 | feild_dicts.push(feild_dict) 30 | } 31 | frappe.prompt(feild_dicts,(values) => { 32 | send_welcome_message(frm, values); 33 | }) 34 | } 35 | }); 36 | }); 37 | } 38 | 39 | function send_welcome_message(frm, values){ 40 | frappe.call({ 41 | method: 'frappe_meta_integration.whatsapp.utils.send_welcome_message', 42 | args: { 43 | "phone_number": frm.doc.user_whatsapp_number, 44 | "parameters": values 45 | }, 46 | callback: function(r) { 47 | frm.set_value('whatsapp_2f_authenticated', 1); 48 | frm.save() 49 | frm.refresh_fields(); 50 | } 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | import frappe 4 | from frappe import _ 5 | import requests 6 | import json 7 | import string 8 | from frappe_meta_integration.whatsapp.doctype.whatsapp_communication.whatsapp_communication import WhatsAppCommunication 9 | from frappe.utils.print_format import download_pdf 10 | from frappe_meta_integration.whatsapp.pdf_utils import * 11 | 12 | def get_access_token(): 13 | return frappe.utils.password.get_decrypted_password( 14 | "WhatsApp Cloud API Settings", "WhatsApp Cloud API Settings", "access_token" 15 | ) 16 | 17 | @frappe.whitelist() 18 | def set_template_parameters(whatsapp_message_template): 19 | template_doc = frappe.get_doc('WhatsApp Message Template', whatsapp_message_template) 20 | return template_doc.parameters 21 | 22 | @frappe.whitelist() 23 | def send_welcome_message(phone_number, parameters = None): 24 | if parameters and isinstance(parameters, str): 25 | parameters = json.loads(parameters) 26 | access_token = get_access_token() 27 | phone_number_id = frappe.db.get_single_value("WhatsApp Cloud API Settings", "phone_number_id") 28 | welcome_message_template = frappe.db.get_single_value("WhatsApp Cloud API Settings", "welcome_message_template") 29 | template_parameters = set_template_parameters(welcome_message_template) 30 | whatsapp_communication_doc = frappe.new_doc("WhatsApp Communication") 31 | whatsapp_communication_doc.to = phone_number 32 | whatsapp_communication_doc.message_type = 'Template' 33 | whatsapp_communication_doc.whatsapp_message_template = welcome_message_template 34 | whatsapp_communication_doc.is_welcome_message = 1 35 | for template_parameter in template_parameters: 36 | row = whatsapp_communication_doc.append('parameters') 37 | row.parameter = template_parameter.parameter 38 | row.value = parameters[template_parameter.parameter] 39 | whatsapp_communication_doc.save() 40 | whatsapp_communication_doc.send_message() 41 | 42 | @frappe.whitelist() 43 | def validate_whatsapp_number(whatsapp_number): 44 | ''' 45 | Validate Phone Number with Special Characters. 46 | ''' 47 | special_chars = string.punctuation #To get all punctuations 48 | bools = list(map(lambda char: char in special_chars, whatsapp_number)) 49 | if any(bools): 50 | frappe.throw( 51 | _("Whatsapp Number {0} is Invalid, Special Characters are not allowed.").format(frappe.bold(whatsapp_number)) 52 | ) 53 | if ' ' in whatsapp_number: 54 | frappe.throw( 55 | _("Whatsapp Number {0} is Invalid, Spaces are not allowed.").format(frappe.bold(whatsapp_number)) 56 | ) 57 | 58 | @frappe.whitelist() 59 | def get_contact_list(txt, page_length=20): 60 | """Returns contacts (from autosuggest)""" 61 | 62 | out = frappe.db.sql( 63 | """ 64 | select 65 | whatsapp_number as value, 66 | concat(first_name, ifnull(concat(' ',last_name), '' )) as description 67 | from 68 | tabContact 69 | where 70 | whatsapp_number <>'' AND ( 71 | name like %(txt)s or 72 | whatsapp_number like %(txt)s 73 | ) 74 | limit 75 | %(page_length)s""", 76 | {"txt": "%" + txt + "%", "page_length": page_length}, 77 | as_dict=True, 78 | ) 79 | out = filter(None, out) 80 | return out 81 | 82 | @frappe.whitelist() 83 | def send_whatsapp_msg(doctype, docname, args): 84 | """ 85 | Generate mediaif exists and send message 86 | """ 87 | pdf_link = None 88 | file_name = None 89 | if args and isinstance(args, str): 90 | args = json.loads(args) 91 | #Setting argumnents is exist 92 | print_format = args['print_format'] if 'print_format' in args else "Standard" 93 | message = args['message'] if 'message' in args else "Please find the attachments." 94 | 95 | #Setting recipients list 96 | recipients = (args['recipients']).replace(" ", "") 97 | last_char = recipients[-1] 98 | if last_char == ',': 99 | receiver_list = recipients[0: -1].split(',') 100 | else: 101 | receiver_list = recipients 102 | 103 | if args['attach_document_print']: #If Attachment included 104 | doctype = doctype 105 | docname = docname 106 | title = docname 107 | print_format = print_format 108 | doctype_folder = create_folder(_(doctype), "Home") 109 | title_folder = create_folder(title, doctype_folder) 110 | pdf_data = get_pdf_data(doctype, docname, print_format) 111 | file_ref = save_and_attach(pdf_data, doctype, docname, title_folder) 112 | pdf_link = file_ref.file_url 113 | file_name = file_ref.file_name 114 | if not args['attach_document_print'] and not message: 115 | frappe.throw( 116 | _("Either Message or Attachment is required.") 117 | ) 118 | WhatsAppCommunication.send_whatsapp_message( 119 | receiver_list = receiver_list, 120 | message = message, 121 | doctype = doctype, 122 | docname = docname, 123 | media = pdf_link, 124 | file_name = file_name, 125 | ) 126 | -------------------------------------------------------------------------------- /frappe_meta_integration/whatsapp/workspace/meta_integration/meta_integration.json: -------------------------------------------------------------------------------- 1 | { 2 | "charts": [], 3 | "content": "[{\"id\":\"YBBzcNWVc5\",\"type\":\"header\",\"data\":{\"text\":\"Meta Integration\",\"col\":12}},{\"id\":\"sa3WXt4tyC\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"WhatsApp Cloud API Settings\",\"col\":4}},{\"id\":\"18IYxYRTfo\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"WhatsApp Message Template\",\"col\":4}},{\"id\":\"fVs7ZNT8d3\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"WhatsApp Communication\",\"col\":4}},{\"id\":\"ESDzUkK-NP\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"WhatsApp Campaign\",\"col\":3}}]", 4 | "creation": "2022-11-07 12:34:58.594498", 5 | "docstatus": 0, 6 | "doctype": "Workspace", 7 | "for_user": "", 8 | "hide_custom": 0, 9 | "icon": "integration", 10 | "idx": 0, 11 | "is_hidden": 0, 12 | "label": "Meta Integration", 13 | "links": [], 14 | "modified": "2023-03-01 12:26:53.994695", 15 | "modified_by": "Administrator", 16 | "module": "WhatsApp", 17 | "name": "Meta Integration", 18 | "owner": "Administrator", 19 | "parent_page": "", 20 | "public": 1, 21 | "quick_lists": [], 22 | "roles": [], 23 | "sequence_id": 10.0, 24 | "shortcuts": [ 25 | { 26 | "color": "Grey", 27 | "doc_view": "List", 28 | "label": "WhatsApp Campaign", 29 | "link_to": "WhatsApp Campaign", 30 | "type": "DocType" 31 | }, 32 | { 33 | "color": "Grey", 34 | "doc_view": "List", 35 | "label": "WhatsApp Cloud API Settings", 36 | "link_to": "WhatsApp Cloud API Settings", 37 | "type": "DocType" 38 | }, 39 | { 40 | "color": "Grey", 41 | "doc_view": "List", 42 | "label": "WhatsApp Message Template", 43 | "link_to": "WhatsApp Message Template", 44 | "type": "DocType" 45 | }, 46 | { 47 | "color": "Grey", 48 | "doc_view": "List", 49 | "label": "WhatsApp Communication", 50 | "link_to": "WhatsApp Communication", 51 | "type": "DocType" 52 | } 53 | ], 54 | "title": "Meta Integration" 55 | } -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | License: MIT -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Feature description 2 | Clearly and concisely describe the feature. 3 | 4 | ## Analysis and design (optional) 5 | Analyse and attach the design documentation 6 | 7 | ## Solution description 8 | Describe your code changes in detail for reviewers. 9 | 10 | ## Output screenshots (optional) 11 | Post the output screenshots, if a UI is affeced or added due to this feature. 12 | 13 | ## Areas affected and ensured 14 | List out the areas affected by your code changes. 15 | 16 | ## Is there any existing behavior change of other features due to this code change? 17 | Mention Yes or No. If Yes, provide the appropriate explanation. 18 | 19 | ## Was this feature tested on the browsers? 20 | - Chrome 21 | - Safari 22 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # frappe -- https://github.com/frappe/frappe is installed via 'bench init' -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open("requirements.txt") as f: 4 | install_requires = f.read().strip().split("\n") 5 | 6 | # get version from __version__ variable in frappe_meta_integration/__init__.py 7 | from frappe_meta_integration import __version__ as version 8 | 9 | setup( 10 | name="frappe_meta_integration", 11 | version=version, 12 | description="Meta Cloud API Integration for frappe framework", 13 | author="efeone Pvt. Ltd.", 14 | author_email="info@efeone.com", 15 | packages=find_packages(), 16 | zip_safe=False, 17 | include_package_data=True, 18 | install_requires=install_requires 19 | ) 20 | --------------------------------------------------------------------------------