├── .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 |  19 | 20 | 21 | 2. WhatsApp Cloud API Settings 22 | 23 |  24 | 25 |  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 |  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.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 | --------------------------------------------------------------------------------