Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions motoko/examples/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
canister_ids.json
oc_public_key.pem
21 changes: 21 additions & 0 deletions motoko/examples/deploy_prod.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# The bot to deploy
BOT=$1

# Get the OpenChat public key
OC_PUBLIC_KEY=$(curl -s https://oc.app/public-key)

if [ $? -ne 0 ]; then
echo "OpenChat is not running on https://oc.app/public-key."
exit 1
fi

# Deploy the bot
dfx deploy --ic $BOT --argument "(\"$OC_PUBLIC_KEY\")" || exit 1

# Get the canister ID
CANISTER_ID=$(dfx canister --ic id $BOT) || exit 1

echo ""
echo "Principal: $CANISTER_ID"
echo "Endpoint: https://$CANISTER_ID.raw.icp0.io"
echo ""
3 changes: 2 additions & 1 deletion motoko/examples/moderator/events.mo
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ module {
apiGateway = eventWrapper.api_gateway;
grantedCommandPermissions = Permissions.fromRaw(event.granted_command_permissions);
grantedAutonomousPermissions = Permissions.fromRaw(event.granted_autonomous_permissions);
lastUpdated = eventWrapper.timestamp;
});
};
case (#Uninstalled event) {
state.installationRegistry.remove(event.location);
state.installationRegistry.remove(event.location, eventWrapper.timestamp);
};
};
};
Expand Down
39 changes: 36 additions & 3 deletions motoko/examples/moderator/main.mo
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import Http "mo:http-types";
import Sdk "mo:openchat-bot-sdk";

import Debug "mo:base/Debug";
import Definition "definition";
import Events "events";
import Http "mo:http-types";
import InstallationEvents "mo:openchat-bot-sdk/installationEvents";
import Nat "mo:base/Nat";
import Permissions "mo:openchat-bot-sdk/api/common/permissions";
import Principal "mo:base/Principal";
import Sdk "mo:openchat-bot-sdk";
import State "state";
import Timer "mo:base/Timer";

persistent actor class ModeratorBot(key : Text) {
var stableState = State.new();
Expand All @@ -26,4 +31,32 @@ persistent actor class ModeratorBot(key : Text) {
system func preupgrade() {
stableState := State.toStable(state);
};

private func onInitOrUpgrade<system>() {
ignore Timer.setTimer<system>(#seconds 0, func () : async () {
let userIndexCanisterId = Principal.fromText("vizcg-th777-77774-qaaea-cai");
ignore InstallationEvents.load(userIndexCanisterId, func (botId, events) {
Debug.print("Loaded " # Nat.toText(events.size()) # " events");
for (event in events.vals()) {
switch (event) {
case (#Installed e) {
state.installationRegistry.insert(e.location, {
apiGateway = e.api_gateway;
grantedCommandPermissions = Permissions.fromRaw(e.granted_permissions);
grantedAutonomousPermissions = Permissions.fromRaw(e.granted_autonomous_permissions);
lastUpdated = e.timestamp;
});
};
case (#Uninstalled e) {
state.installationRegistry.remove(e.location, e.timestamp);
};
};
};

state.botId := ?botId;
});
});
};

onInitOrUpgrade<system>();
};
2 changes: 1 addition & 1 deletion motoko/mops.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ csprng = "1.1.0"

[package]
name = "openchat-bot-sdk"
version = "0.7.0"
version = "0.8.0"
description = "An SDK for developing bots which plugin to the OpenChat bot framework + examples"
repository = "https://github.com/open-chat-labs/open-chat-bots"
keywords = [ "openchat", "sdk", "bots", "examples" ]
Expand Down
44 changes: 44 additions & 0 deletions motoko/src/api/oc/installationEvents.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import B "../common/base";
import I "../common/installationLocation";
import P "../common/permissions";

module {
public type Actor = actor {
bot_installation_events : (Args) -> async Response;
};

public type Args = {
from : Nat32;
size : Nat16;
};

public type Response = {
#Success : SuccessResult;
#Error : B.OCError;
};

public type SuccessResult = {
bot_id : B.UserId;
events : [BotInstallationEvent];
};

public type BotInstallationEvent = {
#Installed : BotInstalled;
#Uninstalled : BotUninstalled;
};

public type BotInstalled = {
location : I.InstallationLocation;
api_gateway : B.CanisterId;
granted_permissions : P.RawPermissions;
granted_autonomous_permissions : P.RawPermissions;
installed_by : B.UserId;
timestamp : B.TimestampMillis;
};

public type BotUninstalled = {
location : I.InstallationLocation;
uninstalled_by : B.UserId;
timestamp : B.TimestampMillis;
};
};
7 changes: 7 additions & 0 deletions motoko/src/client.mo
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import DeleteMessages "client/deleteMessages";
import SendMessage "client/sendMessage";
import AddReaction "client/addReaction";
import CommunitySummary "client/communitySummary";
import InstallationEvents "client/installationEvents";
import InviteUsers "client/inviteUsers";
import RemoveUser "client/removeUser";
import Members "client/members";
Expand Down Expand Up @@ -77,4 +78,10 @@ module {
Members.Builder(context, memberTypes);
};
};

