Skip to content

Commit

Permalink
Merge pull request #7 from fabriguespe/access_v2
Browse files Browse the repository at this point in the history
Update access docs
  • Loading branch information
humanagent authored Jun 4, 2024
2 parents b6e0f54 + ea97f9c commit 3a1f828
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 130 deletions.
5 changes: 0 additions & 5 deletions examples/group/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@ run(async (context: HandlerContext) => {
} else if (typeId == "reply") {
const { receiver, content: reply } = content;
if (receiver && reply.includes("degen")) await degenHandler(context);
} else if (content.content == "/access" && typeId == "silent") {
if (senderAddress) {
/*here put the token gated logic*/
context.grant_access();
}
} else if (typeId == "text") {
if (content.startsWith("@bot")) {
await gptHandler(context, commands);
Expand Down
26 changes: 0 additions & 26 deletions packages/botkit/src/lib/handler-context.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { DecodedMessage } from "@xmtp/xmtp-js";
import { ContentTypeBotMessage } from "../content-types/Bot.js";
import { ContentTypeSilent } from "../content-types/Silent.js";

export default class HandlerContext {
message: DecodedMessage;
Expand All @@ -20,31 +19,6 @@ export default class HandlerContext {
async textReply(content: any) {
await this.message.conversation.send(content);
}
async grant_access() {
await this.message.conversation.send(
{
content: "grant_access",
metadata: {
access: true,
...this.context,
},
},
{
contentType: ContentTypeSilent,
},
);
}
async ping() {
await this.message.conversation.send(
{
content: "ping",
metadata: { ...this.context },
},
{
contentType: ContentTypeSilent,
},
);
}
async reply(
message: string,
receivers: string[] = [],
Expand Down
72 changes: 63 additions & 9 deletions packages/botkit/src/lib/runner.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,88 @@
import xmtpClient from "./client.js";
import HandlerContext from "./handler-context.js";
import { ContentTypeSilent } from "../content-types/Silent.js";

type Handler = (context: HandlerContext) => Promise<void>;

export default async function run(handler: Handler, newBotConfig?: any) {
export default async function run(
handler: Handler,
newBotConfig?: any,
accessHandler?: (context: HandlerContext) => Promise<boolean>,
) {
const client = await xmtpClient(newBotConfig);

const { address } = client;
for await (const message of await client.conversations.streamAllMessages()) {
try {
if (message.senderAddress === client.address) {
const {
senderAddress,
content,
contentType: { typeId },
} = message;

if (senderAddress === address) {
// if same address do nothing
continue;
} else if (message.contentType.typeId == "bot") {
} else if (typeId == "bot") {
//if a bot speaks do nothing
continue;
}
const context = new HandlerContext(
message,
newBotConfig?.context ?? {},
client.address,
address,
);
if (
message.contentType.typeId == "silent" &&
message.content?.content === "/ping"
typeId == "silent" &&
content?.content === "/access" &&
accessHandler
) {
//if a bot speaks do nothing
context.ping();
} else await handler(context);
const accept = await accessHandler(context);
if (accept) {
// add to group
grant_access(message, newBotConfig?.context);
continue;
}
} else if (typeId == "silent" && content?.content === "/ping") {
//if a bot speaks do nothing
ping(message, newBotConfig?.context, accessHandler);
continue;
}

await handler(context);
} catch (e) {
console.log(`error`, e);
}
}
}

async function grant_access(message: any, context: any) {
// add group member
await message.conversation.send(
{
content: "",
metadata: {
type: "access",
...context,
},
},
{
contentType: ContentTypeSilent,
},
);
}
async function ping(message: any, context: any, accessHandler: any) {
await message.conversation.send(
{
content: "",
metadata: {
type: "ping",
access: accessHandler ? true : false,
...context,
},
},
{
contentType: ContentTypeSilent,
},
);
}
96 changes: 27 additions & 69 deletions packages/docs/docs/pages/access.mdx
Original file line number Diff line number Diff line change
@@ -1,96 +1,54 @@
# Access

For declaring that the bot will handle access we need to add the access command to the commands file
Create the `accessHandler` function to handle the access request. Returns true or false based on the access request.

```tsx [src/commands.ts]
export const commands = [
{
name: "General Commands",
icon: "🔧",
description: "Command for managing default behaviours.",
commands: [
/* Declare access command*/
{
command: "/access",
description: "Grant access to a user to the bot.",
},
/* Other commands */
],
},
];
```

### Declare commands

To declare the access command in the botkit app, you need to add the following code:
const accessHandler = async (context: HandlerContext): Promise<boolean> => {
const { senderAddress } = context.message;

```tsx
import { commands } from "./commands.js";
/*here put the token gated logic*/

const newBotConfig = {
context: {
commands: commands,
},
if (senderAddress) return true;
return false;
};

run(async (context: HandlerContext) => {
//Your logic here
}, newBotConfig);
```

For allowing access to a new user botkit receives a `silent` request with this structure
### Send it as parameter

```tsx
{
content: "/access",
metadata: {}
}
```

### Receive an access request

To accept an access request in your app, you can use the following code:
To declare the access command in the botkit app, you need to add the following code:

```jsx
if (typeId == "silent") {
const { content: command } = content;
// Do something with the command
if (command == "/access")
if (senderAddress) {
/*here put the token gated logic*/
// accept the access request
context.grant_access();
}
}
```tsx
run(
async (context: HandlerContext) => {
/* Bot logic here */
},
{
/* Additional bot config*/
},
accessHandler,
);
```

### Full example

```jsx [src/index.ts]
import "dotenv/config";
import { run, HandlerContext } from "@xmtp/botkit";
import { commands } from "./commands.js";

const newBotConfig = {
context: {
commands: commands,
},
const accessHandler = async (context: HandlerContext): Promise<boolean> => {
const { senderAddress } = context.message;

/*here put the token gated logic*/

if (senderAddress) return true;
return false;
};

run(async (context: HandlerContext) => {
const { content, contentType, senderAddress } = context.message;
const { typeId } = contentType;

if (typeId == "silent") {
const { content: command } = content;
// Do something with the command
if (command == "/access")
if (senderAddress) {
/*here put the token gated logic*/
// accept the access request
context.grant_access();
}
}
context.reply("gm!");
}, newBotConfig);

}, {}, accessHandler);
```
24 changes: 16 additions & 8 deletions packages/playground/src/Groups/ListConversations.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,16 +248,24 @@ export const ListConversations = ({
return conversations.map((conversation, index) => {
let lastMessageContent = "";
try {
let test =
lastMessages.find((msg) => msg.topic === conversation.topic)
?.content || "...";
if (test.content) {
test = test.content;
if (test.emoji) {
test = lastMessageContent.content;
let lastMessage = lastMessages.find(
(msg) => msg.topic === conversation.topic,
);
let test = "";
if (lastMessage) {
test = lastMessage.content;
if (lastMessage.emoji) {
test = lastMessage.emoji;
} else if (lastMessage.content?.content?.content) {
test = lastMessage.content?.content?.content;
} else if (lastMessage.content?.content) {
test = lastMessage.content?.content;
} else if (lastMessage.content) {
test = lastMessage.content;
}
console.log(typeof test, test, "test");
if (typeof test === "string") lastMessageContent = test;
}
lastMessageContent = test;
} catch (error) {
console.error("Failed to fetch last message:", error);
}
Expand Down
33 changes: 20 additions & 13 deletions packages/playground/src/Groups/MessageContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,17 +146,18 @@ export const MessageContainer = ({
if (
newMessage.contentType.sameAs(ContentTypeSilent) &&
newMessage.senderAddress !== client.address &&
newMessage.content.content === "grant_access"
newMessage.content?.metadata?.type === "access"
) {
console.log("grant_access", true);
const localStorageKey = `accessGranted-${client.address}-${conversation.topic}`;
localStorage.setItem(localStorageKey, "true");
setHasAccess(true);
} else if (
newMessage.contentType.sameAs(ContentTypeSilent) &&
newMessage.senderAddress !== client.address &&
newMessage.content.content === "ping"
newMessage.content?.metadata?.type === "ping"
) {
let context = newMessage.content.metadata;
console.log("context", newMessage.content);
//console.log("context", newMessage.content);
if (context.commands) {
setCommands(context.commands);
setBotCommands(context.commands);
Expand All @@ -165,6 +166,11 @@ export const MessageContainer = ({
setUsers(context.users);
setUsersData(context.users);
}
const access = newMessage.content?.metadata?.access;

if (access) {
setHasRequiredAccess(true);
}
} else if (
newMessage.contentType.sameAs(ContentTypeBotMessage) &&
newMessage.content.metadata
Expand Down Expand Up @@ -364,18 +370,19 @@ export const MessageContainer = ({
setTextInputValue(value);
};
const [hasAccess, setHasAccess] = useState(true);
const [hasRequiredAccess, setHasRequiredAccess] = useState(false);

useEffect(() => {
if (commands.length > 0 && !hasAccess) {
console.log(commands, hasAccess);
// Assuming that the presence of "/access" means access needs to be requested or granted
const accessRequired = commands.some((command) =>
command.commands?.some((item) => item.command === "/access"),
);
// Set hasAccess to false if "/access" is present, indicating that access is not yet granted
setHasAccess(!accessRequired);
console.log(hasAccess, hasRequiredAccess, "gest");
if (hasRequiredAccess) {
console.log(hasRequiredAccess, "hasRequiredAccess");
// Generate a unique key for local storage based on the user and conversation
const localStorageKey = `accessGranted-${client.address}-${conversation.topic}`;
// Check local storage to see if access has already been granted for this conversation
const accessGranted = localStorage.getItem(localStorageKey) === "true";
setHasAccess(accessGranted);
}
}, [commands]);
}, [hasRequiredAccess, hasAccess]);

const sendAccess = async () => {
try {
Expand Down

0 comments on commit 3a1f828

Please sign in to comment.