diff --git a/backend/bots/examples/group_prize_bot/impl/src/updates/initialize_bot.rs b/backend/bots/examples/group_prize_bot/impl/src/updates/initialize_bot.rs index db1b1d3797..81c725f606 100644 --- a/backend/bots/examples/group_prize_bot/impl/src/updates/initialize_bot.rs +++ b/backend/bots/examples/group_prize_bot/impl/src/updates/initialize_bot.rs @@ -4,6 +4,7 @@ use canister_tracing_macros::trace; use group_prize_bot::initialize_bot::{Response::*, *}; use ic_cdk::update; use types::Cycles; +use user_index_canister::c2c_register_bot::OptionalBotConfig; const BOT_REGISTRATION_FEE: Cycles = 10_000_000_000_000; // 10T @@ -21,6 +22,7 @@ async fn initialize_bot(args: Args) -> Response { let register_bot_args = user_index_canister::c2c_register_bot::Args { username: args.username.clone(), display_name: None, + config: OptionalBotConfig::default(), }; user_index_canister_c2c_client::c2c_register_bot(user_index_canister_id, ®ister_bot_args, BOT_REGISTRATION_FEE) .await diff --git a/backend/canisters/user_index/CHANGELOG.md b/backend/canisters/user_index/CHANGELOG.md index 363138c479..ba1e04bcfc 100644 --- a/backend/canisters/user_index/CHANGELOG.md +++ b/backend/canisters/user_index/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [unreleased] +### Added + +- Add `bot_config` to bot users ([#6220](https://github.com/open-chat-labs/open-chat/pull/6220)) + ### Removed - Remove deprecated `is_bot` field from user records ([#6219](https://github.com/open-chat-labs/open-chat/pull/6219)) diff --git a/backend/canisters/user_index/api/src/updates/c2c_register_bot.rs b/backend/canisters/user_index/api/src/updates/c2c_register_bot.rs index a223bc374e..426d16d650 100644 --- a/backend/canisters/user_index/api/src/updates/c2c_register_bot.rs +++ b/backend/canisters/user_index/api/src/updates/c2c_register_bot.rs @@ -1,11 +1,12 @@ use candid::CandidType; use serde::{Deserialize, Serialize}; -use types::Cycles; +use types::{BotConfig, Cycles}; #[derive(CandidType, Serialize, Deserialize, Debug)] pub struct Args { pub username: String, pub display_name: Option, + pub config: OptionalBotConfig, } #[derive(CandidType, Serialize, Deserialize, Debug)] @@ -20,3 +21,19 @@ pub enum Response { InsufficientCyclesProvided(Cycles), InternalError(String), } + +#[derive(CandidType, Serialize, Deserialize, Debug, Default)] +pub struct OptionalBotConfig { + pub supports_direct_messages: Option, + pub can_be_added_to_groups: Option, +} + +impl From for BotConfig { + fn from(value: OptionalBotConfig) -> Self { + BotConfig { + is_oc_controlled: false, + supports_direct_messages: value.supports_direct_messages.unwrap_or_default(), + can_be_added_to_groups: value.can_be_added_to_groups.unwrap_or_default(), + } + } +} diff --git a/backend/canisters/user_index/impl/src/lib.rs b/backend/canisters/user_index/impl/src/lib.rs index 1a1a54db1c..291d810134 100644 --- a/backend/canisters/user_index/impl/src/lib.rs +++ b/backend/canisters/user_index/impl/src/lib.rs @@ -26,7 +26,7 @@ use std::cell::RefCell; use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; use std::time::Duration; use types::{ - BuildVersion, CanisterId, CanisterWasm, ChatId, Cryptocurrency, Cycles, DiamondMembershipFees, Milliseconds, + BotConfig, BuildVersion, CanisterId, CanisterWasm, ChatId, Cryptocurrency, Cycles, DiamondMembershipFees, Milliseconds, TimestampMillis, Timestamped, UserId, UserType, }; use utils::canister::{CanistersRequiringUpgrade, FailedUpgradeCount}; @@ -413,6 +413,7 @@ impl Data { now, None, UserType::OcControlledBot, + Some(BotConfig::default()), ); // Register the AirdropBot @@ -423,6 +424,7 @@ impl Data { now, None, UserType::OcControlledBot, + Some(BotConfig::default()), ); data diff --git a/backend/canisters/user_index/impl/src/lifecycle/post_upgrade.rs b/backend/canisters/user_index/impl/src/lifecycle/post_upgrade.rs index b75865d77f..bafe63893a 100644 --- a/backend/canisters/user_index/impl/src/lifecycle/post_upgrade.rs +++ b/backend/canisters/user_index/impl/src/lifecycle/post_upgrade.rs @@ -1,15 +1,12 @@ use crate::lifecycle::{init_env, init_state}; use crate::memory::get_upgrades_memory; -use crate::{read_state, Data}; +use crate::{mutate_state, Data}; use canister_logger::LogEntry; use canister_tracing_macros::trace; use ic_cdk::post_upgrade; -use icrc_ledger_types::icrc1::account::Account; -use icrc_ledger_types::icrc1::transfer::TransferArg; use stable_memory::get_reader; -use std::time::Duration; use tracing::info; -use types::Cryptocurrency; +use types::BotConfig; use user_index_canister::post_upgrade::Args; use utils::cycles::init_cycles_dispenser_client; @@ -29,25 +26,25 @@ fn post_upgrade(args: Args) { info!(version = %args.wasm_version, "Post-upgrade complete"); - ic_cdk_timers::set_timer(Duration::ZERO, || ic_cdk::spawn(transfer_to_airdrop_bot())); -} - -async fn transfer_to_airdrop_bot() { - let (airdrop_bot, test_mode) = read_state(|state| (state.data.airdrop_bot_canister_id, state.data.test_mode)); - - const ONE_CHAT: u64 = 1_0000_0000; - let amount = if test_mode { ONE_CHAT } else { 100_000 * ONE_CHAT }; - - let _ = icrc_ledger_canister_c2c_client::icrc1_transfer( - Cryptocurrency::CHAT.ledger_canister_id().unwrap(), - &TransferArg { - from_subaccount: None, - to: Account::from(airdrop_bot), - fee: None, - created_at_time: None, - memo: None, - amount: amount.into(), - }, - ) - .await; + mutate_state(|state| { + let now = state.env.now(); + state.data.users.set_bot_config( + state.data.proposals_bot_canister_id.into(), + BotConfig { + is_oc_controlled: true, + supports_direct_messages: false, + can_be_added_to_groups: false, + }, + now, + ); + state.data.users.set_bot_config( + state.data.airdrop_bot_canister_id.into(), + BotConfig { + is_oc_controlled: true, + supports_direct_messages: true, + can_be_added_to_groups: false, + }, + now, + ); + }); } diff --git a/backend/canisters/user_index/impl/src/model/user.rs b/backend/canisters/user_index/impl/src/model/user.rs index 0885fc6f60..340ce3397e 100644 --- a/backend/canisters/user_index/impl/src/model/user.rs +++ b/backend/canisters/user_index/impl/src/model/user.rs @@ -4,7 +4,7 @@ use candid::{CandidType, Principal}; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use types::{ - is_default, is_empty_slice, CyclesTopUp, CyclesTopUpInternal, PhoneNumber, RegistrationFee, SuspensionAction, + is_default, is_empty_slice, BotConfig, CyclesTopUp, CyclesTopUpInternal, PhoneNumber, RegistrationFee, SuspensionAction, SuspensionDuration, TimestampMillis, UniquePersonProof, UserId, UserSummary, UserSummaryStable, UserSummaryV2, UserSummaryVolatile, UserType, }; @@ -40,6 +40,8 @@ pub struct User { pub referred_by: Option, #[serde(rename = "ut", default, skip_serializing_if = "is_default")] pub user_type: UserType, + #[serde(rename = "bc", default, skip_serializing_if = "Option::is_none")] + pub bot_config: Option, #[serde(rename = "sd", default, skip_serializing_if = "Option::is_none")] pub suspension_details: Option, #[serde( @@ -99,6 +101,7 @@ impl User { now: TimestampMillis, referred_by: Option, user_type: UserType, + bot_config: Option, ) -> User { User { principal, @@ -115,6 +118,7 @@ impl User { phone_status: PhoneStatus::None, referred_by, user_type, + bot_config, suspension_details: None, diamond_membership_details: DiamondMembershipDetailsInternal::default(), moderation_flags_enabled: 0, @@ -167,6 +171,7 @@ impl User { suspended: self.suspension_details.is_some(), diamond_membership_status: self.diamond_membership_details.status(now), is_unique_person: self.unique_person_proof.is_some(), + bot_config: self.bot_config.clone(), } } @@ -236,6 +241,7 @@ impl Default for User { phone_status: PhoneStatus::None, referred_by: None, user_type: UserType::User, + bot_config: None, suspension_details: None, diamond_membership_details: DiamondMembershipDetailsInternal::default(), moderation_flags_enabled: 0, diff --git a/backend/canisters/user_index/impl/src/model/user_map.rs b/backend/canisters/user_index/impl/src/model/user_map.rs index 69f9aedeaa..094236b879 100644 --- a/backend/canisters/user_index/impl/src/model/user_map.rs +++ b/backend/canisters/user_index/impl/src/model/user_map.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use std::collections::{BTreeSet, HashMap}; use std::ops::RangeFrom; use tracing::info; -use types::{CyclesTopUp, Milliseconds, SuspensionDuration, TimestampMillis, UniquePersonProof, UserId, UserType}; +use types::{BotConfig, CyclesTopUp, Milliseconds, SuspensionDuration, TimestampMillis, UniquePersonProof, UserId, UserType}; use utils::case_insensitive_hash_map::CaseInsensitiveHashMap; use utils::time::MonthKey; @@ -62,11 +62,12 @@ impl UserMap { now: TimestampMillis, referred_by: Option, user_type: UserType, + bot_config: Option, ) { self.username_to_user_id.insert(&username, user_id); self.principal_to_user_id.insert(principal, user_id); - let user = User::new(principal, user_id, username, now, referred_by, user_type); + let user = User::new(principal, user_id, username, now, referred_by, user_type, bot_config); self.users.insert(user_id, user); if let Some(ref_by) = referred_by { @@ -224,6 +225,17 @@ impl UserMap { true } + pub fn set_bot_config(&mut self, user_id: UserId, config: BotConfig, now: TimestampMillis) -> bool { + if let Some(user) = self.users.get_mut(&user_id) { + if let Some(c) = &mut user.bot_config { + *c = config; + user.date_updated = now; + return true; + } + } + false + } + pub fn suspend_user( &mut self, user_id: UserId, @@ -386,6 +398,7 @@ impl UserMap { user.date_created, None, UserType::User, + None, ); self.update(user, date_created, false); } @@ -457,9 +470,9 @@ mod tests { let user_id2: UserId = Principal::from_slice(&[3, 2]).into(); let user_id3: UserId = Principal::from_slice(&[3, 3]).into(); - user_map.register(principal1, user_id1, username1.clone(), 1, None, UserType::User); - user_map.register(principal2, user_id2, username2.clone(), 2, None, UserType::User); - user_map.register(principal3, user_id3, username3.clone(), 3, None, UserType::User); + user_map.register(principal1, user_id1, username1.clone(), 1, None, UserType::User, None); + user_map.register(principal2, user_id2, username2.clone(), 2, None, UserType::User, None); + user_map.register(principal3, user_id3, username3.clone(), 3, None, UserType::User, None); let principal_to_user_id: Vec<_> = user_map .principal_to_user_id @@ -496,7 +509,7 @@ mod tests { let user_id = Principal::from_slice(&[1, 1]).into(); - user_map.register(principal, user_id, username1, 1, None, UserType::User); + user_map.register(principal, user_id, username1, 1, None, UserType::User, None); if let Some(original) = user_map.get_by_principal(&principal) { let mut updated = original.clone(); diff --git a/backend/canisters/user_index/impl/src/updates/c2c_notify_events.rs b/backend/canisters/user_index/impl/src/updates/c2c_notify_events.rs index a566bdcf1f..4e6934c440 100644 --- a/backend/canisters/user_index/impl/src/updates/c2c_notify_events.rs +++ b/backend/canisters/user_index/impl/src/updates/c2c_notify_events.rs @@ -133,7 +133,7 @@ fn process_new_user( state .data .users - .register(caller, user_id, username.clone(), now, referred_by, UserType::User); + .register(caller, user_id, username.clone(), now, referred_by, UserType::User, None); state.data.local_index_map.add_user(local_user_index_canister_id, user_id); diff --git a/backend/canisters/user_index/impl/src/updates/c2c_register_bot.rs b/backend/canisters/user_index/impl/src/updates/c2c_register_bot.rs index d94beb23e0..efae87a668 100644 --- a/backend/canisters/user_index/impl/src/updates/c2c_register_bot.rs +++ b/backend/canisters/user_index/impl/src/updates/c2c_register_bot.rs @@ -45,10 +45,15 @@ fn c2c_register_bot_impl(args: Args, state: &mut RuntimeState) -> Response { } ic_cdk::api::call::msg_cycles_accept128(BOT_REGISTRATION_FEE); - state - .data - .users - .register(caller, user_id, args.username.clone(), now, None, UserType::Bot); + state.data.users.register( + caller, + user_id, + args.username.clone(), + now, + None, + UserType::Bot, + Some(args.config.into()), + ); state.push_event_to_all_local_user_indexes( Event::UserRegistered(UserRegistered { diff --git a/backend/libraries/types/can.did b/backend/libraries/types/can.did index ca86102851..6b1ce69e44 100644 --- a/backend/libraries/types/can.did +++ b/backend/libraries/types/can.did @@ -1885,6 +1885,7 @@ type UserSummaryStable = record { suspended : bool; diamond_membership_status : DiamondMembershipStatus; is_unique_person : bool; + bot_config : opt BotConfig; }; type UserSummaryVolatile = record { @@ -1909,6 +1910,12 @@ type CurrentUserSummary = record { is_unique_person : bool; }; +type BotConfig = record { + is_oc_controlled : bool; + supports_direct_messages : bool; + can_be_added_to_groups : bool; +}; + type SuspensionDetails = record { reason : text; action : SuspensionAction; diff --git a/backend/libraries/types/src/bots.rs b/backend/libraries/types/src/bots.rs index a65de5fc20..a5551e290c 100644 --- a/backend/libraries/types/src/bots.rs +++ b/backend/libraries/types/src/bots.rs @@ -2,6 +2,13 @@ use crate::{MessageContentInitial, MessageId}; use candid::CandidType; use serde::{Deserialize, Serialize}; +#[derive(CandidType, Serialize, Deserialize, Clone, Debug, Default)] +pub struct BotConfig { + pub is_oc_controlled: bool, + pub supports_direct_messages: bool, + pub can_be_added_to_groups: bool, +} + #[derive(CandidType, Serialize, Deserialize, Debug)] pub struct BotMessage { pub thread_root_message_id: Option, diff --git a/backend/libraries/types/src/user_summary.rs b/backend/libraries/types/src/user_summary.rs index bc1c7ebebd..b7895b8e82 100644 --- a/backend/libraries/types/src/user_summary.rs +++ b/backend/libraries/types/src/user_summary.rs @@ -1,4 +1,6 @@ -use crate::{DiamondMembershipDetails, DiamondMembershipStatus, DiamondMembershipStatusFull, SuspensionDetails, UserId}; +use crate::{ + BotConfig, DiamondMembershipDetails, DiamondMembershipStatus, DiamondMembershipStatusFull, SuspensionDetails, UserId, +}; use candid::CandidType; use serde::{Deserialize, Serialize}; @@ -34,6 +36,7 @@ pub struct UserSummaryStable { pub suspended: bool, pub diamond_membership_status: DiamondMembershipStatus, pub is_unique_person: bool, + pub bot_config: Option, } #[derive(CandidType, Serialize, Deserialize, Debug)]