Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
*.swp
tags
node_modules
__pycache__
__pycache__
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
158 changes: 103 additions & 55 deletions chatbot/api/telegram_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand All @@ -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)
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")
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}
13 changes: 8 additions & 5 deletions chatbot/chatbot/doctype/chatbot_flow/chatbot_flow.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:name1",
"creation": "2024-09-22 11:22:36.891954",
Expand Down Expand Up @@ -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",
Expand All @@ -121,4 +124,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}
2 changes: 1 addition & 1 deletion chatbot/chatbot/doctype/chatbot_log/chatbot_log.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
}
Original file line number Diff line number Diff line change
@@ -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
},
};
});
},
});
Loading