fix!: Remove "K.I.S.S. Bot" functionality
The feature has been removed from Core since it doesn't add any value to the system, is unmaintained, partially developed and undocumented.
This commit is contained in:
parent
541d8e6255
commit
081d3081bc
4 changed files with 4 additions and 252 deletions
|
|
@ -153,7 +153,7 @@
|
|||
"fieldname": "communication_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Communication Type",
|
||||
"options": "Communication\nComment\nChat\nBot\nNotification\nFeedback\nAutomated Message",
|
||||
"options": "Communication\nComment\nChat\nNotification\nFeedback\nAutomated Message",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
|
|
@ -164,7 +164,7 @@
|
|||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Comment Type",
|
||||
"options": "\nComment\nLike\nInfo\nLabel\nWorkflow\nCreated\nSubmitted\nCancelled\nUpdated\nDeleted\nAssigned\nAssignment Completed\nAttachment\nAttachment Removed\nShared\nUnshared\nBot\nRelinked",
|
||||
"options": "\nComment\nLike\nInfo\nLabel\nWorkflow\nCreated\nSubmitted\nCancelled\nUpdated\nDeleted\nAssigned\nAssignment Completed\nAttachment\nAttachment Removed\nShared\nUnshared\nRelinked",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
|
|
@ -395,7 +395,7 @@
|
|||
"icon": "fa fa-comment",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2021-11-30 09:03:25.728637",
|
||||
"modified": "2022-03-30 11:24:25.728637",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Core",
|
||||
"name": "Communication",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ from frappe.utils import validate_email_address, strip_html, cstr, time_diff_in_
|
|||
from frappe.core.doctype.communication.email import validate_email
|
||||
from frappe.core.doctype.communication.mixins import CommunicationEmailMixin
|
||||
from frappe.core.utils import get_parent_doc
|
||||
from frappe.utils.bot import BotReply
|
||||
from frappe.utils import parse_addr, split_emails
|
||||
from frappe.core.doctype.comment.comment import update_comment_in_doc
|
||||
from email.utils import getaddresses
|
||||
|
|
@ -105,7 +104,7 @@ class Communication(Document, CommunicationEmailMixin):
|
|||
if self.communication_type == "Communication":
|
||||
self.notify_change('add')
|
||||
|
||||
elif self.communication_type in ("Chat", "Notification", "Bot"):
|
||||
elif self.communication_type in ("Chat", "Notification"):
|
||||
if self.reference_name == frappe.session.user:
|
||||
message = self.as_dict()
|
||||
message['broadcast'] = True
|
||||
|
|
@ -160,7 +159,6 @@ class Communication(Document, CommunicationEmailMixin):
|
|||
|
||||
if self.comment_type != 'Updated':
|
||||
update_parent_document_on_communication(self)
|
||||
self.bot_reply()
|
||||
|
||||
def on_trash(self):
|
||||
if self.communication_type == "Communication":
|
||||
|
|
@ -278,20 +276,6 @@ class Communication(Document, CommunicationEmailMixin):
|
|||
if not self.sender_full_name:
|
||||
self.sender_full_name = sender_email
|
||||
|
||||
def bot_reply(self):
|
||||
if self.comment_type == 'Bot' and self.communication_type == 'Chat':
|
||||
reply = BotReply().get_reply(self.content)
|
||||
if reply:
|
||||
frappe.get_doc({
|
||||
"doctype": "Communication",
|
||||
"comment_type": "Bot",
|
||||
"communication_type": "Bot",
|
||||
"content": cstr(reply),
|
||||
"reference_doctype": self.reference_doctype,
|
||||
"reference_name": self.reference_name
|
||||
}).insert()
|
||||
frappe.local.flags.commit = True
|
||||
|
||||
def set_delivery_status(self, commit=False):
|
||||
'''Look into the status of Email Queue linked to this Communication and set the Delivery Status of this Communication'''
|
||||
delivery_status = None
|
||||
|
|
|
|||
|
|
@ -282,14 +282,6 @@ sounds = [
|
|||
# {"name": "chime", "src": "/assets/frappe/sounds/chime.mp3"},
|
||||
]
|
||||
|
||||
bot_parsers = [
|
||||
'frappe.utils.bot.ShowNotificationBot',
|
||||
'frappe.utils.bot.GetOpenListBot',
|
||||
'frappe.utils.bot.ListBot',
|
||||
'frappe.utils.bot.FindBot',
|
||||
'frappe.utils.bot.CountBot'
|
||||
]
|
||||
|
||||
setup_wizard_exception = [
|
||||
"frappe.desk.page.setup_wizard.setup_wizard.email_setup_wizard_exception",
|
||||
"frappe.desk.page.setup_wizard.setup_wizard.log_setup_wizard_exception"
|
||||
|
|
|
|||
|
|
@ -1,224 +0,0 @@
|
|||
# Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: MIT. See LICENSE
|
||||
|
||||
import frappe, re, frappe.utils
|
||||
from frappe.desk.notifications import get_notifications
|
||||
from frappe import _
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_bot_reply(question):
|
||||
return BotReply().get_reply(question)
|
||||
|
||||
class BotParser(object):
|
||||
'''Base class for bot parser'''
|
||||
def __init__(self, reply, query):
|
||||
self.query = query
|
||||
self.reply = reply
|
||||
self.tables = reply.tables
|
||||
self.doctype_names = reply.doctype_names
|
||||
|
||||
def has(self, *words):
|
||||
'''return True if any of the words is present int the query'''
|
||||
for word in words:
|
||||
if re.search(r'\b{0}\b'.format(word), self.query):
|
||||
return True
|
||||
|
||||
def startswith(self, *words):
|
||||
'''return True if the query starts with any of the given words'''
|
||||
for w in words:
|
||||
if self.query.startswith(w):
|
||||
return True
|
||||
|
||||
def strip_words(self, query, *words):
|
||||
'''Remove the given words from the query'''
|
||||
for word in words:
|
||||
query = re.sub(r'\b{0}\b'.format(word), '', query)
|
||||
|
||||
return query.strip()
|
||||
|
||||
def format_list(self, data):
|
||||
'''Format list as markdown'''
|
||||
return _('I found these:') + ' ' + ', '.join(' [{title}](/app/Form/{doctype}/{name})'.format(
|
||||
title = d.title or d.name,
|
||||
doctype=self.get_doctype(),
|
||||
name=d.name) for d in data)
|
||||
|
||||
def get_doctype(self):
|
||||
'''returns the doctype name from self.tables'''
|
||||
return self.doctype_names[self.tables[0]]
|
||||
|
||||
class ShowNotificationBot(BotParser):
|
||||
'''Show open notifications'''
|
||||
def get_reply(self):
|
||||
if self.has("whatsup", "what's up", "wassup", "whats up", 'notifications', 'open tasks'):
|
||||
n = get_notifications()
|
||||
open_items = sorted(n.get('open_count_doctype').items())
|
||||
|
||||
if open_items:
|
||||
return ("Following items need your attention:\n\n"
|
||||
+ "\n\n".join("{0} [{1}](/app/List/{1})".format(d[1], d[0])
|
||||
for d in open_items if d[1] > 0))
|
||||
else:
|
||||
return 'Take it easy, nothing urgent needs your attention'
|
||||
|
||||
class GetOpenListBot(BotParser):
|
||||
'''Get list of open items'''
|
||||
def get_reply(self):
|
||||
if self.startswith('open', 'show open', 'list open', 'get open'):
|
||||
if self.tables:
|
||||
doctype = self.get_doctype()
|
||||
from frappe.desk.notifications import get_notification_config
|
||||
filters = get_notification_config().get('for_doctype').get(doctype, None)
|
||||
if filters:
|
||||
if isinstance(filters, dict):
|
||||
data = frappe.get_list(doctype, filters=filters)
|
||||
else:
|
||||
data = [{'name':d[0], 'title':d[1]} for d in frappe.get_attr(filters)(as_list=True)]
|
||||
|
||||
return ", ".join('[{title}](/app/Form/{doctype}/{name})'.format(doctype=doctype,
|
||||
name=d.get('name'), title=d.get('title') or d.get('name')) for d in data)
|
||||
else:
|
||||
return _("Can't identify open {0}. Try something else.").format(doctype)
|
||||
|
||||
class ListBot(BotParser):
|
||||
def get_reply(self):
|
||||
if self.query.endswith(' ' + _('list')) and self.startswith(_('list')):
|
||||
self.query = _('list') + ' ' + self.query.replace(' ' + _('list'), '')
|
||||
if self.startswith(_('list'), _('show')):
|
||||
like = None
|
||||
if ' ' + _('like') + ' ' in self.query:
|
||||
self.query, like = self.query.split(' ' + _('like') + ' ')
|
||||
|
||||
self.tables = self.reply.identify_tables(self.query.split(None, 1)[1])
|
||||
if self.tables:
|
||||
doctype = self.get_doctype()
|
||||
meta = frappe.get_meta(doctype)
|
||||
fields = ['name']
|
||||
if meta.title_field:
|
||||
fields.append('`{0}` as title'.format(meta.title_field))
|
||||
|
||||
filters = {}
|
||||
if like:
|
||||
filters={
|
||||
meta.title_field or 'name': ('like', '%' + like + '%')
|
||||
}
|
||||
return self.format_list(frappe.get_list(self.get_doctype(), fields=fields, filters=filters))
|
||||
|
||||
class CountBot(BotParser):
|
||||
def get_reply(self):
|
||||
if self.startswith('how many'):
|
||||
self.tables = self.reply.identify_tables(self.query.split(None, 1)[1])
|
||||
if self.tables:
|
||||
return str(frappe.db.sql('select count(*) from `tab{0}`'.format(self.get_doctype()))[0][0])
|
||||
|
||||
class FindBot(BotParser):
|
||||
def get_reply(self):
|
||||
if self.startswith('find', 'search'):
|
||||
query = self.query.split(None, 1)[1]
|
||||
|
||||
if self.has('from'):
|
||||
text, table = query.split('from')
|
||||
|
||||
if self.has('in'):
|
||||
text, table = query.split('in')
|
||||
|
||||
if table:
|
||||
text = text.strip()
|
||||
self.tables = self.reply.identify_tables(table.strip())
|
||||
|
||||
|
||||
if self.tables:
|
||||
filters = {'name': ('like', '%{0}%'.format(text))}
|
||||
or_filters = None
|
||||
|
||||
title_field = frappe.get_meta(self.get_doctype()).title_field
|
||||
if title_field and title_field!='name':
|
||||
or_filters = {'title': ('like', '%{0}%'.format(text))}
|
||||
|
||||
data = frappe.get_list(self.get_doctype(),
|
||||
filters=filters, or_filters=or_filters)
|
||||
if data:
|
||||
return self.format_list(data)
|
||||
else:
|
||||
return _("Could not find {0} in {1}").format(text, self.get_doctype())
|
||||
|
||||
else:
|
||||
self.out = _("Could not identify {0}").format(table)
|
||||
else:
|
||||
self.out = _("You can find things by asking 'find orange in customers'").format(table)
|
||||
|
||||
class BotReply(object):
|
||||
'''Build a reply for the bot by calling all parsers'''
|
||||
def __init__(self):
|
||||
self.tables = []
|
||||
|
||||
def get_reply(self, query):
|
||||
self.query = query.lower()
|
||||
self.setup()
|
||||
self.pre_process()
|
||||
|
||||
# basic replies
|
||||
if self.query.split()[0] in ("hello", "hi"):
|
||||
return _("Hello {0}").format(frappe.utils.get_fullname())
|
||||
|
||||
if self.query == "help":
|
||||
return help_text.format(frappe.utils.get_fullname())
|
||||
|
||||
# build using parsers
|
||||
replies = []
|
||||
for parser in frappe.get_hooks('bot_parsers'):
|
||||
reply = None
|
||||
try:
|
||||
reply = frappe.get_attr(parser)(self, query).get_reply()
|
||||
except frappe.PermissionError:
|
||||
reply = _("Oops, you are not allowed to know that")
|
||||
|
||||
if reply:
|
||||
replies.append(reply)
|
||||
|
||||
if replies:
|
||||
return '\n\n'.join(replies)
|
||||
|
||||
if not reply:
|
||||
return _("Don't know, ask 'help'")
|
||||
|
||||
def setup(self):
|
||||
self.setup_tables()
|
||||
self.identify_tables()
|
||||
|
||||
def pre_process(self):
|
||||
if self.query.endswith("?"):
|
||||
self.query = self.query[:-1]
|
||||
|
||||
if self.query in ("todo", "to do"):
|
||||
self.query = "open todo"
|
||||
|
||||
def setup_tables(self):
|
||||
tables = frappe.get_all("DocType", {"istable": 0})
|
||||
self.all_tables = [d.name.lower() for d in tables]
|
||||
self.doctype_names = {d.name.lower():d.name for d in tables}
|
||||
|
||||
def identify_tables(self, query=None):
|
||||
if not query:
|
||||
query = self.query
|
||||
self.tables = []
|
||||
for t in self.all_tables:
|
||||
if t in query or t[:-1] in query:
|
||||
self.tables.append(t)
|
||||
|
||||
return self.tables
|
||||
|
||||
|
||||
|
||||
help_text = """Hello {0}, I am a K.I.S.S Bot, not AI, so be kind. I can try answering a few questions like,
|
||||
|
||||
- "todo": list my todos
|
||||
- "show customers": list customers
|
||||
- "show customers like giant": list customer containing giant
|
||||
- "locate shirt": find where to find item "shirt"
|
||||
- "open issues": find open issues, try "open sales orders"
|
||||
- "how many users": count number of users
|
||||
- "find asian in sales orders": find sales orders where name or title has "asian"
|
||||
|
||||
have fun!
|
||||
"""
|
||||
Loading…
Add table
Reference in a new issue