Skip to content

Commit

Permalink
2-stage bot messages + bot context in messages (#7060)
Browse files Browse the repository at this point in the history
  • Loading branch information
megrogan authored Dec 16, 2024
1 parent 3a53022 commit 3286ebb
Show file tree
Hide file tree
Showing 42 changed files with 557 additions and 281 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/canisters/community/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed

- Log error if end video call job fails ([#7066](https://github.com/open-chat-labs/open-chat/pull/7066))
- 2-stage bot messages + bot context in messages ([#7060](https://github.com/open-chat-labs/open-chat/pull/7060))

### Fixed

Expand Down
1 change: 1 addition & 0 deletions backend/canisters/community/api/can.did
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,7 @@ type SendMessageResponse = variant {
CommunityFrozen;
RulesNotAccepted;
CommunityRulesNotAccepted;
MessageAlreadyExists;
};

type SendMessageSuccess = record {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub enum Response {
InvalidRequest(String),
CommunityFrozen,
RulesNotAccepted,
MessageAlreadyExists,
CommunityRulesNotAccepted,
UserLapsed,
}
Expand Down
53 changes: 24 additions & 29 deletions backend/canisters/community/impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ impl RuntimeState {
}
}

pub fn caller(&self, bot_id: Option<UserId>) -> CallerResult {
pub fn verified_caller(&self, mut bot_context: Option<BotCaller>) -> CallerResult {
use CallerResult::*;

let caller = self.env.caller();
Expand All @@ -322,37 +322,32 @@ impl RuntimeState {
return Success(Caller::OCBot(OPENCHAT_BOT_USER_ID));
}

if let Some(bot_id) = bot_id {
if let Some(bot) = self.data.bots.get(&bot_id) {
if let Some(member) = self.data.members.get_by_user_id(&bot.added_by) {
if member.suspended().value {
return Suspended;
} else if member.lapsed().value {
return Lapsed;
} else {
return Success(Caller::BotV2(BotCaller {
user_id: bot.added_by,
bot_id,
}));
}
}
}
} else if let Some(member) = self.data.members.get(caller) {
if member.suspended().value {
return Suspended;
} else if member.lapsed().value {
return Lapsed;
let user_or_principal = bot_context.as_ref().map(|bc| bc.initiator.into()).unwrap_or(caller);

let Some(member) = self.data.members.get(user_or_principal) else {
return NotFound;
};

if member.suspended().value {
return Suspended;
} else if member.lapsed().value {
return Lapsed;
}

if let Some(bot_context) = bot_context.take() {
if self.data.bots.get(&bot_context.bot).is_some() {
Success(Caller::BotV2(bot_context))
} else {
return match member.user_type {
UserType::User => Success(Caller::User(member.user_id)),
UserType::BotV2 => NotFound,
UserType::Bot => Success(Caller::Bot(member.user_id)),
UserType::OcControlledBot => Success(Caller::OCBot(member.user_id)),
};
NotFound
}
} else {
match member.user_type {
UserType::User => Success(Caller::User(member.user_id)),
UserType::BotV2 => NotFound,
UserType::Bot => Success(Caller::Bot(member.user_id)),
UserType::OcControlledBot => Success(Caller::OCBot(member.user_id)),
}
}

NotFound
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use canister_tracing_macros::trace;
use community_canister::c2c_handle_bot_action::*;
use community_canister::send_message;
use types::bot_actions::MessageContent;
use types::{BotAction, Chat, HandleBotActionsError, MessageContentInitial};
use types::{BotAction, BotCaller, Chat, HandleBotActionsError, MessageContentInitial};
use utils::bots::can_execute_bot_command;

#[update(guard = "caller_is_local_user_index", msgpack = true)]
Expand All @@ -31,8 +31,8 @@ fn c2c_handle_bot_action_impl(args: Args, state: &mut RuntimeState) -> Response
};

match args.action {
BotAction::SendMessage(content) => {
let content = match content {
BotAction::SendMessage(action) => {
let content = match action.content {
MessageContent::Text(text_content) => MessageContentInitial::Text(text_content),
MessageContent::Image(image_content) => MessageContentInitial::Image(image_content),
MessageContent::Video(video_content) => MessageContentInitial::Video(video_content),
Expand All @@ -59,7 +59,12 @@ fn c2c_handle_bot_action_impl(args: Args, state: &mut RuntimeState) -> Response
message_filter_failed: None,
new_achievement: false,
},
Some(args.bot.user_id),
Some(BotCaller {
bot: args.bot.user_id,
initiator: args.initiator,
command_text: args.command_text,
finalised: action.finalised,
}),
state,
) {
send_message::Response::Success(_) => Ok(()),
Expand All @@ -80,10 +85,7 @@ fn is_bot_permitted_to_execute_command(args: &Args, state: &RuntimeState) -> boo
};

// Get the permissions granted to the user in this community/channel
let Some(granted_to_user) = state
.data
.get_user_permissions_for_bot_commands(&args.commanded_by, &channel_id)
else {
let Some(granted_to_user) = state.data.get_user_permissions_for_bot_commands(&args.initiator, &channel_id) else {
return false;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ fn edit_message_impl(args: Args, state: &mut RuntimeState) -> Response {
message_id: args.message_id,
content: args.content,
block_level_markdown: args.block_level_markdown,
finalise_bot_message: false,
now,
},
Some(&mut state.data.event_store_client),
Expand Down
179 changes: 91 additions & 88 deletions backend/canisters/community/impl/src/updates/send_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use lazy_static::lazy_static;
use regex_lite::Regex;
use std::str::FromStr;
use types::{
Achievement, Caller, ChannelId, ChannelMessageNotification, Chat, EventIndex, EventWrapper, Message, MessageContent,
MessageIndex, Notification, TimestampMillis, User, UserId, Version,
Achievement, BotCaller, Caller, ChannelId, ChannelMessageNotification, Chat, EventIndex, EventWrapper, Message,
MessageContent, MessageIndex, Notification, TimestampMillis, User, UserId, Version,
};
use user_canister::{CommunityCanisterEvent, MessageActivity, MessageActivityEvent};

Expand All @@ -34,8 +34,8 @@ fn c2c_send_message(args: C2CArgs) -> C2CResponse {
mutate_state(|state| c2c_send_message_impl(args, state))
}

pub(crate) fn send_message_impl(args: Args, bot_id: Option<UserId>, state: &mut RuntimeState) -> Response {
let caller = match state.caller(bot_id) {
pub(crate) fn send_message_impl(args: Args, bot: Option<BotCaller>, state: &mut RuntimeState) -> Response {
let caller = match state.verified_caller(bot) {
CallerResult::Success(caller) => caller,
CallerResult::NotFound => return UserNotInCommunity,
CallerResult::Suspended => return UserSuspended,
Expand Down Expand Up @@ -86,7 +86,7 @@ pub(crate) fn send_message_impl(args: Args, bot_id: Option<UserId>, state: &mut
}

fn c2c_send_message_impl(args: C2CArgs, state: &mut RuntimeState) -> C2CResponse {
let caller = match state.caller(None) {
let caller = match state.verified_caller(None) {
CallerResult::Success(caller) => caller,
CallerResult::NotFound => return UserNotInCommunity,
CallerResult::Suspended => return UserSuspended,
Expand Down Expand Up @@ -202,105 +202,107 @@ fn process_send_message_result(
let content = &message_event.event.content;
let community_id = state.env.canister_id().into();

let notification = Notification::ChannelMessage(ChannelMessageNotification {
community_id,
channel_id,
thread_root_message_index,
message_index: message_event.event.message_index,
event_index: message_event.index,
community_name: state.data.name.value.clone(),
channel_name,
sender: caller.agent(),
sender_name: sender_username,
sender_display_name,
message_type: content.message_type(),
message_text: content
.notification_text(&users_mentioned.mentioned_directly, &users_mentioned.user_groups_mentioned),
image_url: content.notification_image_url(),
community_avatar_id: state.data.avatar.as_ref().map(|d| d.id),
channel_avatar_id,
crypto_transfer: content.notification_crypto_transfer_details(&users_mentioned.mentioned_directly),
});
state.push_notification(result.users_to_notify, notification);

register_timer_jobs(channel_id, thread_root_message_index, message_event, now, &mut state.data);

if new_achievement && !caller.is_bot() {
for a in result
.message_event
.event
.achievements(false, thread_root_message_index.is_some())
{
state.data.notify_user_of_achievement(caller.agent(), a);
if !result.unfinalised_bot_message {
let notification = Notification::ChannelMessage(ChannelMessageNotification {
community_id,
channel_id,
thread_root_message_index,
message_index: message_event.event.message_index,
event_index: message_event.index,
community_name: state.data.name.value.clone(),
channel_name,
sender: caller.agent(),
sender_name: sender_username,
sender_display_name,
message_type: content.message_type(),
message_text: content
.notification_text(&users_mentioned.mentioned_directly, &users_mentioned.user_groups_mentioned),
image_url: content.notification_image_url(),
community_avatar_id: state.data.avatar.as_ref().map(|d| d.id),
channel_avatar_id,
crypto_transfer: content.notification_crypto_transfer_details(&users_mentioned.mentioned_directly),
});
state.push_notification(result.users_to_notify, notification);

if new_achievement && !caller.is_bot() {
for a in result
.message_event
.event
.achievements(false, thread_root_message_index.is_some())
{
state.data.notify_user_of_achievement(caller.agent(), a);
}
}
}

let mut activity_events = Vec::new();

if let MessageContent::Crypto(c) = &message_event.event.content {
let recipient_is_human = state
.data
.members
.get_by_user_id(&c.recipient)
.map_or(false, |m| !m.user_type.is_bot());
let mut activity_events = Vec::new();

if recipient_is_human {
state
if let MessageContent::Crypto(c) = &message_event.event.content {
let recipient_is_human = state
.data
.notify_user_of_achievement(c.recipient, Achievement::ReceivedCrypto);
.members
.get_by_user_id(&c.recipient)
.map_or(false, |m| !m.user_type.is_bot());

activity_events.push((c.recipient, MessageActivity::Crypto));
}
}
if recipient_is_human {
state
.data
.notify_user_of_achievement(c.recipient, Achievement::ReceivedCrypto);

if let Some(channel) = state.data.channels.get(&channel_id) {
for user_id in users_mentioned.all_users_mentioned {
if user_id != caller.initiator()
&& channel.chat.members.get(&user_id).map_or(false, |m| !m.user_type().is_bot())
{
activity_events.push((user_id, MessageActivity::Mention));
activity_events.push((c.recipient, MessageActivity::Crypto));
}
}

if let Some(replying_to_event_index) = message_event
.event
.replies_to
.as_ref()
.filter(|r| r.chat_if_other.is_none())
.map(|r| r.event_index)
{
if let Some((message, _)) = channel.chat.events.message_internal(
EventIndex::default(),
thread_root_message_index,
replying_to_event_index.into(),
) {
if message.sender != caller.initiator()
&& channel
.chat
.members
.get(&message.sender)
.map_or(false, |m| !m.user_type().is_bot())
if let Some(channel) = state.data.channels.get(&channel_id) {
for user_id in users_mentioned.all_users_mentioned {
if user_id != caller.initiator()
&& channel.chat.members.get(&user_id).map_or(false, |m| !m.user_type().is_bot())
{
activity_events.push((message.sender, MessageActivity::QuoteReply));
activity_events.push((user_id, MessageActivity::Mention));
}
}

if let Some(replying_to_event_index) = message_event
.event
.replies_to
.as_ref()
.filter(|r| r.chat_if_other.is_none())
.map(|r| r.event_index)
{
if let Some((message, _)) = channel.chat.events.message_internal(
EventIndex::default(),
thread_root_message_index,
replying_to_event_index.into(),
) {
if message.sender != caller.initiator()
&& channel
.chat
.members
.get(&message.sender)
.map_or(false, |m| !m.user_type().is_bot())
{
activity_events.push((message.sender, MessageActivity::QuoteReply));
}
}
}
}
}

for (user_id, activity) in activity_events {
state.data.user_event_sync_queue.push(
user_id,
CommunityCanisterEvent::MessageActivity(MessageActivityEvent {
chat: Chat::Channel(community_id, channel_id),
thread_root_message_index,
message_index,
message_id,
event_index,
activity,
timestamp: now,
user_id: Some(caller.agent()),
}),
);
for (user_id, activity) in activity_events {
state.data.user_event_sync_queue.push(
user_id,
CommunityCanisterEvent::MessageActivity(MessageActivityEvent {
chat: Chat::Channel(community_id, channel_id),
thread_root_message_index,
message_index,
message_id,
event_index,
activity,
timestamp: now,
user_id: Some(caller.agent()),
}),
);
}
}

handle_activity_notification(state);
Expand All @@ -321,6 +323,7 @@ fn process_send_message_result(
SendMessageResult::UserSuspended => UserSuspended,
SendMessageResult::UserLapsed => UserLapsed,
SendMessageResult::RulesNotAccepted => RulesNotAccepted,
SendMessageResult::MessageAlreadyExists => MessageAlreadyExists,
SendMessageResult::InvalidRequest(error) => InvalidRequest(error),
}
}
Expand Down
1 change: 1 addition & 0 deletions backend/canisters/group/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed

- Log error if end video call job fails ([#7066](https://github.com/open-chat-labs/open-chat/pull/7066))
- 2-stage bot messages + bot context in messages ([#7060](https://github.com/open-chat-labs/open-chat/pull/7060))

## [[2.0.1516](https://github.com/open-chat-labs/open-chat/releases/tag/v2.0.1516-group)] - 2024-12-13

Expand Down
Loading

0 comments on commit 3286ebb

Please sign in to comment.