diff --git a/.gitignore b/.gitignore index ba04025..b1fde41 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ *.swp tags node_modules -__pycache__ \ No newline at end of file +__pycache__ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..81bf4f6 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml diff --git a/README.md b/README.md index 653943f..50cbbd8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Introduction -With the **Chatbot Builder**, organizations can easily automate responses to time-consuming and frequently asked questions. The process is simple: +With the **Chatbot Builder**, organizations can easily automate responses to time-consuming and frequently asked questions. The process is simple: 1. **Identify Common Questions**: List the repetitive questions that your team handles. 2. **Create Reply Templates**: Design templates with variables to customize responses. @@ -75,3 +75,4 @@ You can visualize the chatbot conversation as a tree structure. Each branch repr You are now ready to design your **Chatbot Flow** and start automating your interactions with users. +mit diff --git a/chatbot/api/telegram_api.py b/chatbot/api/telegram_api.py index 6e9321f..16632aa 100644 --- a/chatbot/api/telegram_api.py +++ b/chatbot/api/telegram_api.py @@ -2,44 +2,33 @@ import requests import json from chatbot.chatbot.doctype.chatbot_log.chatbot_log import log_chatbot -from chatbot.utils import validate_user, get_root_chatbot_flow, get_associated_party_types, fetch_all_children - +from chatbot.utils import validate_user, get_root_chatbot_flow, get_associated_party_types, fetch_all_children,send_document +import hashlib +from passlib.context import CryptContext +passlibctx = CryptContext(schemes=["pbkdf2_sha256","argon2",], ) class TelegramAPI: - def __init__(self, update): + def __init__(self, update,headers): self.update = update - self.token = frappe.get_doc("Chatbot Setup").get_password('telegram_api_token') - self.base_url = f"https://api.telegram.org/bot{self.token}/" - self.reply_text = str() - self.reply_markup = {} - self.data = str() - self.chat_id=str() - print("update",update) - if "callback_query" in update: - self.callback_query =update.get('callback_query') - self.message = self.callback_query.get('message') - self.data =self.callback_query.get('data') - self.chat_id = self.message.get("chat").get("id") - self.user_name="@"+self.message.get("chat").get("username") - elif "message" in update: - self.message = update.get('message') - - if self.message: - self.chat_id = self.message.get("chat").get("id") - self.user_name = "@"+self.message["chat"]["username"] + self.headers=headers + self.validate_token=False + self._validate_token() + if self.validate_token: + self.set_default_values() + + def find_type_and_send(self): + if self.response_type == "Text": + self.send_message() else: - frappe.throw(msg="Message object not found") - - def send_message(self, text:str): + self.send_document(file_path=send_document(type=self.response_type)) + def send_message(self, text:str =None): """Send a message to a Telegram chat, with optional inline buttons.""" url = f"{self.base_url}sendMessage" payload = { "chat_id": self.chat_id, - "text": text, - "ForceReply" - "reply_markup": self.reply_markup # Inline keyboard markup + "text": self.reply_text, + "reply_markup":self.reply_markup# Inline keyboard markup } - print("self.reply_markup",self.reply_markup) response = requests.post(url, json=payload) log_chatbot( @@ -83,16 +72,16 @@ def send_photo(self, chat_id, photo_url): return response.json() - def send_document(self, chat_id, file_path): + def send_document(self, file_path): """Send a document (PDF or other file types) to a Telegram chat.""" url = f"{self.base_url}sendDocument" with open(file_path, 'rb') as file: files = {'document': file} payload = { - "chat_id": chat_id + "chat_id": self.chat_id } response = requests.post(url, files=files, data=payload) - + self.send_message() if response.status_code != 200: frappe.log_error(f"Error sending document: {response.text}", "Telegram API Error") return None @@ -112,46 +101,105 @@ def process_update(self): self.reply_text = "User not registered" self.send_message(self.reply_text) - + def _validate_token(self): + secret_token_ft=self.headers.get("X-Telegram-Bot-Api-Secret-Token") + host=self.headers.get("X-Forwarded-Host") + proto=self.headers.get("X-Forwarded-Proto") + if proto !="https": + frappe.throw("Not an https Request", frappe.PermissionError) + if host !=frappe.db.get_value("Chatbot Setup","Chatbot Setup","telegram_webhook_url").strip()[8:]: + frappe.throw("Not an Host", frappe.PermissionError) + if secret_token_ft: + secret_token = frappe.get_doc("Chatbot Setup").get_password("secret_token") + if secret_token: + random_value=hashlib.sha256(secret_token_ft.encode()) + random_value=random_value.hexdigest() + if passlibctx.verify(random_value,secret_token): + self.validate_token =True + else: + frappe.throw("Verify Error", frappe.PermissionError) + else: + frappe.throw("StFxx0n0ecre Tnmx0naoke Error", frappe.PermissionError) + else: + frappe.throw("StFxx0n0ecre Tnmx0naoke Error FT", frappe.PermissionError) def handle_message(self, party_type, party_name): """Handle incoming messages.""" # Determine chatbot flow and template + self.data=self.data if frappe.db.exists("Chatbot Flow",self.data) else None pre_chatbot_flow_name=frappe.cache.get_value(self.chat_id) + pre_chatbot_flow_name=pre_chatbot_flow_name if frappe.db.exists("Chatbot Flow",pre_chatbot_flow_name) else None + parent_flow=get_root_chatbot_flow(party_type) if self.data: - chatbot_flow = frappe.get_doc('Chatbot Flow', self.data) + + chatbot_flow = frappe.get_doc('Chatbot Flow', self.data) elif pre_chatbot_flow_name: chatbot_flow = frappe.get_doc('Chatbot Flow', pre_chatbot_flow_name) children = fetch_all_children(chatbot_flow.get('name')) - if children: + if children and self.received_reply_text and frappe.db.get_value("Chatbot Message Template",chatbot_flow.template,"is_get_value")==1: chatbot_flow = frappe.get_doc('Chatbot Flow', children[0].get("name")) else: - chatbot_flow= get_root_chatbot_flow() - - + chatbot_flow= parent_flow + + else: - chatbot_flow= get_root_chatbot_flow() - + chatbot_flow=parent_flow + associated_parties = get_associated_party_types(chatbot_flow.get('name')) children = fetch_all_children(chatbot_flow.get('name')) template = chatbot_flow.get('template') - - # Build reply markup (inline keyboard) - self.reply_markup = {"inline_keyboard": [], 'resize_keyboard': True} - - if children: - self.reply_markup["inline_keyboard"] = [ - [{"text": child.get('button_text'), "callback_data": child.get('name')}] - for child in children - ] - + chatbot_template = frappe.get_doc('Chatbot Message Template', template) + reply_markup=None + if chatbot_template.is_get_value==1: + reply_markup={"force_reply":True} + else: + inline_keyboard=[[]] + if children: + inline_keyboard= [ + [{"text": child.get('button_text'), "callback_data": child.get('name')} ] + for child in children if child.get('button_text') + ] + + if chatbot_flow.get('name') !=parent_flow.get("name"): + inline_keyboard[0].append({"text": "Main Menu", "callback_data":parent_flow.get("name") }) + reply_markup={"inline_keyboard": inline_keyboard, 'resize_keyboard': True} + self.reply_markup=reply_markup # Check if party_type is in associated parties if party_type in associated_parties: - chatbot_template = frappe.get_doc('Chatbot Message Template', template) - kwargs = {party_type.lower(): party_name} + kwargs = {party_type.lower(): party_name,"data":self.message_received} self.reply_text = chatbot_template.get_rendered_template(**kwargs) - self.send_message(self.reply_text) + self.response_type =chatbot_template.response_type + self.find_type_and_send() frappe.cache.set_value(self.chat_id,chatbot_flow.get('name')) def set_chat_id(self,party_type,party_name): - chat_id =frappe.db.get_value(party_type,party_name,"telegram_user_id") + chat_id = frappe.db.get_value(party_type,party_name,"telegram_user_id") if not chat_id or chat_id!=self.chat_id: - frappe.db.set_value(party_type,party_name,"telegram_user_id",self.chat_id) \ No newline at end of file + frappe.db.set_value(party_type,party_name,"telegram_user_id",self.chat_id) + def set_default_values(self): + self.token = frappe.get_doc("Chatbot Setup").get_password('telegram_api_token') + self.base_url = f"https://api.telegram.org/bot{self.token}/" + self.reply_text = str() + self.reply_markup = {} + self.data = str() + self.chat_id=str() + self.message_received=str() + self.received_reply_text=str() + + if "callback_query" in self.update: + self.callback_query =self.update.get('callback_query') + self.message = self.callback_query.get('message') + self.data =self.callback_query.get('data') + self.chat_id = self.message.get("chat").get("id") + self.user_name="@"+self.message.get("chat").get("username") + elif "message" in self.update: + self.message = self.update.get('message') + + if self.message: + + self.chat_id = self.message.get("chat").get("id") + self.user_name = "@"+self.message["chat"]["username"] + self.received_reply_text =self.message.get("reply_to_message") + if self.received_reply_text: + self.received_reply_text=self.received_reply_text.get("text") + self.message_received=self.message.get("text") + else: + frappe.throw(msg="Message object not found") diff --git a/chatbot/chatbot/doctype/chatbot_associated_party_types/chatbot_associated_party_types.json b/chatbot/chatbot/doctype/chatbot_associated_party_types/chatbot_associated_party_types.json index 086a8f2..5d9d11c 100644 --- a/chatbot/chatbot/doctype/chatbot_associated_party_types/chatbot_associated_party_types.json +++ b/chatbot/chatbot/doctype/chatbot_associated_party_types/chatbot_associated_party_types.json @@ -29,4 +29,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/chatbot/chatbot/doctype/chatbot_flow/chatbot_flow.json b/chatbot/chatbot/doctype/chatbot_flow/chatbot_flow.json index 313b1a5..879bb0d 100644 --- a/chatbot/chatbot/doctype/chatbot_flow/chatbot_flow.json +++ b/chatbot/chatbot/doctype/chatbot_flow/chatbot_flow.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_import": 1, "allow_rename": 1, "autoname": "field:name1", "creation": "2024-09-22 11:22:36.891954", @@ -78,26 +79,28 @@ "fieldname": "associated_party_types", "fieldtype": "Table MultiSelect", "label": "Associated Party Types", - "options": "Chatbot Associated Party Types" + "options": "Chatbot Associated Party Types", + "reqd": 1 }, { "fieldname": "template", "fieldtype": "Link", "label": "Template", - "options": "Chatbot Message Template" + "options": "Chatbot Message Template", + "reqd": 1 }, { "fieldname": "type", "fieldtype": "Select", "label": "Type", - "options": "\nButton\nText\nGet Value\nImage\nVideo\nDocument\nLocation", + "options": "\nButton\nText", "reqd": 1 } ], "index_web_pages_for_search": 1, "is_tree": 1, "links": [], - "modified": "2024-09-25 12:55:43.551793", + "modified": "2024-09-27 16:17:25.856464", "modified_by": "Administrator", "module": "Chatbot", "name": "Chatbot Flow", @@ -121,4 +124,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/chatbot/chatbot/doctype/chatbot_log/chatbot_log.json b/chatbot/chatbot/doctype/chatbot_log/chatbot_log.json index eddd891..09c846d 100644 --- a/chatbot/chatbot/doctype/chatbot_log/chatbot_log.json +++ b/chatbot/chatbot/doctype/chatbot_log/chatbot_log.json @@ -125,4 +125,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/chatbot/chatbot/doctype/chatbot_message_template/chatbot_message_template.js b/chatbot/chatbot/doctype/chatbot_message_template/chatbot_message_template.js index c8eaeda..df25317 100644 --- a/chatbot/chatbot/doctype/chatbot_message_template/chatbot_message_template.js +++ b/chatbot/chatbot/doctype/chatbot_message_template/chatbot_message_template.js @@ -1,8 +1,14 @@ // Copyright (c) 2024, Aerele and contributors // For license information, please see license.txt -// frappe.ui.form.on("Chatbot Message Template", { -// refresh(frm) { - -// }, -// }); +frappe.ui.form.on("Chatbot Message Template", { + refresh(frm) { + frm.set_query("print_format", function (doc) { + return { + filters: { + "doc_type": frm.doc.default_doctype + }, + }; + }); + }, +}); diff --git a/chatbot/chatbot/doctype/chatbot_message_template/chatbot_message_template.json b/chatbot/chatbot/doctype/chatbot_message_template/chatbot_message_template.json index 9bc4a6c..65e0603 100644 --- a/chatbot/chatbot/doctype/chatbot_message_template/chatbot_message_template.json +++ b/chatbot/chatbot/doctype/chatbot_message_template/chatbot_message_template.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_import": 1, "allow_rename": 1, "autoname": "prompt", "creation": "2024-09-21 14:32:31.588744", @@ -7,13 +8,15 @@ "engine": "InnoDB", "field_order": [ "placeholder", - "enable_dynamic_response", "response_type", + "is_get_value", + "enable_dynamic_response", "template", + "attachment", + "default_doctype", "print_format", "default_document", "report", - "attachment", "is_custom_function", "custom_function_path", "server_script", @@ -23,10 +26,11 @@ { "fieldname": "placeholder", "fieldtype": "Data", + "hidden": 1, "label": "Placeholder" }, { - "default": "0", + "default": "1", "fieldname": "enable_dynamic_response", "fieldtype": "Check", "label": "Enable Dynamic Response" @@ -34,9 +38,10 @@ { "fieldname": "response_type", "fieldtype": "Select", - "hidden": 1, + "in_list_view": 1, "label": "Response Type", - "options": "Text\nPrint format\nReport\nImage" + "options": "\nText\nPrint format\nReport\nImage", + "reqd": 1 }, { "depends_on": "eval:doc.enable_dynamic_response", @@ -45,6 +50,7 @@ "label": "Template" }, { + "depends_on": "eval:doc.response_type == \"Print format\"", "fieldname": "print_format", "fieldtype": "Link", "label": "Print format", @@ -52,11 +58,13 @@ }, { "fieldname": "default_document", - "fieldtype": "Link", + "fieldtype": "Dynamic Link", + "hidden": 1, "label": "Default Document", - "options": "Print Format" + "options": "default_doctype" }, { + "depends_on": "eval:doc.response_type == \"Report\"", "fieldname": "report", "fieldtype": "Link", "label": "Report", @@ -89,12 +97,27 @@ { "fieldname": "html_yzlg", "fieldtype": "HTML", - "options": "

Help

\n

Custom Function

\n

It contains the keyword arguments respective to the associated party names

\n

{Party Type} = {Party Name}

\n

Example: customer = \"customer_name\"

\n

So, make sure your custom function has **kwargs as a param to get values

\n
\n

Server Script

\n
\nres = {}\n\n#get the default arguments passed using form_dict.party_name\nif frappe.form_dict.customer: \n    customer = frappe.form_dict.customer\n    company = frappe.db.get_default('company')\n    res[\"customer_name\"] = customer\n    res[\"company\"] = company\n    res[\"phone\"] = \"9876543218\"\n    res[\"email\"] = \"kavin@aerele.in\"\n    res[\"website_url\"] = \"www.aerele.in\"\n    \n    #response should be passed in flags.response key : value dict\n    frappe.flags.response = res \n\n
" + "options": "

Help

\n

Custom Function

\n

It contains the keyword arguments respective to the associated party names

\n

{Party Type} = {Party Name}

\n

Example: customer = \"customer_name\"

\n

So, make sure your custom function has **kwargs as a param to get values

\n
\n

Server Script

\n
\nres = {}\n\n#get the default arguments passed using form_dict.party_name\nif frappe.form_dict.customer: \n    customer = frappe.form_dict.customer\n    company = frappe.db.get_default('company')\n    res[\"customer_name\"] = customer\n    res[\"company\"] = company\n    res[\"phone\"] = \"9876543218\"\n    res[\"email\"] = \"kavin@aerele.in\"\n    res[\"website_url\"] = \"www.aerele.in\"\n    \n    #response should be passed in flags.response key : value dict\n    frappe.flags.response = res \n\n
", + "read_only": 1 + }, + { + "depends_on": "eval:doc.response_type == \"Print format\"", + "fieldname": "default_doctype", + "fieldtype": "Link", + "label": "Default Doctype", + "mandatory_depends_on": "eval:doc.response_type == \"Print format\"", + "options": "DocType" + }, + { + "default": "1", + "fieldname": "is_get_value", + "fieldtype": "Check", + "label": "Requires Input" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-09-22 19:52:04.827393", + "modified": "2024-09-28 15:02:14.687617", "modified_by": "Administrator", "module": "Chatbot", "name": "Chatbot Message Template", @@ -117,4 +140,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/chatbot/chatbot/doctype/chatbot_message_template/chatbot_message_template.py b/chatbot/chatbot/doctype/chatbot_message_template/chatbot_message_template.py index 6ad835d..3195dd9 100644 --- a/chatbot/chatbot/doctype/chatbot_message_template/chatbot_message_template.py +++ b/chatbot/chatbot/doctype/chatbot_message_template/chatbot_message_template.py @@ -18,8 +18,7 @@ def execute_server_script(self, **kwargs): api_method = frappe.db.get_value('Server Script', {'name':self.server_script, 'script_type':'API'}, 'api_method') - if api_method: response = run_script(self.server_script, **kwargs).get('response') - return response \ No newline at end of file + return response diff --git a/chatbot/chatbot/doctype/chatbot_party_type/chatbot_party_type.json b/chatbot/chatbot/doctype/chatbot_party_type/chatbot_party_type.json index efcae12..2771b23 100644 --- a/chatbot/chatbot/doctype/chatbot_party_type/chatbot_party_type.json +++ b/chatbot/chatbot/doctype/chatbot_party_type/chatbot_party_type.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_import": 1, "allow_rename": 1, "autoname": "field:party_name", "creation": "2024-09-21 23:57:22.931228", @@ -19,7 +20,7 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-09-22 00:01:09.139990", + "modified": "2024-09-24 13:27:10.904021", "modified_by": "Administrator", "module": "Chatbot", "name": "Chatbot Party Type", @@ -42,4 +43,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/chatbot/chatbot/doctype/chatbot_party_type/chatbot_party_type.py b/chatbot/chatbot/doctype/chatbot_party_type/chatbot_party_type.py index 0ed2568..27f2e00 100644 --- a/chatbot/chatbot/doctype/chatbot_party_type/chatbot_party_type.py +++ b/chatbot/chatbot/doctype/chatbot_party_type/chatbot_party_type.py @@ -12,28 +12,37 @@ def validate(self): def create_customer_custom_field(party_name): enable_telegram,enable_whatsapp,enable_slack=frappe.db.get_value("Chatbot Setup","Chatbot Setup",["enable_telegram","enable_whatsapp","enable_slack"]) + insert_afrter_fieldname= frappe.get_meta(party_name) + insert_afrter_fieldname=insert_afrter_fieldname.fields[-1].get("fieldname") custom_fields = {} if enable_telegram: - custom_fields.update( { + custom_fields = { party_name: [ dict( fieldname="chatbot_details", label="Chatbot Details", fieldtype="Tab Break", + insert_afrter=insert_afrter_fieldname, ), dict( - fieldname="custom_telegram_username", + fieldname="sb_chb", + label="", + fieldtype="Section Break", + insert_afrter="chatbot_details" + ), + dict( + fieldname="telegram_username", label="Telegram Username", fieldtype="Data", - insert_after="chatbot_details", + insert_after="sb_chb", ), dict( fieldname="telegram_user_id", read_only=1, label="", fieldtype="Data", - insert_after="custom_telegram_username", + insert_after="telegram_username", ), ] - }) - create_custom_fields(custom_fields, update=True) \ No newline at end of file + } + create_custom_fields(custom_fields, update=True) diff --git a/chatbot/chatbot/doctype/chatbot_setup/chatbot_setup.json b/chatbot/chatbot/doctype/chatbot_setup/chatbot_setup.json index 6e36881..0b05379 100644 --- a/chatbot/chatbot/doctype/chatbot_setup/chatbot_setup.json +++ b/chatbot/chatbot/doctype/chatbot_setup/chatbot_setup.json @@ -1,6 +1,5 @@ { "actions": [], - "allow_rename": 1, "creation": "2024-09-21 14:44:19.266520", "doctype": "DocType", "engine": "InnoDB", @@ -11,6 +10,7 @@ "telegram_username", "telegram_api_token", "telegram_webhook_url", + "secret_token", "whatsapp_tab", "enable_whatsapp", "whatsapp_title", @@ -42,7 +42,8 @@ { "fieldname": "telegram_username", "fieldtype": "Data", - "label": "Bot Username" + "label": "Bot Username", + "read_only": 1 }, { "fieldname": "telegram_api_token", @@ -82,12 +83,19 @@ "fieldname": "enable_slack", "fieldtype": "Check", "label": "Enable" + }, + { + "fieldname": "secret_token", + "fieldtype": "Password", + "hidden": 1, + "no_copy": 1, + "read_only": 1 } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-09-26 11:46:39.605839", + "modified": "2024-09-26 16:10:19.515181", "modified_by": "Administrator", "module": "Chatbot", "name": "Chatbot Setup", @@ -107,4 +115,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/chatbot/chatbot/doctype/chatbot_setup/chatbot_setup.py b/chatbot/chatbot/doctype/chatbot_setup/chatbot_setup.py index b0976c3..92bd7c6 100644 --- a/chatbot/chatbot/doctype/chatbot_setup/chatbot_setup.py +++ b/chatbot/chatbot/doctype/chatbot_setup/chatbot_setup.py @@ -4,53 +4,74 @@ import frappe from frappe.model.document import Document import requests - +import secrets +import hashlib +from passlib.context import CryptContext +passlibctx = CryptContext(schemes=["pbkdf2_sha256","argon2",], ) class ChatbotSetup(Document): - def autoname(self): - """Automatically set the name by replacing spaces with hyphens in the title.""" - self.name = self.title.replace(" ", "-") + def autoname(self): + """Automatically set the name by replacing spaces with hyphens in the title.""" + self.name = self.title.replace(" ", "-") + + def validate(self): + """Validate the Telegram API token and set the webhook if needed.""" + self.validate_api_token() + self.set_webhook() + + def validate_api_token(self): + """Validate the Telegram API token using Telegram's getMe API.""" + if not self.has_value_changed("telegram_api_token"): + return # Skip validation if token hasn't changed - def validate(self): - """Validate the Telegram API token and set the webhook if needed.""" - self.validate_api_token() - self.set_webhook() + frappe.db.commit() #Commit current changes to get recent decrypted token + api_token = self.get_password("telegram_api_token") - def validate_api_token(self): - """Validate the Telegram API token using Telegram's getMe API.""" - if not self.has_value_changed("telegram_api_token"): - return # Skip validation if token hasn't changed + url = f"https://api.telegram.org/bot{api_token}/getMe" - frappe.db.commit() #Commit current changes to get recent decrypted token - api_token = self.get_password("telegram_api_token") + try: + response = requests.get(url) + response.raise_for_status() # Raises an HTTPError for 4xx/5xx status codes - url = f"https://api.telegram.org/bot{api_token}/getMe" + data = response.json() - try: - response = requests.get(url) - response.raise_for_status() # Raises an HTTPError for 4xx/5xx status codes + # Ensure that the API response contains valid bot information + if data.get("result", {}).get("is_bot"): + self.telegram_username = "@" + data["result"]["username"] + else: + frappe.throw("The Telegram user is not a bot.") - data = response.json() + except requests.exceptions.RequestException as e: + frappe.throw(f"Failed to validate Telegram API token: {e}") - # Ensure that the API response contains valid bot information - if data.get("result", {}).get("is_bot"): - self.telegram_username = "@" + data["result"]["username"] - else: - frappe.throw("The Telegram user is not a bot.") + def set_webhook(self): + """Set the Telegram webhook URL using Telegram's setWebhook API.""" + if self.has_value_changed("telegram_webhook_url"): + api_token = self.get_password("telegram_api_token") + self.del_webhook() + s_token=self.get_secret_token() + webhook_url = f"{self.telegram_webhook_url}/api/method/chatbot.webhook.telegram_webhook" + url = f"https://api.telegram.org/bot{api_token}/setWebhook?url={webhook_url}&secret_token={s_token}&drop_pending_updates=True" - except requests.exceptions.RequestException as e: - frappe.throw(f"Failed to validate Telegram API token: {e}") + try: - def set_webhook(self): - """Set the Telegram webhook URL using Telegram's setWebhook API.""" - if self.has_value_changed("telegram_webhook_url"): - api_token = self.get_password("telegram_api_token") + response = requests.get(url) + response.raise_for_status() # Raises an HTTPError for 4xx/5xx status codes - webhook_url = f"{self.telegram_webhook_url}/api/method/chatbot.webhook.telegram_webhook" - url = f"https://api.telegram.org/bot{api_token}/setWebhook?url={webhook_url}" + except requests.exceptions.RequestException as e: + frappe.throw(f"Failed to set Telegram webhook: {e}") + def del_webhook(self): + api_token = self.get_password("telegram_api_token") + url=f"https://api.telegram.org/bot{api_token}/deleteWebhook?drop_pending_updates=1" + try: - try: - response = requests.get(url) - response.raise_for_status() # Raises an HTTPError for 4xx/5xx status codes + response = requests.get(url) + response.raise_for_status() # Raises an HTTPError for 4xx/5xx status codes - except requests.exceptions.RequestException as e: - frappe.throw(f"Failed to set Telegram webhook: {e}") + except requests.exceptions.RequestException as e: + frappe.throw(f"Failed to set Telegram webhook: {e}") + def get_secret_token(self): + random_value_to_send =secrets.token_hex(32) + random_value=hashlib.sha256(random_value_to_send.encode()) + random_value=random_value.hexdigest() + self.secret_token=passlibctx.hash(random_value) + return random_value_to_send diff --git a/chatbot/customization/after_migrate.py b/chatbot/customization/after_migrate.py index e585a3b..efccaaa 100644 --- a/chatbot/customization/after_migrate.py +++ b/chatbot/customization/after_migrate.py @@ -1,4 +1,4 @@ def after_migrate(): - pass \ No newline at end of file + pass diff --git a/chatbot/hooks.py b/chatbot/hooks.py index b4b8212..39bef44 100644 --- a/chatbot/hooks.py +++ b/chatbot/hooks.py @@ -243,4 +243,3 @@ # default_log_clearing_doctypes = { # "Logging DocType Name": 30 # days to retain logs # } - diff --git a/chatbot/modules.txt b/chatbot/modules.txt index 9f2bfd9..037aec8 100644 --- a/chatbot/modules.txt +++ b/chatbot/modules.txt @@ -1 +1 @@ -Chatbot \ No newline at end of file +Chatbot diff --git a/chatbot/patches.txt b/chatbot/patches.txt index f15c3a9..73efa6f 100644 --- a/chatbot/patches.txt +++ b/chatbot/patches.txt @@ -3,4 +3,4 @@ # Read docs to understand patches: https://frappeframework.com/docs/v14/user/en/database-migrations [post_model_sync] -# Patches added in this section will be executed after doctypes are migrated \ No newline at end of file +# Patches added in this section will be executed after doctypes are migrated diff --git a/chatbot/utils.py b/chatbot/utils.py index 491907a..68883eb 100644 --- a/chatbot/utils.py +++ b/chatbot/utils.py @@ -1,41 +1,76 @@ import frappe - +import os +import pdfkit +import json +from datetime import datetime +from frappe.www.printview import get_html_and_style def validate_user(user_name:str, service_name:str): - party_list = frappe.db.get_all("Chatbot Party Type", pluck="name") + party_list = frappe.db.get_all("Chatbot Party Type", pluck="name") - if service_name == "Telegram": - for party in party_list: - party_name = frappe.db.get_value(party, {"telegram_username":user_name}) - if party_name: - return party, party_name + if service_name == "Telegram": + for party in party_list: + party_name = frappe.db.get_value(party, {"telegram_username":user_name}) + if party_name: + return party, party_name - return None, None + return None, None -def get_root_chatbot_flow(): - root_doc = frappe.db.get_value('Chatbot Flow', - {'parent_chatbot_flow': ["is", "null"], 'button_text' : ["is", "null"]}, - ['template', 'name'], as_dict=1) +def get_root_chatbot_flow(party_type): + party_tree=frappe.db.get_all("Chatbot Associated Party Types",{"parent":["is","set"],"parenttype":"Chatbot Flow","party_name": party_type},["parent"],pluck="parent") + root_doc = frappe.db.get_value('Chatbot Flow', + {'parent_chatbot_flow': ["is", "null"], 'button_text' : ["is", "null"],"name":["in",party_tree]}, + ['template', 'name'], as_dict=1) - if root_doc: - return root_doc - else: - frappe.throw("No root document found for Chatbot Flow.", exc=frappe.DoesNotExistError) + if root_doc: + return root_doc + else: + frappe.throw("No root document found for Chatbot Flow.", exc=frappe.DoesNotExistError) def get_associated_party_types(docname:str): - party_types = frappe.db.get_all("Chatbot Associated Party Types", - filters={'parent':docname}, - pluck='party_name' - ) + party_types = frappe.db.get_all("Chatbot Associated Party Types", + filters={'parent':docname}, + pluck='party_name' + ) - return party_types + return party_types def fetch_all_children(docname:str): - children = frappe.db.get_all('Chatbot Flow', - filters={'parent_chatbot_flow':docname}, - fields=['name', 'button_text'] - ) + children = frappe.db.get_all('Chatbot Flow', + filters={'parent_chatbot_flow':docname}, + fields=['name', 'button_text'] + ) - return children + return children +def send_document(base_html=None,name=None,options = {'orientation':'Portrait'},type = "pdf"): + if type == "pdf_value": + now = datetime.now() + now = now.strftime("%d-%m-%Y") + statement_id =str(name)+"_"+str(now) + path = os.getcwd()+frappe.utils.get_site_base_path()[1:]+"/public/files/"+statement_id+".pdf" + pdfkit.from_string(base_html, path,options={'orientation':'Portrait'}) + return path +def send_file_contet(path): + with open(path, 'rb') as file: + return file +def get_prinformat_html(doctype,name): + base_html=get_html_and_style(doc=doctype,name=name) + base_html=""" """+base_html.get("style")+base_html.get("html")+" " + return base_html +def get_report_html(report,filters=None): + base_html="" + if not filters: + filters= '{"company":"A (Demo)","from_date":"2024-08-27","to_date":"2024-09-27","group_by":"Group by Voucher (Consolidated)","include_dimensions":1,"show_opening_entries":0,"include_default_book_entries":1,"show_cancelled_entries":0,"show_net_values_in_party_account":0,"add_values_in_transaction_currency":0,"show_remarks":0,"ignore_err":0,"ignore_cr_dr_notes":0}' + if report == "General Ledger": + report_get_htm_doc=frappe.new_doc("Auto Email Report") + report_get_htm_doc.report=report + report_get_htm_doc.filters =filters + report_get_htm_doc.report_type="Script Report" + report_get_htm_doc.user="Administrator" + report_get_htm_doc.name="General Ledger" + report_get_htm_doc.description="General Ledger" + report_get_htm_doc.format = "HTML" + base_html=report_get_htm_doc.get_report_content() + return base_html diff --git a/chatbot/webhook.py b/chatbot/webhook.py index ed13390..c3d90dd 100644 --- a/chatbot/webhook.py +++ b/chatbot/webhook.py @@ -8,7 +8,8 @@ def telegram_webhook(): """Webhook endpoint for Telegram.""" try: update = frappe.request.get_json() - api = TelegramAPI(update) + headers=frappe.request.headers + api = TelegramAPI(update,headers) try: api.process_update() @@ -22,4 +23,4 @@ def telegram_webhook(): # Log the error and return an error response frappe.log_error(title="Error processing webhook" ,message=frappe.get_traceback(with_context=1)) - return {"status": "error", "message": "Failed to process update"}, 500 \ No newline at end of file + return {"status": "error", "message": "Failed to process update"}, 500