public class UserIndexClient(userIndexCanisterId : B.CanisterId) {
public func installationEvents() : InstallationEvents.Builder {
InstallationEvents.Builder(userIndexCanisterId);
};
}
};
39 changes: 39 additions & 0 deletions motoko/src/client/installationEvents.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Error "mo:base/Error";
import Principal "mo:base/Principal";
import Result "mo:base/Result";
import B "../api/common/base";
import InstallationEvents "../api/oc/installationEvents";

module {
public class Builder(userIndexCanisterId : B.CanisterId) = this {
var from : Nat32 = 0;
var size : Nat16 = 5000;

public func withFrom(value : Nat32) : Builder {
from := value;
this;
};

public func withSize(value : Nat16) : Builder {
size := value;
this;
};

public func execute() : async Result.Result<InstallationEvents.Response, (Error.ErrorCode, Text)> {
let botApiActor = actor (Principal.toText(userIndexCanisterId)) : InstallationEvents.Actor;

try {
let response = await botApiActor.bot_installation_events({
from;
size;
});

#ok response;
} catch (error) {
#err((Error.code(error), Error.message(error)));
};
};
};

public type Result = Result.Result<InstallationEvents.Response, Error.Error>;
};
52 changes: 52 additions & 0 deletions motoko/src/installationEvents.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import Array "mo:base/Array";
import Base "api/common/base";
import Client "client";
import Debug "mo:base/Debug";
import I "api/oc/installationEvents";
import Nat16 "mo:base/Nat16";
import Timer "mo:base/Timer";

module {
type EventsCallback = (Base.UserId, [I.BotInstallationEvent]) -> ();

let pageSize : Nat16 = 5_000;

public func load<system>(userIndexCanisterId: Base.CanisterId, callback : EventsCallback) : async () {
ignore loadPage<system>(userIndexCanisterId, [], 0, callback);
};

func loadPage<system>(
userIndexCanisterId: Base.CanisterId,
events: [I.BotInstallationEvent],
from : Nat32,
callback : EventsCallback)
: async () {
let response = await Client.UserIndexClient(userIndexCanisterId)
.installationEvents()
.withFrom(from)
.withSize(pageSize)
.execute();

switch (response) {
case (#ok(#Success result)) {
let count = result.events.size();
let aggregatedEvents = Array.append(events, result.events);

if (count == Nat16.toNat(pageSize)) {
ignore Timer.setTimer<system>(#seconds 0, func () : async () {
ignore loadPage<system>(
userIndexCanisterId,
aggregatedEvents,
from + Nat16.toNat32(pageSize),
callback);
});
} else {
callback(result.bot_id, aggregatedEvents);
}
};
case other {
Debug.print("Failed to load installation events: " # debug_show(other));
};
};
};
};
24 changes: 20 additions & 4 deletions motoko/src/installationRegistry.mo
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,27 @@ module {
InstallationLocation.equal,
InstallationLocation.hash);

public func insert(location: InstallationLocation, details: InstallationRecord) {
map.put(location, details);
public func insert(location: InstallationLocation, newRecord: InstallationRecord) {
switch (map.get(location)) {
case (?record) {
if (newRecord.lastUpdated <= record.lastUpdated) {
return;
}
};
case null {};
};

map.put(location, newRecord);
};

public func remove(location: InstallationLocation) {
map.delete(location);
public func remove(location: InstallationLocation, timestamp: Base.TimestampMillis) {
let ?record = map.get(location) else {
return;
};

if (timestamp > record.lastUpdated) {
map.delete(location);
}
};

public func get(location: InstallationLocation) : ?InstallationRecord {
Expand All @@ -40,5 +55,6 @@ module {
apiGateway: Base.CanisterId;
grantedCommandPermissions: Permissions.Permissions;
grantedAutonomousPermissions: Permissions.Permissions;
lastUpdated : Base.TimestampMillis;
};
};
15 changes: 6 additions & 9 deletions rs/sdk/src/installation_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,13 @@ impl InstallationRegistry {
}

pub fn insert(&mut self, location: InstallationLocation, new_record: InstallationRecord) {
if let Some(record) = self.locations.get_mut(&location) {
if new_record.last_updated > record.last_updated {
record.api_gateway = new_record.api_gateway;
record.granted_command_permissions = new_record.granted_command_permissions;
record.granted_autonomous_permissions = new_record.granted_autonomous_permissions;
record.last_updated = new_record.last_updated;
}
} else {
self.locations.insert(location, new_record);
if let Some(record) = self.locations.get_mut(&location)
&& new_record.last_updated <= record.last_updated
{
return;
}

self.locations.insert(location, new_record);
}

pub fn remove(&mut self, location: &InstallationLocation, timestamp: TimestampMillis) {
Expand Down