From 965238124a2f56aa7f9077746abb7e7eaec2136a Mon Sep 17 00:00:00 2001 From: loarsaw Date: Sun, 24 Aug 2025 19:20:01 +0530 Subject: [PATCH 1/6] chore: mentioning bot in the group and cron jobs --- raven/ai/ai.py | 55 ++++++++++ raven/api/raven_channel.py | 5 + raven/hooks.py | 8 +- .../doctype/raven_message/raven_message.py | 103 +++++++++++++++++- raven/scheduler/morning_reminder.py | 20 ++++ raven/utils.py | 16 +++ 6 files changed, 201 insertions(+), 6 deletions(-) create mode 100644 raven/scheduler/morning_reminder.py diff --git a/raven/ai/ai.py b/raven/ai/ai.py index 0250071a5..1b86038c1 100644 --- a/raven/ai/ai.py +++ b/raven/ai/ai.py @@ -28,6 +28,61 @@ def handle_bot_dm(message, bot): return handle_bot_dm_with_assistants(message, bot) +def push_message_to_channel(channel_id, text, is_bot_message=False, bot_user=None): + message = frappe.get_doc({ + "doctype": "Raven Message", + "channel_id": channel_id, + "text": text, + "message_type": "Text", + "is_bot_message": is_bot_message, + "bot": bot_user if is_bot_message else None, + }) + message.insert(ignore_permissions=True) + message.save() + + +# def handle_ai_message_on_mention(self): +# if self.is_bot_message: +# return + +# raven_settings = frappe.get_cached_doc("Raven Settings") +# if not raven_settings.enable_ai_integration: +# return + +# channel_doc = frappe.get_cached_doc("Raven Channel", self.channel_id) + +# if channel_doc.is_ai_thread: +# frappe.enqueue(handle_ai_thread_message, message=self, channel=channel_doc) +# return + +# # if channel_doc.is_direct_message: +# # peer_user = get_peer_user(self.channel_id, True) +# # if peer_user and peer_user.get("type") == "Bot": +# # bot = frappe.get_cached_doc("Raven Bot", peer_user.get("user_id")) +# # if bot.is_ai_bot: +# # frappe.enqueue(handle_bot_dm, message=self, bot=bot) +# # return + +# if not channel_doc.is_direct_message: +# if self.text and any(cmd in self.text.lower() for cmd in ["@HelloBot"]): +# bot_name = "@HelloBot" +# bot = frappe.get_doc("Raven Bot", bot_name) + +# # push_message_to_channel( +# # channel_id=self.channel_id, +# # text="Pre", +# # is_bot_message=True, +# # bot_user=bot.name, +# # ) + +# frappe.enqueue( +# process_message_with_agent, +# message=self, +# bot=bot, +# channel_id=self.channel_id, +# is_new_conversation=True, +# ) + def handle_bot_dm_with_agents(message, bot): """ Handle direct messages using Agents SDK. diff --git a/raven/api/raven_channel.py b/raven/api/raven_channel.py index 70a36e0dd..9b23fd9b5 100644 --- a/raven/api/raven_channel.py +++ b/raven/api/raven_channel.py @@ -107,6 +107,10 @@ def get_channels(hide_archived=False): return channels +# def get_ + + + def get_peer_user(channel_id: str, is_direct_message: int, is_self_message: bool = False) -> dict: """ For a given channel, fetches the peer's member object @@ -122,6 +126,7 @@ def get_peer_user(channel_id: str, is_direct_message: int, is_self_message: bool for member in members: if member != frappe.session.user: + # print(members[member] , "membersss") return members[member] return None diff --git a/raven/hooks.py b/raven/hooks.py index 8b18adc62..193c33b8f 100644 --- a/raven/hooks.py +++ b/raven/hooks.py @@ -147,7 +147,8 @@ "on_trash": "raven.raven_integrations.controllers.department.on_trash", }, "Employee": { - "after_insert": "raven.raven_integrations.controllers.employee.after_insert", + "after_insert": "rav" + "en.raven_integrations.controllers.employee.after_insert", "on_update": "raven.raven_integrations.controllers.employee.on_update", "on_trash": "raven.raven_integrations.controllers.employee.on_trash", }, @@ -158,8 +159,9 @@ scheduler_events = { "cron": { - # run every 5 minutes - "*/5 * * * *": ["raven.scheduler.close_expired_polls.close_expired_polls"] + "*/5 * * * *": ["raven.scheduler.close_expired_polls.close_expired_polls"], + "0 10 * * *": ["raven.scheduler.morning_reminder.post_work_plan"], + "0 19 * * *":["raven.scheduler.morning_reminder.post_work_update"] } } diff --git a/raven/raven_messaging/doctype/raven_message/raven_message.py b/raven/raven_messaging/doctype/raven_message/raven_message.py index 4224b15fa..991ee68b0 100644 --- a/raven/raven_messaging/doctype/raven_message/raven_message.py +++ b/raven/raven_messaging/doctype/raven_message/raven_message.py @@ -25,6 +25,47 @@ ) + +def handle_bot_command(message, bot): + command = message.text.strip().lower() + bs = BeautifulSoup(command, 'html.parser') + text = bs.get_text() + command_map = { + "/work_plan": "Update Your Todays Work Plan ", + "/work_update": "Hello Mention your Work Update.", + + } + + if text == "/work_plan": + from frappe.utils import now + reply = f"The current server time is: {now()}" + else: + reply = command_map.get(text, "Unknown command. Try /help") + + send_bot_message(bot=bot.name, to_channel=message.channel_id, content=reply) + + + +def send_bot_message(bot, to_channel, content): + bot_user = frappe.get_cached_value("Raven Bot", bot, "bot_user") + + frappe.get_doc({ + "doctype": "Raven Message", + "channel_id": to_channel, + "text": content, + "content": content, + "message_type": "Text", + "is_bot_message": 1, + "bot": bot, + "owner": bot_user, + }).insert(ignore_permissions=True) + + + frappe.db.commit() + + + + class RavenMessage(Document): # begin: auto-generated types # ruff: noqa @@ -198,14 +239,18 @@ def after_insert(self): if self.message_type == "Text": self.handle_ai_message() + self.handle_mention_on_group() self.send_push_notification() def handle_ai_message(self): # If the message was sent by a bot, do not call the function + + # print("inside of ai message" ,self.as_dict()) if self.is_bot_message: return + # If AI Integration is not enabled, do not call the function raven_settings = frappe.get_cached_doc("Raven Settings") @@ -235,7 +280,6 @@ def handle_ai_message(self): is_dm = channel_doc.is_direct_message # Only DMs to bots need to be handled (for now) - if not is_dm: return @@ -252,9 +296,61 @@ def handle_ai_message(self): return bot = frappe.get_cached_doc("Raven Bot", peer_user_doc.bot) - if not bot.is_ai_bot: + # print(self.as_dict()) + # for key, value in self.items(): + # print(f"{key}: {value}") + # return + + handle_bot_command(message=self , bot=bot) + # return + + frappe.enqueue( + method=handle_bot_dm, + message=self, + bot=bot, + timeout=600, + job_name="handle_bot_dm", + at_front=True, + ) + + def handle_mention_on_group(self): + print(self.as_dict()) + + if self.is_bot_message: return + + + raven_settings = frappe.get_cached_doc("Raven Settings") + if not raven_settings.enable_ai_integration: + return + + + channel_doc = frappe.get_cached_doc("Raven Channel", self.channel_id) + + is_ai_thread = channel_doc.is_ai_thread + + if is_ai_thread: + frappe.enqueue( + method=handle_ai_thread_message, + message=self, + timeout=600, + channel=channel_doc, + at_front=True, + job_name="handle_ai_thread_message", + ) + + return + + bot = frappe.get_cached_doc("Raven Bot", self.bot) + if not bot.is_ai_bot: + # print(self.as_dict()) + # for key, value in self.items(): + # print(f"{key}: {value}") + # return + + handle_bot_command(message=self , bot=bot) + # return frappe.enqueue( method=handle_bot_dm, @@ -265,9 +361,10 @@ def handle_ai_message(self): at_front=True, ) + + def set_last_message_timestamp(self): - # Update directly via SQL since we do not want to invalidate the document cache message_details = json.dumps( { "message_id": self.name, diff --git a/raven/scheduler/morning_reminder.py b/raven/scheduler/morning_reminder.py new file mode 100644 index 000000000..abc0e4ee3 --- /dev/null +++ b/raven/scheduler/morning_reminder.py @@ -0,0 +1,20 @@ + +import frappe +from raven.utils import push_message_to_channel + +def post_work_plan(): + channel_id = "#general" + text = "Daily Work Plan:\nPlease update your Work Plan for today!" + + bot_user_id = "HelloBot" + push_message_to_channel(channel_id, text, is_bot_message=True, bot_user=bot_user_id) + + + +def post_work_update(): + channel_id = "#general" + text = "Daily Work Plan:\nPlease update your tasks for today!" + + bot_user_id = "HelloBot" + push_message_to_channel(channel_id, text, is_bot_message=True, bot_user=bot_user_id) + diff --git a/raven/utils.py b/raven/utils.py index 261083d66..195ab6f29 100644 --- a/raven/utils.py +++ b/raven/utils.py @@ -213,3 +213,19 @@ def clear_thread_reply_count_cache(thread_id: str): Clear the thread reply count cache """ frappe.cache().hdel("raven:thread_reply_count", thread_id) + + +# your_app/utils.py + +def push_message_to_channel(channel_id, text, is_bot_message=False, bot_user=None): + doc = frappe.get_doc({ + "doctype": "Raven Message", + "channel_id": channel_id, + "text": text, + "message_type": "Text", + "is_bot_message": is_bot_message, + "bot": bot_user if is_bot_message else None, + }) + + + doc.insert(ignore_permissions=True) From f7505ecb39c06db5d4ef19ea8cabc064eaeb674a Mon Sep 17 00:00:00 2001 From: loarsaw Date: Sun, 24 Aug 2025 19:44:16 +0530 Subject: [PATCH 2/6] cleanup --- raven/ai/ai.py | 41 ------------------- .../doctype/raven_message/raven_message.py | 14 +------ 2 files changed, 2 insertions(+), 53 deletions(-) diff --git a/raven/ai/ai.py b/raven/ai/ai.py index 1b86038c1..9846d111e 100644 --- a/raven/ai/ai.py +++ b/raven/ai/ai.py @@ -41,47 +41,6 @@ def push_message_to_channel(channel_id, text, is_bot_message=False, bot_user=Non message.save() -# def handle_ai_message_on_mention(self): -# if self.is_bot_message: -# return - -# raven_settings = frappe.get_cached_doc("Raven Settings") -# if not raven_settings.enable_ai_integration: -# return - -# channel_doc = frappe.get_cached_doc("Raven Channel", self.channel_id) - -# if channel_doc.is_ai_thread: -# frappe.enqueue(handle_ai_thread_message, message=self, channel=channel_doc) -# return - -# # if channel_doc.is_direct_message: -# # peer_user = get_peer_user(self.channel_id, True) -# # if peer_user and peer_user.get("type") == "Bot": -# # bot = frappe.get_cached_doc("Raven Bot", peer_user.get("user_id")) -# # if bot.is_ai_bot: -# # frappe.enqueue(handle_bot_dm, message=self, bot=bot) -# # return - -# if not channel_doc.is_direct_message: -# if self.text and any(cmd in self.text.lower() for cmd in ["@HelloBot"]): -# bot_name = "@HelloBot" -# bot = frappe.get_doc("Raven Bot", bot_name) - -# # push_message_to_channel( -# # channel_id=self.channel_id, -# # text="Pre", -# # is_bot_message=True, -# # bot_user=bot.name, -# # ) - -# frappe.enqueue( -# process_message_with_agent, -# message=self, -# bot=bot, -# channel_id=self.channel_id, -# is_new_conversation=True, -# ) def handle_bot_dm_with_agents(message, bot): """ diff --git a/raven/raven_messaging/doctype/raven_message/raven_message.py b/raven/raven_messaging/doctype/raven_message/raven_message.py index 991ee68b0..9d2750518 100644 --- a/raven/raven_messaging/doctype/raven_message/raven_message.py +++ b/raven/raven_messaging/doctype/raven_message/raven_message.py @@ -275,7 +275,6 @@ def handle_ai_message(self): return - # If not a part of a AI Thread, then check if this is a DM to a bot - if yes, then we should create a new thread is_dm = channel_doc.is_direct_message @@ -297,13 +296,9 @@ def handle_ai_message(self): bot = frappe.get_cached_doc("Raven Bot", peer_user_doc.bot) if not bot.is_ai_bot: - # print(self.as_dict()) - # for key, value in self.items(): - # print(f"{key}: {value}") - # return + handle_bot_command(message=self , bot=bot) - # return frappe.enqueue( method=handle_bot_dm, @@ -344,13 +339,8 @@ def handle_mention_on_group(self): bot = frappe.get_cached_doc("Raven Bot", self.bot) if not bot.is_ai_bot: - # print(self.as_dict()) - # for key, value in self.items(): - # print(f"{key}: {value}") - # return - + handle_bot_command(message=self , bot=bot) - # return frappe.enqueue( method=handle_bot_dm, From a43f25fcc195ac79dd6546d836fabb7044b95d8d Mon Sep 17 00:00:00 2001 From: loarsaw Date: Mon, 25 Aug 2025 12:10:08 +0530 Subject: [PATCH 3/6] chore: enbled commands for the bot --- .../doctype/raven_message/raven_message.py | 70 +++++++++++++------ 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/raven/raven_messaging/doctype/raven_message/raven_message.py b/raven/raven_messaging/doctype/raven_message/raven_message.py index 9d2750518..d24d25964 100644 --- a/raven/raven_messaging/doctype/raven_message/raven_message.py +++ b/raven/raven_messaging/doctype/raven_message/raven_message.py @@ -2,13 +2,14 @@ # For license information, please see license.txt import datetime import json - +import re import frappe from bs4 import BeautifulSoup from frappe import _ from frappe.model.document import Document from frappe.utils import get_datetime, get_system_timezone from pytz import timezone, utc +from frappe.utils import now from raven.ai.ai import handle_ai_thread_message, handle_bot_dm from raven.api.raven_channel import get_peer_user @@ -27,20 +28,35 @@ def handle_bot_command(message, bot): - command = message.text.strip().lower() - bs = BeautifulSoup(command, 'html.parser') - text = bs.get_text() + bot_command = None + hornet = message.json["content"][0]["content"] + print(hornet) + # print(message.as_dict()) + + for item in hornet: + if item.get("type") == "text": + bot_command = item.get("text") + + reply = '' + command_map = { "/work_plan": "Update Your Todays Work Plan ", "/work_update": "Hello Mention your Work Update.", + "/help" : "List of Available Commands
1. /work_plan ie @HelloBot /work_update Working on Issue Number #34
2. /work_update\n @HelloBot /work_update Completed Issue number #34
" } - - if text == "/work_plan": - from frappe.utils import now - reply = f"The current server time is: {now()}" + print(bot_command , "bot_command") + print(repr(bot_command), "repr(bot_command)") + + if bot_command.strip() == "/work_plan": + reply = f"{command_map[bot_command.strip()]}" + elif bot_command.strip() == "/work_update": + reply = f"{command_map[bot_command.strip()]}" + elif bot_command.strip() == "/help": + reply = f"{command_map[bot_command.strip()]}" else: - reply = command_map.get(text, "Unknown command. Try /help") + reply = command_map.get(bot_command, f"{bot_command} Unknown command. Try /help") + # print(reply , bot , bot.as_dict(), "reponse against mention") send_bot_message(bot=bot.name, to_channel=message.channel_id, content=reply) @@ -296,9 +312,10 @@ def handle_ai_message(self): bot = frappe.get_cached_doc("Raven Bot", peer_user_doc.bot) if not bot.is_ai_bot: + return - handle_bot_command(message=self , bot=bot) + # handle_bot_command(message=self , bot=bot) frappe.enqueue( method=handle_bot_dm, @@ -310,7 +327,7 @@ def handle_ai_message(self): ) def handle_mention_on_group(self): - print(self.as_dict()) + # print(self.as_dict()) if self.is_bot_message: return @@ -336,20 +353,27 @@ def handle_mention_on_group(self): ) return + mentioned_bot = self.content + bot_pattern = re.search(r'@(\w+)' , mentioned_bot) + if bot_pattern: + bot_name = bot_pattern.group(1) + # print(name) + bot = frappe.get_cached_doc("Raven Bot", bot_name) + print(bot.as_dict()) + if not bot.is_ai_bot: + handle_bot_command(message=self , bot=bot) + # frappe.enqueue( + # method=handle_bot_dm, + # message=self, + # bot=bot, + # timeout=600, + # job_name="handle_bot_dm", + # at_front=True, + # ) - bot = frappe.get_cached_doc("Raven Bot", self.bot) - if not bot.is_ai_bot: - handle_bot_command(message=self , bot=bot) - - frappe.enqueue( - method=handle_bot_dm, - message=self, - bot=bot, - timeout=600, - job_name="handle_bot_dm", - at_front=True, - ) + # print(bot_name) + From 325829902022ac7c075596d569d4d2fdd62d6341 Mon Sep 17 00:00:00 2001 From: loarsaw Date: Mon, 25 Aug 2025 14:38:23 +0530 Subject: [PATCH 4/6] chore: workflow for workplan and workupdates --- raven/hooks.py | 4 +- .../doctype/raven_message/raven_message.py | 63 +++++++++++++++---- ...ng_reminder.py => work_update_reminder.py} | 25 +++++++- raven/utils.py | 11 +++- 4 files changed, 86 insertions(+), 17 deletions(-) rename raven/scheduler/{morning_reminder.py => work_update_reminder.py} (52%) diff --git a/raven/hooks.py b/raven/hooks.py index 193c33b8f..31af5c2b8 100644 --- a/raven/hooks.py +++ b/raven/hooks.py @@ -160,8 +160,8 @@ scheduler_events = { "cron": { "*/5 * * * *": ["raven.scheduler.close_expired_polls.close_expired_polls"], - "0 10 * * *": ["raven.scheduler.morning_reminder.post_work_plan"], - "0 19 * * *":["raven.scheduler.morning_reminder.post_work_update"] + "0 10 * * *": ["raven.scheduler.work_update_reminder.post_work_plan"], + "0 19 * * *":["raven.scheduler.work_update_reminder.post_work_update"] } } diff --git a/raven/raven_messaging/doctype/raven_message/raven_message.py b/raven/raven_messaging/doctype/raven_message/raven_message.py index d24d25964..67794f2ad 100644 --- a/raven/raven_messaging/doctype/raven_message/raven_message.py +++ b/raven/raven_messaging/doctype/raven_message/raven_message.py @@ -3,6 +3,7 @@ import datetime import json import re +from datetime import datetime import frappe from bs4 import BeautifulSoup from frappe import _ @@ -10,6 +11,7 @@ from frappe.utils import get_datetime, get_system_timezone from pytz import timezone, utc from frappe.utils import now +from raven.utils import create_doc from raven.ai.ai import handle_ai_thread_message, handle_bot_dm from raven.api.raven_channel import get_peer_user @@ -26,16 +28,37 @@ ) +def extract_message(text, command): + if text.startswith(command): + return text[len(command):].strip() + return text + + + +def bifurcate(text): + if not text.startswith("/"): + return None, text + + parts = text.split(" ", 1) + command = parts[0] + message = parts[1].strip() if len(parts) > 1 else "" + return command, message + def handle_bot_command(message, bot): + bot_command_raw = None bot_command = None hornet = message.json["content"][0]["content"] - print(hornet) - # print(message.as_dict()) - + print(message.as_dict() , "messsage") + update_message = None for item in hornet: if item.get("type") == "text": - bot_command = item.get("text") + bot_command_raw = item.get("text").strip() + # print(bot_command_raw , "bot command raw") + + bot_command , update_message = bifurcate(bot_command_raw) + + print(bot_command , "bot command" , update_message , "Bifurcation Result") reply = '' @@ -43,15 +66,31 @@ def handle_bot_command(message, bot): "/work_plan": "Update Your Todays Work Plan ", "/work_update": "Hello Mention your Work Update.", "/help" : "List of Available Commands
1. /work_plan ie @HelloBot /work_update Working on Issue Number #34
2. /work_update\n @HelloBot /work_update Completed Issue number #34
" - } - print(bot_command , "bot_command") - print(repr(bot_command), "repr(bot_command)") - - if bot_command.strip() == "/work_plan": - reply = f"{command_map[bot_command.strip()]}" + if bot_command is None: + reply = "Enter Commands type /work_update or /work_plan" + elif update_message is None or update_message == "": + reply = "Enter Update" + elif len(update_message)<10: + reply = "Length of Messge cannot be less than 10 Characters" + elif bot_command.strip() == "/work_plan": + create_doc({ + "log_time":datetime.now(), + "email":message.owner, + "work_update":update_message, + "type":"Plan" + }) + reply = f"Submitted Work Plan Approved {message.owner}" + elif bot_command.strip() == "/work_update": - reply = f"{command_map[bot_command.strip()]}" + + create_doc({ + "log_time":datetime.now(), + "email":message.owner, + "work_update":update_message, + "type":"Update" + }) + reply = f"Submitted Work Update Approved {message.owner}" elif bot_command.strip() == "/help": reply = f"{command_map[bot_command.strip()]}" else: @@ -359,7 +398,7 @@ def handle_mention_on_group(self): bot_name = bot_pattern.group(1) # print(name) bot = frappe.get_cached_doc("Raven Bot", bot_name) - print(bot.as_dict()) + # print(bot.as_dict()) if not bot.is_ai_bot: handle_bot_command(message=self , bot=bot) # frappe.enqueue( diff --git a/raven/scheduler/morning_reminder.py b/raven/scheduler/work_update_reminder.py similarity index 52% rename from raven/scheduler/morning_reminder.py rename to raven/scheduler/work_update_reminder.py index abc0e4ee3..d37ba9387 100644 --- a/raven/scheduler/morning_reminder.py +++ b/raven/scheduler/work_update_reminder.py @@ -1,9 +1,10 @@ import frappe +from datetime import datetime from raven.utils import push_message_to_channel def post_work_plan(): - channel_id = "#general" + channel_id = "updates" text = "Daily Work Plan:\nPlease update your Work Plan for today!" bot_user_id = "HelloBot" @@ -12,9 +13,29 @@ def post_work_plan(): def post_work_update(): - channel_id = "#general" + channel_id = "updates" text = "Daily Work Plan:\nPlease update your tasks for today!" bot_user_id = "HelloBot" push_message_to_channel(channel_id, text, is_bot_message=True, bot_user=bot_user_id) + +def get_todays_work_updates(): + + today = datetime.today().date() + + filters = { + "creation": [">=", f"{today} 00:00:00"], + "creation": ["<=", f"{today} 23:59:59"] + } + + + + entries = frappe.get_all( + "Work Updates", + filters=filters, + fields=["name", "email"], + order_by="creation desc" + ) + + return entries diff --git a/raven/utils.py b/raven/utils.py index 195ab6f29..460822db5 100644 --- a/raven/utils.py +++ b/raven/utils.py @@ -215,7 +215,6 @@ def clear_thread_reply_count_cache(thread_id: str): frappe.cache().hdel("raven:thread_reply_count", thread_id) -# your_app/utils.py def push_message_to_channel(channel_id, text, is_bot_message=False, bot_user=None): doc = frappe.get_doc({ @@ -229,3 +228,13 @@ def push_message_to_channel(channel_id, text, is_bot_message=False, bot_user=Non doc.insert(ignore_permissions=True) + +def create_doc(data, ignore_permissions=False): + + doc = frappe.get_doc({ + "doctype": "Work Updates", + **data + }) + + doc.insert(ignore_permissions=ignore_permissions) + return doc From 620762dda3d5ab5e128363b69da550354b4e1571 Mon Sep 17 00:00:00 2001 From: loarsaw Date: Tue, 26 Aug 2025 14:08:18 +0530 Subject: [PATCH 5/6] chore: updating the saame child table for appending same type of the updates --- raven/ai/ai.py | 3 + .../doctype/raven_message/raven_message.py | 91 ++++++++++++++----- raven/utils.py | 56 +++++++++++- 3 files changed, 127 insertions(+), 23 deletions(-) diff --git a/raven/ai/ai.py b/raven/ai/ai.py index 9846d111e..50842096f 100644 --- a/raven/ai/ai.py +++ b/raven/ai/ai.py @@ -13,6 +13,9 @@ ) + + + def handle_bot_dm(message, bot): """ Function to handle direct messages to the bot. diff --git a/raven/raven_messaging/doctype/raven_message/raven_message.py b/raven/raven_messaging/doctype/raven_message/raven_message.py index 67794f2ad..98ce7a5b8 100644 --- a/raven/raven_messaging/doctype/raven_message/raven_message.py +++ b/raven/raven_messaging/doctype/raven_message/raven_message.py @@ -3,6 +3,7 @@ import datetime import json import re +from datetime import date , time from datetime import datetime import frappe from bs4 import BeautifulSoup @@ -48,19 +49,27 @@ def bifurcate(text): def handle_bot_command(message, bot): bot_command_raw = None bot_command = None + today = date.today() hornet = message.json["content"][0]["content"] - print(message.as_dict() , "messsage") + response_output = False update_message = None + s_reply = f"Update Submitted Successfully" + for item in hornet: if item.get("type") == "text": bot_command_raw = item.get("text").strip() - # print(bot_command_raw , "bot command raw") bot_command , update_message = bifurcate(bot_command_raw) - print(bot_command , "bot command" , update_message , "Bifurcation Result") reply = '' + existing = frappe.get_list( + "Work Updates", + filters={"email": message.owner, "log_date": today}, + fields=["name"] + ) + print(existing , "existing values are in this module") + command_map = { "/work_plan": "Update Your Todays Work Plan ", @@ -75,29 +84,57 @@ def handle_bot_command(message, bot): reply = "Length of Messge cannot be less than 10 Characters" elif bot_command.strip() == "/work_plan": create_doc({ - "log_time":datetime.now(), + "log_date":date.today(), "email":message.owner, - "work_update":update_message, + "table_fkqe":[{ + "task":update_message, + "time_log":datetime.now().time() + + }], "type":"Plan" }) - reply = f"Submitted Work Plan Approved {message.owner}" + response_output = True + if len(existing)>=1: + reply = f"Work Plan Updated {message.owner}" + else: + reply = f"Submitted Work Update Approved {message.owner}" + + # reply = f"Submitted Work Plan Approved {message.owner}" elif bot_command.strip() == "/work_update": create_doc({ - "log_time":datetime.now(), + "log_date":date.today(), "email":message.owner, - "work_update":update_message, + "table_fkqe":[{ + "task":update_message, + "time_log":datetime.now().time() + + }], "type":"Update" }) - reply = f"Submitted Work Update Approved {message.owner}" + response_output = True + + if len(existing)>=1: + reply = f"Work Updated List Updated {message.owner}" + else: + reply = f"Submitted Work Update Approved {message.owner}" + + elif bot_command.strip() == "/help": reply = f"{command_map[bot_command.strip()]}" else: reply = command_map.get(bot_command, f"{bot_command} Unknown command. Try /help") - # print(reply , bot , bot.as_dict(), "reponse against mention") - send_bot_message(bot=bot.name, to_channel=message.channel_id, content=reply) + if response_output: + send_bot_message(bot=bot.name, to_channel="Technoculture-updates", content=reply) + send_bot_message(bot=bot.name, to_channel=message.channel_id, content=s_reply) + + else: + send_bot_message(bot=bot.name, to_channel=message.channel_id, content=reply) + + + @@ -293,8 +330,9 @@ def after_insert(self): self.publish_unread_count_event(last_message_details) if self.message_type == "Text": - self.handle_ai_message() - self.handle_mention_on_group() + # self.handle_ai_message() + self.update_on_group() + # self. self.send_push_notification() @@ -302,7 +340,6 @@ def handle_ai_message(self): # If the message was sent by a bot, do not call the function - # print("inside of ai message" ,self.as_dict()) if self.is_bot_message: return @@ -365,19 +402,22 @@ def handle_ai_message(self): at_front=True, ) - def handle_mention_on_group(self): + + def update_on_group(self): # print(self.as_dict()) + # print(self.channel_id , "channel ID") if self.is_bot_message: return - + # print("BOT Values") + raven_settings = frappe.get_cached_doc("Raven Settings") if not raven_settings.enable_ai_integration: return - channel_doc = frappe.get_cached_doc("Raven Channel", self.channel_id) + channel_doc = frappe.get_cached_doc("Raven Channel", "Technoculture-updates") is_ai_thread = channel_doc.is_ai_thread @@ -393,12 +433,17 @@ def handle_mention_on_group(self): return mentioned_bot = self.content + channel_vlue = frappe.get_doc("Raven Channel" ,self.channel_id) + + # print(channel_vlue.as_dict()) + bot_name = channel_vlue.channel_name.split(" _ ")[1] bot_pattern = re.search(r'@(\w+)' , mentioned_bot) - if bot_pattern: - bot_name = bot_pattern.group(1) - # print(name) + # print(bot_name , "hot Name") + # print(bot_pattern , "pattern") + if bot_name: + # bot_name = bot_pattern.group(1) bot = frappe.get_cached_doc("Raven Bot", bot_name) - # print(bot.as_dict()) + print(bot ,"botter") if not bot.is_ai_bot: handle_bot_command(message=self , bot=bot) # frappe.enqueue( @@ -411,7 +456,9 @@ def handle_mention_on_group(self): # ) - # print(bot_name) + + + diff --git a/raven/utils.py b/raven/utils.py index 460822db5..82062620d 100644 --- a/raven/utils.py +++ b/raven/utils.py @@ -229,8 +229,62 @@ def push_message_to_channel(channel_id, text, is_bot_message=False, bot_user=Non doc.insert(ignore_permissions=True) +from datetime import date +import frappe + +def create_doc(data, ignore_permissions=False): + today = date.today() + + # Check if a Work Updates doc already exists for today + existing = frappe.get_list( + "Work Updates", + filters={"employee": data.get("employee"), "date": today}, + fields=["name"] + ) + + if existing: + # Fetch the existing doc + doc = frappe.get_doc("Work Updates", existing[0].name) + + # Option 1: Append new tasks to child table + for task in data.get("work_update_tasks", []): + doc.append("work_update_tasks", task) + + # Optionally update any other fields from data + if "title" in data: + doc.title = data["title"] + + doc.save(ignore_permissions=ignore_permissions) + return doc + + # If not existing, create new + doc = frappe.get_doc({ + "doctype": "Work Updates", + **data + }) + + doc.insert(ignore_permissions=ignore_permissions) + return doc + def create_doc(data, ignore_permissions=False): - + today = date.today() + + existing = frappe.get_list( + "Work Updates", + filters={"email": data["email"], "log_date": today , "type":data["type"]}, + fields=["name"] + ) + + if existing: + doc = frappe.get_doc("Work Updates", existing[0].name) + + for task in data.get("table_fkqe", []): + doc.append("table_fkqe", task) + + doc.save(ignore_permissions=ignore_permissions) + return doc + + # If not existing, create new doc = frappe.get_doc({ "doctype": "Work Updates", **data From e01e83af0a24e53e293280e1af7a7eba8fa0d6ed Mon Sep 17 00:00:00 2001 From: loarsaw Date: Thu, 28 Aug 2025 15:51:02 +0530 Subject: [PATCH 6/6] chore: sending and appending work updates --- .../doctype/raven_message/raven_message.py | 136 ++++++++++++++---- raven/utils.py | 67 ++++++++- 2 files changed, 172 insertions(+), 31 deletions(-) diff --git a/raven/raven_messaging/doctype/raven_message/raven_message.py b/raven/raven_messaging/doctype/raven_message/raven_message.py index 98ce7a5b8..c0701687a 100644 --- a/raven/raven_messaging/doctype/raven_message/raven_message.py +++ b/raven/raven_messaging/doctype/raven_message/raven_message.py @@ -12,7 +12,7 @@ from frappe.utils import get_datetime, get_system_timezone from pytz import timezone, utc from frappe.utils import now -from raven.utils import create_doc +from raven.utils import create_doc , update_doc from raven.ai.ai import handle_ai_thread_message, handle_bot_dm from raven.api.raven_channel import get_peer_user @@ -36,6 +36,10 @@ def extract_message(text, command): +def get_username_by_email(email): + + user = frappe.get_value("Raven User", {"user": email}, "full_name") + return user def bifurcate(text): if not text.startswith("/"): return None, text @@ -48,13 +52,13 @@ def bifurcate(text): def handle_bot_command(message, bot): bot_command_raw = None + print(message.as_dict() , "messages are here") bot_command = None today = date.today() hornet = message.json["content"][0]["content"] response_output = False update_message = None s_reply = f"Update Submitted Successfully" - for item in hornet: if item.get("type") == "text": bot_command_raw = item.get("text").strip() @@ -68,7 +72,6 @@ def handle_bot_command(message, bot): filters={"email": message.owner, "log_date": today}, fields=["name"] ) - print(existing , "existing values are in this module") command_map = { @@ -86,7 +89,8 @@ def handle_bot_command(message, bot): create_doc({ "log_date":date.today(), "email":message.owner, - "table_fkqe":[{ + "c_log_table":[{ + "uid":message.name, "task":update_message, "time_log":datetime.now().time() @@ -94,19 +98,21 @@ def handle_bot_command(message, bot): "type":"Plan" }) response_output = True + username = get_username_by_email(message.owner) if len(existing)>=1: - reply = f"Work Plan Updated {message.owner}" + reply = f"Work Plan Updated @{username} " else: - reply = f"Submitted Work Update Approved {message.owner}" + reply = f"Submitted Work Update Approved @{username} " - # reply = f"Submitted Work Plan Approved {message.owner}" + # reply = f"Submitted Work Plan Approved {message.owner}" elif bot_command.strip() == "/work_update": create_doc({ "log_date":date.today(), "email":message.owner, - "table_fkqe":[{ + "c_log_table":[{ + "uid":message.name, "task":update_message, "time_log":datetime.now().time() @@ -116,9 +122,11 @@ def handle_bot_command(message, bot): response_output = True if len(existing)>=1: - reply = f"Work Updated List Updated {message.owner}" + username = get_username_by_email(message.owner) + reply = f"Work Updated List Updated by @{username} " else: - reply = f"Submitted Work Update Approved {message.owner}" + username = get_username_by_email(message.owner) + reply = f"Submitted Work Update by @{username} " elif bot_command.strip() == "/help": @@ -157,6 +165,24 @@ def send_bot_message(bot, to_channel, content): +def get_updated_chat(json_data): + """Safely extract the updated content from the message JSON.""" + try: + if ( + json_data + and isinstance(json_data, dict) + and isinstance(json_data.get("content"), list) + and len(json_data["content"]) > 0 + and isinstance(json_data["content"][0], dict) + and isinstance(json_data["content"][0].get("content"), list) + ): + return json_data["content"][0]["content"] + except Exception as e: + frappe.log_error(f"Error in get_updated_chat: {e}") + return [] + + + class RavenMessage(Document): # begin: auto-generated types @@ -404,12 +430,10 @@ def handle_ai_message(self): def update_on_group(self): - # print(self.as_dict()) - # print(self.channel_id , "channel ID") - + + if self.is_bot_message: return - # print("BOT Values") raven_settings = frappe.get_cached_doc("Raven Settings") @@ -418,7 +442,7 @@ def update_on_group(self): channel_doc = frappe.get_cached_doc("Raven Channel", "Technoculture-updates") - + # print(self.as_dict() , "working way") is_ai_thread = channel_doc.is_ai_thread if is_ai_thread: @@ -435,25 +459,14 @@ def update_on_group(self): mentioned_bot = self.content channel_vlue = frappe.get_doc("Raven Channel" ,self.channel_id) - # print(channel_vlue.as_dict()) bot_name = channel_vlue.channel_name.split(" _ ")[1] bot_pattern = re.search(r'@(\w+)' , mentioned_bot) - # print(bot_name , "hot Name") - # print(bot_pattern , "pattern") + if bot_name: - # bot_name = bot_pattern.group(1) bot = frappe.get_cached_doc("Raven Bot", bot_name) - print(bot ,"botter") if not bot.is_ai_bot: handle_bot_command(message=self , bot=bot) - # frappe.enqueue( - # method=handle_bot_dm, - # message=self, - # bot=bot, - # timeout=600, - # job_name="handle_bot_dm", - # at_front=True, - # ) + @@ -738,10 +751,75 @@ def publish_deprecated_event_for_desk(self): after_commit=True, ) + def on_update(self): + # print(self.as_dict() ,"hello") + if self.is_edited == 1 or self.is_edited == "1": + bot_command_raw = None + bot_command = None + today = date.today() + response_output = False + update_message = None + s_reply = None + s_reply_channel = None + + updated_chat = get_updated_chat(self.json) + # print(updated_chat , type(updated_chat) , "how are ") + + + for item in updated_chat: + # print("updated once") + # print(item , "Items under review") + if item.get("type") == "text": + bot_command_raw = item.get("text", "").strip() + bot_command, update_message = bifurcate(bot_command_raw) + # print(bot_command , "bot command") + if bot_command.strip() == "/work_plan": + s_reply = "Todays Work Plan has been Updated" + s_reply_channel = f"Todays Work Plan has been updated by {self.owner}" + else: + s_reply = "Todays Work Updated has been Updated" + s_reply_channel = f"Todays Work Update has been updated by {self.owner}" + + + channel_value = frappe.get_doc("Raven Channel", self.channel_id) + bot_name = channel_value.channel_name.split(" _ ")[1] + if bot_name: + bot = frappe.get_cached_doc("Raven Bot", bot_name) + + if not bot.is_ai_bot: + # print(update_message ,"hello" ) + update_doc({ + "log_date": today, + "email": self.owner, + "c_log_table": [{ + "uid": self.name, + "task": update_message, + "time_log": datetime.now().time() + }], + "type": "Plan" + }) + + send_bot_message(bot=bot.name, to_channel=self.channel_id, content=s_reply) + send_bot_message(bot=bot.name, to_channel="Technoculture-updates", content=s_reply_channel) + + + + + + + + + + + + + + + - # TEMP: this is a temp fix for the Desk interface self.publish_deprecated_event_for_desk() + if self.is_edited or self.is_thread or self.flags.editing_metadata: frappe.publish_realtime( diff --git a/raven/utils.py b/raven/utils.py index 82062620d..9031a3fb6 100644 --- a/raven/utils.py +++ b/raven/utils.py @@ -278,8 +278,8 @@ def create_doc(data, ignore_permissions=False): if existing: doc = frappe.get_doc("Work Updates", existing[0].name) - for task in data.get("table_fkqe", []): - doc.append("table_fkqe", task) + for task in data.get("c_log_table", []): + doc.append("c_log_table", task) doc.save(ignore_permissions=ignore_permissions) return doc @@ -292,3 +292,66 @@ def create_doc(data, ignore_permissions=False): doc.insert(ignore_permissions=ignore_permissions) return doc + + +def update_doc(data, ignore_permissions=False): + """ + Upserts a Work Updates document: + - If it exists, update child table rows based on uid + - If not, create a new Work Updates document + - Appends child rows if uid not found + """ + + required_fields = ["uid", "task"] # Add more required fields if needed + today = date.today() + + # Validate input structure + for row in data.get("c_log_table", []): + for field in required_fields: + if not row.get(field): + frappe.throw(f"Missing required field '{field}' in child table row: {row}") + + # Check for existing Work Updates doc + existing = frappe.get_list( + "Work Updates", + filters={ + "email": data["email"], + "log_date": today, + "type": data["type"] + }, + fields=["name"] + ) + + if existing: + doc = frappe.get_doc("Work Updates", existing[0].name) + else: + # Create new Work Updates document + doc = frappe.get_doc({ + "doctype": "Work Updates", + "email": data["email"], + "log_date": today, + "type": data["type"], + "c_log_table": [] + }) + + # Map incoming data by UID + uid_map = {row["uid"]: row for row in data.get("c_log_table", [])} + updated_uids = [] + + # Update existing child rows + for row in doc.c_log_table: + if row.uid in uid_map: + updates = uid_map[row.uid] + for key, value in updates.items(): + if key != "uid": + setattr(row, key, value) + updated_uids.append(row.uid) + + # Append new child rows that weren't updated + for uid, row_data in uid_map.items(): + if uid not in updated_uids: + doc.append("c_log_table", row_data) + + # Save the document + doc.save(ignore_permissions=ignore_permissions) + return doc