Skip to main content

Overview

The BotServiceImpl class provides the main interface for bot operations on OpenChat. It handles bot initialization, avatar management, group/community/channel operations, and message management.

Constructor

public class BotServiceImpl(
  botModel : BT.BotModel,
  ocService : OC.OCService,
  logService : LT.LogService
)
botModel
BT.BotModel
required
The bot’s data model containing status, name, groups, and saved messages
ocService
OC.OCService
required
Service for making OpenChat API calls
logService
LT.LogService
required
Service for logging bot operations

Bot Initialization

initBot

Initializes the bot by registering it with the OpenChat platform. Requires 10T cycles for registration fee.
public func initBot<system>(
  name : Text,
  _displayName : ?Text
) : async Result.Result<(), Text>
name
Text
required
The bot’s username (must be unique on OpenChat)
_displayName
?Text
Optional display name for the bot
Result
Result.Result<(), Text>
Returns #ok() on success, or #err(message) with error details:
  • "Initializing" - Bot is currently initializing
  • "Initialized" - Bot is already initialized
  • "AlreadyRegistered" - Username is already registered
  • "Not enough cycles. Required: {n}" - Insufficient cycles provided
  • "Error: {msg}" - Other registration errors
Example:
let result = await botService.initBot<system>("my_bot", ?"My Bot Display Name");
switch(result) {
  case(#ok()) { /* Bot initialized successfully */ };
  case(#err(msg)) { /* Handle error: msg */ };
}
Note: This function requires system capability to add cycles. The bot status must be #NotInitialized before calling.

Bot Configuration

setAvatar

Sets the bot’s avatar image. Maximum size is 800KB.
public func setAvatar(
  args : BT.SetAvatarArgs
) : async* Result.Result<BT.SetAvatarResponse, Text>
args
SetAvatarArgs
required
Avatar configuration containing the document to use as avatar
Result
Result.Result<SetAvatarResponse, Text>
  • #ok(#Success) - Avatar set successfully
  • #err("Avatar too big") - Avatar exceeds 800KB limit
  • #err("Avatar not found") - No avatar provided in args
  • #err("Error: {msg}") - OpenChat API error
Example:
let avatarDoc : OCApi.Document = {
  id = 12345;
  mime_type = "image/png";
  data = avatarBlob;
};

let result = await* botService.setAvatar({
  avatar = ?avatarDoc
});

getBotStatus

Returns the current initialization status of the bot.
public func getBotStatus() : BT.BotStatus
BotStatus
BotStatus
One of:
  • #NotInitialized - Bot has not been initialized
  • #Initializing - Bot initialization in progress
  • #Initialized - Bot is ready to use

setBotStatus

Manually sets the bot status to initialized.
public func setBotStatus() : ()
Note: Use with caution. This is typically handled by initBot.

Group Operations

joinGroup

Joins an OpenChat group.
public func joinGroup(
  groupCanisterId : Text,
  inviteCode : ?Nat64
) : async* Result.Result<Text, Text>
groupCanisterId
Text
required
The principal/canister ID of the group to join
inviteCode
?Nat64
Optional invite code for private groups
Result
Result.Result<Text, Text>
  • #ok("OK") - Successfully joined group
  • #err("Already in group") - Bot is already a member
  • #err("Error") - Join operation failed
  • #err(msg) - Other errors from OpenChat
Example:
let result = await* botService.joinGroup(
  "evg6t-laaaa-aaaar-a4j5q-cai",
  null
);

sendGroupMessage

Sends a message to a group with any content type.
public func sendGroupMessage(
  groupCanisterId : Text,
  content : OCApi.MessageContentInitial,
  threadIndexId : ?Nat32
) : async* Result.Result<T.SendMessageResponse, Text>
groupCanisterId
Text
required
The group’s canister ID
content
MessageContentInitial
required
Message content (text, image, poll, etc.)
threadIndexId
?Nat32
Optional thread root message index for threaded replies
Result
Result.Result<SendMessageResponse, Text>
On success, returns #ok(#Success({ event_index; message_index; message_id }))Possible error variants:
  • #MessageEmpty - Content is empty
  • #TextTooLong(maxLength) - Text exceeds maximum length
  • #NotAuthorized - Bot lacks permission
  • #UserNotInChannel - Bot is not a group member
  • #UserSuspended - Bot is suspended
  • #RulesNotAccepted - Group rules must be accepted
Example:
let content = #Text({ text = "Hello from bot!" });
let result = await* botService.sendGroupMessage(
  "evg6t-laaaa-aaaar-a4j5q-cai",
  content,
  null
);

switch(result) {
  case(#ok(#Success(data))) {
    // Message sent: data.message_id
  };
  case(#ok(error)) { /* Handle specific error variant */ };
  case(#err(msg)) { /* Handle trapped error */ };
}

sendTextGroupMessage

Convenience function to send a text message to a group.
public func sendTextGroupMessage(
  groupCanisterId : Text,
  content : Text,
  threadIndexId : ?Nat32
) : async* Result.Result<T.SendMessageResponse, Text>
groupCanisterId
Text
required
The group’s canister ID
content
Text
required
Plain text message content
threadIndexId
?Nat32
Optional thread root message index
Example:
let result = await* botService.sendTextGroupMessage(
  "evg6t-laaaa-aaaar-a4j5q-cai",
  "Hello, world!",
  null
);

editGroupMessage

Edits a previously sent group message.
public func editGroupMessage(
  groupCanisterId : Text,
  messageId : OCApi.MessageId,
  threadRootIndex : ?OCApi.MessageIndex,
  newContent : OCApi.MessageContentInitial
) : async* Result.Result<OCApi.EditMessageResponse, Text>
groupCanisterId
Text
required
The group’s canister ID
messageId
MessageId
required
The ID of the message to edit (from send response)
threadRootIndex
?MessageIndex
Thread root index if editing a threaded message
newContent
MessageContentInitial
required
New message content

editTextGroupMessage

Convenience function to edit a text message in a group.
public func editTextGroupMessage(
  groupCanisterId : Text,
  messageId : OCApi.MessageId,
  threadRootIndex : ?OCApi.MessageIndex,
  newContent : Text
) : async* Result.Result<OCApi.EditMessageResponse, Text>
Example:
let result = await* botService.editTextGroupMessage(
  "evg6t-laaaa-aaaar-a4j5q-cai",
  savedMessageId,
  null,
  "Updated message text"
);

getGroupMessagesByIndex

Retrieves specific messages from a group by their indexes.
public func getGroupMessagesByIndex(
  groupCanisterId : Text,
  indexes : [Nat32],
  latest_known_update : ?Nat64
) : async* Result.Result<OCApi.MessagesResponse, Text>
groupCanisterId
Text
required
The group’s canister ID
indexes
[Nat32]
required
Array of message indexes to retrieve
latest_known_update
?Nat64
Optional timestamp of latest known update for caching
Result
Result.Result<MessagesResponse, Text>
Returns the requested messages with their full content and metadata
Example:
let indexes : [Nat32] = [100, 101, 102];
let result = await* botService.getGroupMessagesByIndex(
  "evg6t-laaaa-aaaar-a4j5q-cai",
  indexes,
  null
);

getLatestGroupMessageIndex

Gets the index of the most recent message in a group.
public func getLatestGroupMessageIndex(
  groupCanisterId : Text
) : async* ?OCApi.MessageIndex
groupCanisterId
Text
required
The group’s canister ID
Result
?MessageIndex
Returns the latest message index, or null if unavailable

Community Operations

joinCommunity

Joins an OpenChat community.
public func joinCommunity(
  communityCanisterId : Text,
  inviteCode : ?Nat64,
  botPrincipal : Principal
) : async* Result.Result<Text, Text>
communityCanisterId
Text
required
The community’s canister ID
inviteCode
?Nat64
Optional invite code for private communities
botPrincipal
Principal
required
The bot’s principal ID
Result
Result.Result<Text, Text>
  • #ok("OK") - Successfully joined community
  • #err("Already in community") - Bot is already a member
  • #err("GateCheckFailed") - Bot doesn’t meet community requirements
  • #err("NotInvited") - Invite required but not provided
  • #err("UserBlocked") - Bot is blocked from community
  • #err("MemberLimitReached") - Community is full
  • #err("CommunityFrozen") - Community is frozen
  • #err("PrivateCommunity") - Cannot access private community info
Example:
let botPrincipal = Principal.fromActor(this);
let result = await* botService.joinCommunity(
  "community-canister-id",
  null,
  botPrincipal
);

joinChannel

Joins a channel within a community.
public func joinChannel(
  communityCanisterId : Text,
  channelId : Nat,
  inviteCode : ?Nat64
) : async* Result.Result<Text, Text>
communityCanisterId
Text
required
The community’s canister ID
channelId
Nat
required
The channel ID within the community
inviteCode
?Nat64
Optional invite code for private channels
Result
Result.Result<Text, Text>
Similar error cases to joinCommunity, plus:
  • #err("UserNotInCommunity") - Must join community first
  • #err("UserSuspended") - Bot is suspended from community
  • #err("ChannelNotFound") - Channel doesn’t exist

sendChannelMessage

Sends a message to a community channel.
public func sendChannelMessage(
  communityCanisterId : Text,
  channelId : Nat,
  content : OCApi.MessageContent,
  threadIndexId : ?Nat32
) : async* Result.Result<T.SendMessageResponse, Text>
communityCanisterId
Text
required
The community’s canister ID
channelId
Nat
required
The channel ID
content
MessageContent
required
Message content
threadIndexId
?Nat32
Optional thread root message index
Result
Result.Result<SendMessageResponse, Text>
Same response format as sendGroupMessage, with additional variants:
  • #ChannelNotFound - Channel doesn’t exist
  • #UserNotInCommunity - Bot not in community
  • #UserNotInChannel - Bot not in channel
  • #CommunityFrozen - Community is frozen
  • #CommunityRulesNotAccepted - Community rules must be accepted
Example:
let content = #Text({ text = "Hello channel!" });
let result = await* botService.sendChannelMessage(
  "community-canister-id",
  42,
  content,
  null
);

editChannelMessage

Edits a message in a community channel.
public func editChannelMessage(
  communityCanisterId : Text,
  channelId : Nat,
  messageId : OCApi.MessageId,
  threadRootIndex : ?OCApi.MessageIndex,
  newContent : OCApi.MessageContentInitial
) : async* Result.Result<OCApi.EditChannelMessageResponse, Text>
communityCanisterId
Text
required
The community’s canister ID
channelId
Nat
required
The channel ID
messageId
MessageId
required
The message ID to edit
threadRootIndex
?MessageIndex
Thread root index if editing a threaded message
newContent
MessageContentInitial
required
New message content

Message Storage

The bot service provides key-value storage for message IDs, useful for tracking messages you want to edit later.

saveMessageId

Saves a message ID with an associated key.
public func saveMessageId(
  key : Text,
  messageid : OCApi.MessageId
) : ()
key
Text
required
Unique identifier for this message
messageid
MessageId
required
The message ID to save
Example:
// After sending a message
switch(result) {
  case(#ok(#Success(data))) {
    botService.saveMessageId("welcome-message", data.message_id);
  };
  case(_) {};
}

getMessageId

Retrieves a previously saved message ID.
public func getMessageId(
  key : Text
) : ?OCApi.MessageId
key
Text
required
The key used when saving the message ID
Result
?MessageId
Returns the message ID if found, or null if not found
Example:
switch(botService.getMessageId("welcome-message")) {
  case(?msgId) {
    // Edit the message
    await* botService.editTextGroupMessage(groupId, msgId, null, "Updated text");
  };
  case(null) { /* Message ID not found */ };
}

deleteMessageId

Deletes a saved message ID.
public func deleteMessageId(
  key : Text
) : ()
key
Text
required
The key of the message ID to delete

deleteAllMessageIds

Clears all saved message IDs.
public func deleteAllMessageIds() : ()
Use with caution: This removes all stored message ID mappings.

Utility Functions

getNNSProposalMessageData

Extracts NNS proposal information from a message event.
public func getNNSProposalMessageData(
  message : OCApi.MessageEventWrapper
) : Result.Result<{proposalId : OCApi.ProposalId; messageIndex : OCApi.MessageIndex}, Text>
message
MessageEventWrapper
required
A message event from OpenChat
Result
Result.Result<ProposalData, Text>
  • #ok({ proposalId; messageIndex }) - Successfully extracted proposal data
  • #err("Not a governance proposal") - Message doesn’t contain a proposal
  • #err("Not a NNS proposal") - Proposal is SNS, not NNS
Example:
let #ok(messages) = await* botService.getGroupMessagesByIndex(groupId, [100], null)
else { return; };

for (msg in messages.messages.vals()) {
  switch(botService.getNNSProposalMessageData(msg)) {
    case(#ok(data)) {
      // Process NNS proposal: data.proposalId
    };
    case(#err(_)) { /* Not an NNS proposal message */ };
  };
}

Constants

  • BOT_REGISTRATION_FEE: 10T cycles (10,000,000,000,000)
  • MAX_AVATAR_SIZE: 800KB (819,200 bytes)
  • USER_INDEX_CANISTER: "4bkt6-4aaaa-aaaaf-aaaiq-cai"

Error Handling

All async functions in BotService return Result.Result<T, Text> types. Always handle both success and error cases:
switch(await* botService.someOperation()) {
  case(#ok(value)) {
    // Handle success
  };
  case(#err(message)) {
    // Log or handle error: message
  };
}
For functions that return variant responses (like SendMessageResponse), check the specific variant:
switch(await* botService.sendGroupMessage(groupId, content, null)) {
  case(#ok(#Success(data))) { /* Success */ };
  case(#ok(#TextTooLong(max))) { /* Handle text length error */ };
  case(#ok(#NotAuthorized)) { /* Handle authorization error */ };
  case(#err(msg)) { /* Handle trapped error */ };
}

Build docs developers (and LLMs) love