diff --git a/raven/ai/ai.py b/raven/ai/ai.py
index 0250071a5..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.
@@ -28,6 +31,20 @@ 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_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..31af5c2b8 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.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 4224b15fa..c0701687a 100644
--- a/raven/raven_messaging/doctype/raven_message/raven_message.py
+++ b/raven/raven_messaging/doctype/raven_message/raven_message.py
@@ -2,13 +2,17 @@
# For license information, please see license.txt
import datetime
import json
-
+import re
+from datetime import date , time
+from datetime import datetime
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.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
@@ -25,6 +29,161 @@
)
+def extract_message(text, command):
+ if text.startswith(command):
+ return text[len(command):].strip()
+ return text
+
+
+
+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
+
+ 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
+ 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()
+
+ bot_command , update_message = bifurcate(bot_command_raw)
+
+
+ reply = ''
+ existing = frappe.get_list(
+ "Work Updates",
+ filters={"email": message.owner, "log_date": today},
+ fields=["name"]
+ )
+
+
+ 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 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_date":date.today(),
+ "email":message.owner,
+ "c_log_table":[{
+ "uid":message.name,
+ "task":update_message,
+ "time_log":datetime.now().time()
+
+ }],
+ "type":"Plan"
+ })
+ response_output = True
+ username = get_username_by_email(message.owner)
+ if len(existing)>=1:
+ reply = f"Work Plan Updated @{username} "
+ else:
+ reply = f"Submitted Work Update Approved @{username} "
+
+ # reply = f"Submitted Work Plan Approved {message.owner}"
+
+ elif bot_command.strip() == "/work_update":
+
+ create_doc({
+ "log_date":date.today(),
+ "email":message.owner,
+ "c_log_table":[{
+ "uid":message.name,
+ "task":update_message,
+ "time_log":datetime.now().time()
+
+ }],
+ "type":"Update"
+ })
+ response_output = True
+
+ if len(existing)>=1:
+ username = get_username_by_email(message.owner)
+ reply = f"Work Updated List Updated by @{username} "
+ else:
+ username = get_username_by_email(message.owner)
+ reply = f"Submitted Work Update by @{username} "
+
+
+ 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")
+
+ 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)
+
+
+
+
+
+
+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()
+
+
+
+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
# ruff: noqa
@@ -197,15 +356,19 @@ def after_insert(self):
self.publish_unread_count_event(last_message_details)
if self.message_type == "Text":
- self.handle_ai_message()
+ # self.handle_ai_message()
+ self.update_on_group()
+ # self.
self.send_push_notification()
def handle_ai_message(self):
# If the message was sent by a bot, do not call the function
+
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")
@@ -230,12 +393,10 @@ 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
# Only DMs to bots need to be handled (for now)
-
if not is_dm:
return
@@ -252,9 +413,11 @@ def handle_ai_message(self):
return
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)
frappe.enqueue(
method=handle_bot_dm,
@@ -265,9 +428,56 @@ def handle_ai_message(self):
at_front=True,
)
+
+ def update_on_group(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", "Technoculture-updates")
+ # print(self.as_dict() , "working way")
+ 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
+ mentioned_bot = self.content
+ channel_vlue = frappe.get_doc("Raven Channel" ,self.channel_id)
+
+ bot_name = channel_vlue.channel_name.split(" _ ")[1]
+ bot_pattern = re.search(r'@(\w+)' , mentioned_bot)
+
+ if bot_name:
+ bot = frappe.get_cached_doc("Raven Bot", bot_name)
+ if not bot.is_ai_bot:
+ handle_bot_command(message=self , bot=bot)
+
+
+
+
+
+
+
+
+
+
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,
@@ -541,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/scheduler/work_update_reminder.py b/raven/scheduler/work_update_reminder.py
new file mode 100644
index 000000000..d37ba9387
--- /dev/null
+++ b/raven/scheduler/work_update_reminder.py
@@ -0,0 +1,41 @@
+
+import frappe
+from datetime import datetime
+from raven.utils import push_message_to_channel
+
+def post_work_plan():
+ channel_id = "updates"
+ 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 = "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 261083d66..9031a3fb6 100644
--- a/raven/utils.py
+++ b/raven/utils.py
@@ -213,3 +213,145 @@ def clear_thread_reply_count_cache(thread_id: str):
Clear the thread reply count cache
"""
frappe.cache().hdel("raven:thread_reply_count", thread_id)
+
+
+
+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 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("c_log_table", []):
+ doc.append("c_log_table", task)
+
+ 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 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