Skip to main content

Overview

After initialization, your bot must join groups or communities before it can send messages. OpenChat supports three join operations:
  • Groups - Standalone chat groups
  • Communities - Collections of channels with shared membership
  • Channels - Individual chat rooms within a community
For private groups/communities, an admin must first invite your bot using the OpenChat UI before it can join.

Joining a Group

Groups are standalone chat spaces identified by their canister ID.
1

Obtain the group canister ID

Get the group’s canister ID from OpenChat. Example: "evg6t-laaaa-aaaar-a4j5q-cai"
2

Look up the local user index

Before joining, the bot must find the group’s local user index canister:
// From BotService.mo:499-516
func lookupLocalUserIndex(group: Text) : async* Result.Result<Principal, Text> {
  let res = await* ocService.publicGroupSummary(group, { invite_code = null});
  switch(res){
    case(#ok(data)){
      switch(data){
        case (#Success(response)){
          #ok(response.summary.local_user_index_canister_id)
        };
        case (#NotAuthorized(_)){
          #err("NotAuthorized")
        };
      }
    };
    case(#err(msg)){ #err(msg) };
  }
};
This step is handled automatically by joinGroup.
3

Call joinGroup

Join the group with an optional invite code:
# With an invite code
dfx canister call OCBot tryJoinGroup '("abc123", opt 123456789)' --ic

# Without an invite code (for public groups)
dfx canister call OCBot tryJoinGroup '("abc123", null)' --ic

Group Join Implementation

// From BotService.mo:159-185
public func joinGroup(groupCanisterId : Text, inviteCode : ?Nat64) : async* Result.Result<Text, Text>{
  let indexCanister = await* lookupLocalUserIndex(groupCanisterId);
  switch(indexCanister){
    case(#ok(id)){
      let #ok(res) = await* ocService.joinGroup(Principal.toText(id), {
        chat_id = Principal.fromText(groupCanisterId);
        invite_code = inviteCode;
        correlation_id = 0
      }) else{
        return #err("Trapped");
      };

      switch(res){
        case(#Success(_)){
          Map.set(botModel.groups, thash, groupCanisterId, ());
          #ok("OK")
        };
        case(#AlreadyInGroup or #AlreadyInGroupV2(_)){
          #err("Already in group")
        };
        case(_){ #err("Error") }
      };
    };
    case(#err(msg)){ #err(msg) }
  };
};

Group Join Responses

ResponseDescription
#SuccessSuccessfully joined group
#AlreadyInGroupBot is already a member
#NotAuthorizedGroup is private, invitation required
#UserBlockedBot has been blocked from group

Joining a Community

Communities contain multiple channels and require two-step joining process.
1

Get community canister ID

Obtain the community’s canister ID from OpenChat.
2

Look up the local community index

The bot finds the community’s local user index:
// From BotService.mo:187-204
func localCommunityIndex(communityCanisterId : Text) : async* Result.Result<Principal, Text>{
  let res = await* ocService.publicCommunitySummary(communityCanisterId, { invite_code = null});
  switch(res){
    case(#ok(data)){
      switch(data){
        case (#Success(response)){
          #ok(response.local_user_index_canister_id)
        };
        case (#PrivateCommunity(_)){
          #err("PrivateCommunity")
        };
      }
    };
    case(#err(msg)){ #err(msg) };
  }
};
3

Join the community

# With an invite code
dfx canister call OCBot tryJoinCommunity '("community-id", opt 123456789)' --ic

# Without an invite code (for public communities)
dfx canister call OCBot tryJoinCommunity '("community-id", null)' --ic

Community Join Implementation

// From BotService.mo:208-263
public func joinCommunity(
  communityCanisterId : Text,
  inviteCode : ?Nat64,
  botPrincipal : Principal
) : async* Result.Result<Text, Text>{
  let indexCanister = await* localCommunityIndex(communityCanisterId);
  switch(indexCanister){
    case(#ok(id)){
      let res = await* ocService.joinCommunity(Principal.toText(id), {
        community_id = Principal.fromText(communityCanisterId);
        user_id = botPrincipal;
        principal = botPrincipal;
        invite_code = inviteCode;
        is_platform_moderator = false;
        is_bot = true;
        diamond_membership_expires_at = null;
        verified_credential_args = null;
      });
      switch(res){
        case(#ok(data)){
          switch(data){
            case(#Success(_)){ #ok("OK") };
            case(#AlreadyInCommunity(_)){ #err("Already in community") };
            case(#GateCheckFailed(_)){ #err("GateCheckFailed") };
            case(#NotInvited){ #err("NotInvited") };
            case(#UserBlocked){ #err("UserBlocked") };
            case(#MemberLimitReached(limit)){ #err("MemberLimitReached") };
            case(#CommunityFrozen){ #err("CommunityFrozen") };
            case(#InternalError(e)){ #err("InternalError: " # e) };
          };
        };
        case(#err(e)){ #err(e) }
      };
    };
    case(#err(msg)){ #err(msg) }
  };
};

Community Join Responses

ResponseDescription
#SuccessSuccessfully joined community
#AlreadyInCommunityBot is already a member
#GateCheckFailedBot doesn’t meet gate requirements (tokens, credentials)
#NotInvitedCommunity is private, invitation required
#UserBlockedBot has been blocked
#MemberLimitReachedCommunity is at maximum capacity
#CommunityFrozenCommunity has been frozen

Joining a Channel

You must join the parent community before joining any of its channels.
1

Ensure community membership

First join the community using joinCommunity as shown above.
2

Join the channel

# With an invite code
dfx canister call OCBot tryJoinChannel '("${community_id}", ${channel_id}, opt 123456789)' --ic

# Without an invite code
dfx canister call OCBot tryJoinChannel '("${community_id}", ${channel_id}, null)' --ic
Example:
dfx canister call OCBot tryJoinChannel '("uxyan-oyaaa-aaaaf-aaa5q-cai", 123, null)' --ic

Channel Join Implementation

// From BotService.mo:265-334
public func joinChannel(
  communityCanisterId : Text,
  channelId: Nat,
  inviteCode : ?Nat64
) : async* Result.Result<Text, Text>{
  let indexCanister = await* localCommunityIndex(communityCanisterId);
  switch(indexCanister){
    case(#ok(id)){
      let res = await* ocService.joinChannel(Principal.toText(id), {
        community_id = Principal.fromText(communityCanisterId);
        channel_id = channelId;
        user_id = Principal.fromText("7g2oq-raaaa-aaaap-qb7sq-cai");
        principal = Principal.fromText("7g2oq-raaaa-aaaap-qb7sq-cai");
        invite_code = inviteCode;
        is_platform_moderator = false;
        is_bot = true;
        diamond_membership_expires_at = null;
        verified_credential_args = null;
      });
      switch(res){
        case(#ok(data)){
          switch(data){
            case(#Success(_)){ #ok("OK") };
            case(#SuccessJoinedCommunity(_)){ #ok("OK") };
            case(#AlreadyInChannel(_)){ #err("Already in community") };
            case(#UserNotInCommunity){ #err("UserNotInCommunity") };
            case(#ChannelNotFound){ #err("UserNotInCommunity") };
            // ... other error cases
          };
        };
        case(#err(e)){ #err(e) }
      };
    };
    case(#err(msg)){ #err(msg) }
  };
};

Channel Join Responses

ResponseDescription
#SuccessSuccessfully joined channel
#SuccessJoinedCommunityJoined both community and channel
#AlreadyInChannelBot is already a member
#UserNotInCommunityMust join community first
#ChannelNotFoundChannel does not exist
#UserSuspendedBot has been suspended

Complete Workflow Example

Here’s a complete example joining a community and channel:
// 1. Get bot principal
let botPrincipal = Principal.fromActor(this);

// 2. Join the community
let communityResult = await* botService.joinCommunity(
  "uxyan-oyaaa-aaaaf-aaa5q-cai",
  null, // No invite code for public communities
  botPrincipal
);

switch(communityResult){
  case(#ok(msg)){
    logService.logInfo("Joined community: " # msg, null);
    
    // 3. Join a channel in that community
    let channelResult = await* botService.joinChannel(
      "uxyan-oyaaa-aaaaf-aaa5q-cai",
      42, // channel ID
      null
    );
    
    switch(channelResult){
      case(#ok(msg)){
        logService.logInfo("Joined channel: " # msg, null);
      };
      case(#err(e)){
        logService.logError("Failed to join channel: " # e, null);
      };
    };
  };
  case(#err(e)){
    logService.logError("Failed to join community: " # e, null);
  };
};

Error Handling Best Practices

// Handle already-member gracefully
let result = await* botService.joinGroup(groupId, null);
switch(result){
  case(#ok(_)){ /* Success */ };
  case(#err("Already in group")){
    // Not actually an error - bot is already member
    logService.logInfo("Bot already in group", null);
  };
  case(#err(msg)){
    // Actual error
    logService.logError("Failed to join: " # msg, null);
  };
};

Next Steps

Send Messages

Start sending messages to your joined groups

Manage Subscriptions

Set up notification subscriptions

Build docs developers (and LLMs) love