create-message-kit 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,2 +1 @@
1
- KEY= # 0x... the private key of the bot wallet (with the 0x prefix)
2
- MSG_LOG=false # logs the message on the console
1
+ KEY= # 0x... the private key of the bot wallet (with the 0x prefix)
@@ -3,7 +3,7 @@ import { run, HandlerContext } from "@xmtp/message-kit";
3
3
  run(async (context: HandlerContext) => {
4
4
  // Get the message and the address from the sender
5
5
  const { content, sender } = context.message;
6
-
6
+ console.log(content);
7
7
  // To reply, just call `reply` on the HandlerContext.
8
- await context.reply(`gm`);
8
+ await context.send(`gm`);
9
9
  });
@@ -1,4 +1,3 @@
1
1
  KEY= # 0x... the private key of the bot wallet (with the 0x prefix)
2
2
  OPEN_AI_API_KEY= # openai api key
3
- STACK_API_KEY= # stack api key
4
- MSG_LOG=false # logs the message on the console
3
+ STACK_API_KEY= # stack api key
@@ -1,14 +1,21 @@
1
1
  import type { CommandGroup } from "@xmtp/message-kit";
2
+ import { handler as tipping } from "./handler/tipping.js";
3
+ import { handler as agent } from "./handler/agent.js";
4
+ import { handler as transaction } from "./handler/transaction.js";
5
+ import { handler as games } from "./handler/game.js";
6
+ import { handler as admin } from "./handler/moderation.js";
7
+ import { handler as loyalty } from "./handler/loyalty.js";
2
8
 
3
9
  export const commands: CommandGroup[] = [
4
10
  {
5
11
  name: "Tipping",
6
- icon: "🎩",
7
12
  description: "Tip tokens via emoji, replies or command.",
13
+ triggers: ["/tip", "🎩", "@tip"],
8
14
  commands: [
9
15
  {
10
16
  command: "/tip [@users] [amount] [token]",
11
17
  description: "Tip users in a specified token.",
18
+ handler: tipping,
12
19
  params: {
13
20
  username: {
14
21
  default: "",
@@ -23,14 +30,15 @@ export const commands: CommandGroup[] = [
23
30
  ],
24
31
  },
25
32
  {
26
- name: "Base Transactions",
27
- icon: "🖼️",
33
+ name: "Transactions",
34
+ triggers: ["@send", "/send", "@swap", "/swap", "/show"],
28
35
  description: "Multipurpose transaction frame built onbase.",
29
36
  commands: [
30
37
  {
31
38
  command: "/send [amount] [token] [@username]",
32
39
  description:
33
40
  "Send a specified amount of a cryptocurrency to a destination address.",
41
+ handler: transaction,
34
42
  params: {
35
43
  amount: {
36
44
  default: 10,
@@ -50,6 +58,7 @@ export const commands: CommandGroup[] = [
50
58
  {
51
59
  command: "/swap [amount] [token_from] [token_to]",
52
60
  description: "Exchange one type of cryptocurrency for another.",
61
+ handler: transaction,
53
62
  params: {
54
63
  amount: {
55
64
  default: 10,
@@ -67,70 +76,28 @@ export const commands: CommandGroup[] = [
67
76
  },
68
77
  },
69
78
  },
70
- {
71
- command: "/mint [collection_address] [token_id]",
72
- description: "Create (mint) a new token or NFT.",
73
- params: {
74
- collection: {
75
- default: "0x73a333cb82862d4f66f0154229755b184fb4f5b0",
76
- type: "address",
77
- },
78
- tokenId: {
79
- default: 1,
80
- type: "number",
81
- },
82
- },
83
- },
84
79
  {
85
80
  command: "/show",
81
+ handler: transaction,
86
82
  description: "Show the whole frame.",
87
83
  params: {},
88
84
  },
89
85
  ],
90
86
  },
91
- {
92
- name: "Betting",
93
- icon: "🎰",
94
- description: "Create bets with friends.",
95
- commands: [
96
- {
97
- command: "/bet @users [name] [amount] [token]",
98
- description: "Bet on basebet.",
99
- params: {
100
- username: {
101
- default: "",
102
- type: "username",
103
- },
104
- name: {
105
- default: "",
106
- type: "quoted",
107
- },
108
- amount: {
109
- default: 10,
110
- type: "number",
111
- },
112
- token: {
113
- default: "eth",
114
- type: "string",
115
- values: ["eth", "dai", "usdc", "degen"],
116
- },
117
- },
118
- },
119
- ],
120
- },
121
87
  {
122
88
  name: "Games",
123
- icon: "🕹️",
89
+ triggers: ["/game", "@game", "🔎", "🔍"],
124
90
  description: "Provides various gaming experiences.",
125
91
  commands: [
126
92
  {
127
93
  command: "/game [game]",
94
+ handler: games,
128
95
  description: "Play a game.",
129
96
  params: {
130
97
  game: {
131
98
  default: "",
132
99
  type: "string",
133
- values: ["wordle", "slot", "guessr", "rockpaperscissors", "help"],
100
+ values: ["wordle", "slot", "help"],
134
101
  },
135
102
  },
136
103
  },
@@ -138,16 +105,18 @@ export const commands: CommandGroup[] = [
138
105
  },
139
106
  {
140
107
  name: "Loyalty",
141
- icon: "🔓",
108
+ triggers: ["/points", "@points", "/leaderboard", "@leaderboard"],
142
109
  description: "Manage group members and metadata.",
143
110
  commands: [
144
111
  {
145
112
  command: "/points",
113
+ handler: loyalty,
146
114
  description: "Check your points.",
147
115
  params: {},
148
116
  },
149
117
  {
150
118
  command: "/leaderboard",
119
+ handler: loyalty,
151
120
  description: "Check the points of a user.",
152
121
  params: {},
153
122
  },
@@ -155,16 +124,17 @@ export const commands: CommandGroup[] = [
155
124
  },
156
125
  {
157
126
  name: "Agent",
158
- icon: "🤖",
127
+ triggers: ["/agent", "@agent"],
159
128
  description: "Manage agent commands.",
160
129
  commands: [
161
130
  {
162
131
  command: "/agent [prompt]",
132
+ handler: agent,
163
133
  description: "Manage agent commands.",
164
134
  params: {
165
135
  prompt: {
166
136
  default: "",
167
- type: "string",
137
+ type: "prompt",
168
138
  },
169
139
  },
170
140
  },
@@ -172,11 +142,12 @@ export const commands: CommandGroup[] = [
172
142
  },
173
143
  {
174
144
  name: "Admin",
175
- icon: "🔐",
145
+ triggers: ["/add", "@add", "/remove", "@remove"],
176
146
  description: "Manage group members and metadata.",
177
147
  commands: [
178
148
  {
179
149
  command: "/add [username]",
150
+ handler: admin,
180
151
  description: "Add a user.",
181
152
  params: {
182
153
  username: {
@@ -187,6 +158,7 @@ export const commands: CommandGroup[] = [
187
158
  },
188
159
  {
189
160
  command: "/remove [username]",
161
+ handler: admin,
190
162
  description: "Remove a user.",
191
163
  params: {
192
164
  username: {
@@ -195,15 +167,26 @@ export const commands: CommandGroup[] = [
195
167
  },
196
168
  },
197
169
  },
170
+ ],
171
+ },
172
+ {
173
+ name: "Split Payments",
174
+ image: true,
175
+ triggers: [],
176
+ description: "Split payments between users.",
177
+ commands: [],
178
+ },
179
+ {
180
+ name: "Help",
181
+ triggers: ["/help"],
182
+
183
+ description: "Get help with the bot.",
184
+ commands: [
198
185
  {
199
- command: "/name [name]",
200
- description: "Set the name of the group.",
201
- params: {
202
- name: {
203
- default: "",
204
- type: "quoted",
205
- },
206
- },
186
+ command: "/help",
187
+ handler: undefined,
188
+ description: "Get help with the bot.",
189
+ params: {},
207
190
  },
208
191
  ],
209
192
  },
@@ -16,9 +16,7 @@ export async function handler(context: HandlerContext) {
16
16
  const systemPrompt = generateSystemPrompt(context);
17
17
  try {
18
18
  let userPrompt = params?.prompt ?? content;
19
- if (process?.env?.MSG_LOG === "true") {
20
- console.log("userPrompt", userPrompt);
21
- }
19
+ console.log("userPrompt", userPrompt);
22
20
 
23
21
  const { reply } = await textGeneration(userPrompt, systemPrompt);
24
22
  console.log("intent:", reply);
@@ -36,15 +34,29 @@ function generateSystemPrompt(context: HandlerContext) {
36
34
  message: { sender },
37
35
  } = context;
38
36
 
39
- const systemPrompt = `You are a helpful agent that lives inside a web3 messaging group.\n
40
- These are the users of the group: ${JSON.stringify(members?.map((member: User) => ({ ...member, username: `@${member.username}` })))}\n
41
- This group app has many commands available: ${JSON.stringify(commands)}\n
42
- If a user asks jokes, make jokes about web3 devs\n
43
- If the user asks about performing an action and you can think of a command that would help, answer directly with the command and nothing else. Populate the command with the correct or random values. Always return commands with real values only, using usernames with @ and excluding addresses.\n
44
- If the user asks a question or makes a statement that does not clearly map to a command, respond with helpful information or a clarification question.\n
37
+ const systemPrompt = `
38
+ ### Context
39
+
40
+ You are a helpful bot agent that lives inside a web3 messaging group that helps interpret user requests and execute commands.
41
+ #### Users
42
+ ${JSON.stringify(members?.map((member: User) => ({ ...member, username: `@${member.username}` })))}\n
43
+ #### Commands
44
+ ${JSON.stringify(commands)}\n
45
45
  The message was sent by @${sender?.username}
46
- Everytime your are thanked, ask for a tip or whever you feel like it.
47
- .`;
46
+
47
+ ### Example s
48
+ prompt: /agent lets add @user
49
+ reply: /add @user
48
50
 
51
+ prompt /agent tip alix and bo
52
+ reply /tip @alix @bo 10
53
+
54
+ Important:
55
+ - If a user asks jokes, make jokes about web3 devs\n
56
+ - If the user asks about performing an action and you can think of a command that would help, answer directly with the command and nothing else.
57
+ - Populate the command with the correct or random values. Always return commands with real values only, using usernames with @ and excluding addresses.\n
58
+ - If the user asks a question or makes a statement that does not clearly map to a command, respond with helpful information or a clarification question.\n
59
+ - If the user is grateful, respond asking for a tip in a playful manner.
60
+ `;
49
61
  return systemPrompt;
50
62
  }
@@ -4,34 +4,38 @@ import { HandlerContext } from "@xmtp/message-kit";
4
4
  export async function handler(context: HandlerContext) {
5
5
  const {
6
6
  message: {
7
- content: {
8
- command,
9
- params: { game },
10
- },
7
+ content: { command, params },
11
8
  },
12
9
  } = context;
13
-
10
+ if (!command) {
11
+ const { content: text } = context?.message?.content;
12
+ if (text === "🔎" || text === "🔍") {
13
+ // Send the URL for the requested game
14
+ context.reply("https://framedl.xyz/");
15
+ }
16
+ return;
17
+ }
14
18
  // URLs for each game type
15
19
  const gameUrls: { [key: string]: string } = {
16
- wordle: "https://openframedl.vercel.app/",
20
+ wordle: "https://framedl.xyz/",
17
21
  slot: "https://slot-machine-frame.vercel.app/",
18
22
  };
19
-
20
23
  // Respond with the appropriate game URL or an error message
21
- switch (game) {
24
+ switch (params.game) {
22
25
  case "wordle":
23
26
  case "slot":
24
27
  // Retrieve the URL for the requested game using a simplified variable assignment
25
- const gameUrl = gameUrls[game];
28
+ const gameUrl = gameUrls[params.game];
26
29
  // Send the URL for the requested game
27
- context.reply(gameUrl);
30
+ context.send(gameUrl);
28
31
  break;
32
+
29
33
  case "help":
30
- context.reply("Available games: \n/game wordle\n/game slot");
34
+ context.send("Available games: \n/game wordle\n/game slot");
31
35
  break;
32
36
  default:
33
37
  // Inform the user about unrecognized commands and provide available options
34
- context.reply(
38
+ context.send(
35
39
  "Command not recognized. Available games: wordle, slot, or help.",
36
40
  );
37
41
  }
@@ -1,5 +1,5 @@
1
1
  import { HandlerContext, User } from "@xmtp/message-kit";
2
- import { getStackClient, StackClient } from "../lib/stack.js";
2
+ import { getStackClient } from "../lib/stack.js";
3
3
 
4
4
  export async function handler(context: HandlerContext, fake?: boolean) {
5
5
  const stack = getStackClient();
@@ -7,18 +7,20 @@ export async function handler(context: HandlerContext, fake?: boolean) {
7
7
  members,
8
8
  group,
9
9
  getMessageById,
10
- message: { id, content, sender, typeId },
10
+ message: {
11
+ content,
12
+ content: { command },
13
+ sender,
14
+ typeId,
15
+ },
11
16
  } = context;
12
- if (!group) return;
13
- if (fake && stack) {
14
- //for fake demo
15
- fakeReaction(sender.username, sender.address, id, stack, context);
16
- return;
17
- } else if (typeId === "text") {
17
+ console.log(command);
18
+ if (typeId === "text" && group) {
18
19
  const { command } = content;
19
20
  if (command === "points") {
20
21
  const points = await stack?.getPoints(sender.address);
21
22
  context.reply(`You have ${points} points`);
23
+ return;
22
24
  } else if (command === "leaderboard") {
23
25
  const leaderboard = await stack?.getLeaderboard();
24
26
  const formattedLeaderboard = leaderboard?.leaderboard
@@ -33,8 +35,9 @@ export async function handler(context: HandlerContext, fake?: boolean) {
33
35
  context.reply(
34
36
  `Leaderboard:\n\n${formattedLeaderboard}\n\nCheck out the public leaderboard\nhttps://www.stack.so/leaderboard/degen-group`,
35
37
  );
38
+ return;
36
39
  }
37
- } else if (typeId === "group_updated") {
40
+ } else if (typeId === "group_updated" && group) {
38
41
  const { initiatedByInboxId, addedInboxes } = content;
39
42
  const adminAddress = members?.find(
40
43
  (member: User) => member.inboxId === initiatedByInboxId,
@@ -47,7 +50,7 @@ export async function handler(context: HandlerContext, fake?: boolean) {
47
50
  uniqueId: adminAddress?.username ?? "",
48
51
  });
49
52
  }
50
- } else if (typeId === "reaction") {
53
+ } else if (typeId === "reaction" && group) {
51
54
  const { content: emoji, action } = content;
52
55
  const msg = await getMessageById(content.reference);
53
56
  if (action === "added") {
@@ -68,27 +71,3 @@ export async function handler(context: HandlerContext, fake?: boolean) {
68
71
  }
69
72
  }
70
73
  }
71
- async function fakeReaction(
72
- username: string,
73
- address: string,
74
- id: string,
75
- stack: StackClient,
76
- context: HandlerContext,
77
- ) {
78
- if (username === "me") {
79
- if (Math.random() < 0.1) {
80
- //Fake reactions
81
- const emojis = ["😀", "👍", "🎩", "🐐", "🔥"];
82
- const randomEmoji = emojis[Math.floor(Math.random() * emojis.length)];
83
- context.react(randomEmoji);
84
- let points = 1;
85
- if (randomEmoji === "🎩") {
86
- points = 10;
87
- }
88
- await stack?.track("reaction", {
89
- points,
90
- account: address,
91
- });
92
- }
93
- }
94
- }
@@ -0,0 +1,110 @@
1
+ import { HandlerContext } from "@xmtp/message-kit";
2
+ import type { User } from "@xmtp/message-kit";
3
+
4
+ // Reusable function to handle adding members
5
+ function handleAddMembers(
6
+ addedInboxes: { inboxId: string }[],
7
+ members: User[],
8
+ ) {
9
+ const addedNames = members
10
+ ?.filter((member: User) =>
11
+ addedInboxes.some(
12
+ (added: { inboxId: string }) =>
13
+ added?.inboxId?.toLowerCase() === member?.inboxId?.toLowerCase(),
14
+ ),
15
+ )
16
+ .map((member: User) => member.username)
17
+ .filter((username: string) => username && username.trim() !== "")
18
+ .map((username: string) => `@${username}`)
19
+ .join(", "); // Join names for message formatting
20
+
21
+ if (addedNames) {
22
+ let messages = [
23
+ `Yo, ${addedNames}! 🫡`,
24
+ `LFG ${addedNames}!`,
25
+ `${addedNames}🤝`,
26
+ ];
27
+ return messages[Math.floor(Math.random() * messages.length)];
28
+ }
29
+ return "";
30
+ }
31
+ function handleRemoveMembers() {
32
+ let messages = [`🪦`, `👻`, `hasta la vista, baby`];
33
+ return messages[Math.floor(Math.random() * messages.length)];
34
+ }
35
+
36
+ export async function handler(context: HandlerContext) {
37
+ const {
38
+ group,
39
+ members,
40
+ message: { content, typeId },
41
+ } = context;
42
+ if (typeId === "group_updated" && group) {
43
+ const { removedInboxes, addedInboxes } = content;
44
+ let message: string = "";
45
+ if (addedInboxes && addedInboxes.length > 0) {
46
+ message += handleAddMembers(addedInboxes, members!);
47
+ } else if (removedInboxes && removedInboxes.length > 0) {
48
+ message += handleRemoveMembers();
49
+ }
50
+ await context.send(message);
51
+ } else if (typeId === "text" && group) {
52
+ const {
53
+ command,
54
+ params: { username, name },
55
+ params,
56
+ } = content;
57
+
58
+ console.log("removedInboxes", command, params);
59
+
60
+ switch (command) {
61
+ case "remove":
62
+ try {
63
+ if (!Array.isArray(username)) {
64
+ context.reply("Wrong username");
65
+ return;
66
+ }
67
+ const removedInboxes = username?.map((user: User) => user.inboxId);
68
+ if (!removedInboxes || removedInboxes.length === 0) {
69
+ context.reply("Wrong username");
70
+ return;
71
+ }
72
+ await group.sync();
73
+ await group.removeMembersByInboxId(removedInboxes);
74
+ const messages = handleRemoveMembers();
75
+ context.reply(messages);
76
+ } catch (error) {
77
+ context.reply("Error: Check admin privileges");
78
+ console.error(error);
79
+ }
80
+ break;
81
+ case "add":
82
+ try {
83
+ if (!Array.isArray(username)) {
84
+ context.reply("Wrong username");
85
+ return;
86
+ }
87
+ const addedInboxes = username?.map((user: User) =>
88
+ user?.inboxId?.toLowerCase(),
89
+ );
90
+ if (!addedInboxes || addedInboxes.length === 0) {
91
+ context.reply("Wrong username");
92
+ return;
93
+ }
94
+ await group.sync();
95
+ await group.addMembersByInboxId(addedInboxes);
96
+ await group.sync();
97
+ const messages = handleAddMembers(
98
+ [{ inboxId: addedInboxes[0] }],
99
+ members!,
100
+ );
101
+ context.reply(messages);
102
+ } catch (error) {
103
+ context.reply("Error: Check admin privileges");
104
+ console.error(error);
105
+ }
106
+ break;
107
+ }
108
+ }
109
+ return;
110
+ }
@@ -0,0 +1,62 @@
1
+ import { HandlerContext } from "@xmtp/message-kit";
2
+ import { vision, textGeneration } from "../lib/openai.js";
3
+
4
+ export async function handler(context: HandlerContext) {
5
+ if (!process?.env?.OPEN_AI_API_KEY) {
6
+ console.log("No OPEN_AI_API_KEY found in .env");
7
+ return;
8
+ }
9
+ const {
10
+ members,
11
+ commands,
12
+ message: {
13
+ typeId,
14
+ content: { attachment },
15
+ sender,
16
+ },
17
+ } = context;
18
+
19
+ if (attachment && typeId === "remoteStaticAttachment") {
20
+ const { data, filename, mimeType } = attachment;
21
+ const response = await vision(
22
+ data,
23
+ "This image is the bill of a restaurant dinner. Return the total. If you can't find the total, return 'undefined'.",
24
+ );
25
+ if (response?.includes("undefined")) {
26
+ return;
27
+ } else {
28
+ context.reply(
29
+ "You uploaded a new bill. Let's go ahead and split the bill.",
30
+ );
31
+ }
32
+ if (response) {
33
+ const prompt = `You a split wise agent that splits the bill between the members of this group except for the sender and bot.\n
34
+ These are the users of the group: ${JSON.stringify(members?.map((member) => ({ ...member, username: `@${member.username}` })))}\n
35
+ This group app has many commands available: ${JSON.stringify(commands)}\n
36
+
37
+
38
+ ## Instructions:
39
+ When you receive the totals you should split the bill between the members of the group and send to each one a transaction frame
40
+ - For the sake of this demo. Only send the payment to the sender not to all the other members.
41
+
42
+ ### Return message
43
+ Don't use markdown. Return messages in a json object The first message detailing the split. The second one you will send the command for the receiver to pay directly to the sender.
44
+ Example:
45
+ [
46
+ "This are the details: Total: $49.52. Tip (20%): $9.90",
47
+ "All users owe X USDC to @${sender?.username}. Pay here:",
48
+ "/send @${sender?.username} $9.90"
49
+ ]
50
+ `;
51
+
52
+ //I want the reply to be an array of messages so the bot feels like is sending multuple ones
53
+ const { reply } = await textGeneration(response, prompt);
54
+ let splitMessages = JSON.parse(reply);
55
+ for (const message of splitMessages) {
56
+ let msg = message as string;
57
+ if (msg.startsWith("/")) await context.intent(msg);
58
+ else await context.reply(msg);
59
+ }
60
+ }
61
+ }
62
+ }
@@ -6,12 +6,10 @@ export async function handler(context: HandlerContext) {
6
6
  getMessageById,
7
7
  message: { content, sender, typeId },
8
8
  } = context;
9
-
10
9
  const msg = await getMessageById(content.reference);
11
10
  const replyReceiver = members?.find(
12
11
  (member) => member.inboxId === msg?.senderInboxId,
13
12
  );
14
-
15
13
  let amount: number = 0,
16
14
  receivers: User[] = [];
17
15
  // Handle different types of messages
@@ -51,7 +49,6 @@ export async function handler(context: HandlerContext) {
51
49
  return;
52
50
  }
53
51
  const receiverAddresses = receivers.map((receiver) => receiver.address);
54
-
55
52
  // Process sending tokens to each receiver
56
53
  context.sendTo(
57
54
  `You received ${amount} tokens from ${sender.address}.`,
@@ -46,23 +46,6 @@ export async function handler(context: HandlerContext) {
46
46
  });
47
47
  context.reply(`${url_swap}`);
48
48
  break;
49
- case "mint":
50
- // Destructure and provide default values for the mint command
51
- const { collection, tokenId } = params; // [!code hl] // [!code focus]
52
-
53
- if (!collection || !tokenId) {
54
- context.reply(
55
- "Missing required parameters. Please provide collection address and token id.",
56
- );
57
- return;
58
- }
59
- // Generate URL for the mint transaction
60
- let url_mint = generateFrameURL(baseUrl, "mint", {
61
- collection,
62
- token_id: tokenId,
63
- });
64
- context.reply(`${url_mint}`);
65
- break;
66
49
  case "show": // [!code hl] // [!code focus]
67
50
  // Show the base URL without the transaction path
68
51
  context.reply(`${baseUrl.replace("/transaction", "")}`);
@@ -1,61 +1,8 @@
1
- import { run, HandlerContext, CommandHandlers } from "@xmtp/message-kit";
2
- import { commands } from "./commands.js";
3
- import { handler as bet } from "./handler/betting.js";
1
+ import { run, HandlerContext } from "@xmtp/message-kit";
4
2
  import { handler as tipping } from "./handler/tipping.js";
5
3
  import { handler as agent } from "./handler/agent.js";
6
- import { handler as transaction } from "./handler/transaction.js";
7
- import { handler as splitpayment } from "./handler/payment.js";
8
- import { handler as games } from "./handler/game.js";
9
- import { handler as admin } from "./handler/admin.js";
10
- import { handler as loyalty } from "./handler/loyalty.js";
11
-
12
- // Define command handlers
13
- const commandHandlers: CommandHandlers = {
14
- "/tip": tipping,
15
- "/agent": agent,
16
- "/bet": bet,
17
- "/send": transaction,
18
- "/swap": transaction,
19
- "/mint": transaction,
20
- "/show": transaction,
21
- "/points": loyalty,
22
- "/leaderboard": loyalty,
23
- "/game": games,
24
- "/add": admin,
25
- "/remove": admin,
26
- "/name": admin,
27
- "/help": async (context: HandlerContext) => {
28
- const intro =
29
- "Available experiences:\n" +
30
- commands
31
- .flatMap((app) => app.commands)
32
- .map((command) => `${command.command} - ${command.description}`)
33
- .join("\n") +
34
- "\nUse these commands to interact with specific apps.";
35
- context.reply(intro);
36
- },
37
- "/apps": async (context: HandlerContext) => {
38
- const intro =
39
- "Decentralized secure messaging. Built for crypto.\n" +
40
- "Welcome to the Apps Directory\n\n" +
41
- "- 🎰 betbot.eth : Create bets with your friends.\n\n\n" +
42
- "- 💧 faucetbot.eth : Delivers Faucet funds to devs on Testnet\n\n\n" +
43
- "- 🛍️ thegeneralstore.eth : Simple ecommerce storefront for hackathon goods\n\n\n" +
44
- "- 📅 dailywordle.eth : Play daily to the WORDLE game through messaging.\n\n\n" +
45
- "- 🪨 mintframe - 0xB7d51dD8331551D2FA0d185F8Ba08DcCd71499aD : Turn a Zora url into a mint frame.\n\n\n" +
46
- "To learn how to build your own app, visit MessageKit: https://message-kit.vercel.app/\n\n" +
47
- "To publish your app, visit Directory: https://message-kit.vercel.app/directory\n\n" +
48
- "You are currently inside Message Kit Group Starter. You can type:\n/help command to see available commands\n/apps to trigger the directory.";
49
-
50
- context.reply(intro);
51
- },
52
- };
53
-
54
- // App configuration
55
- const appConfig = {
56
- commands: commands,
57
- commandHandlers: commandHandlers,
58
- };
4
+ import { handler as splitpayment } from "./handler/splitpayment.js";
5
+ import { handler as admin } from "./handler/moderation.js";
59
6
 
60
7
  // Main function to run the app
61
8
  run(async (context: HandlerContext) => {
@@ -66,21 +13,18 @@ run(async (context: HandlerContext) => {
66
13
  switch (typeId) {
67
14
  case "reaction":
68
15
  handleReaction(context);
69
- loyalty(context);
70
16
  break;
71
17
  case "reply":
72
18
  handleReply(context);
73
19
  break;
74
20
  case "group_updated":
75
21
  admin(context);
76
- loyalty(context);
77
22
  break;
78
23
  case "remoteStaticAttachment":
79
24
  handleAttachment(context);
80
25
  break;
81
26
  case "text":
82
27
  handleTextMessage(context);
83
- loyalty(context, true);
84
28
  break;
85
29
  default:
86
30
  console.warn(`Unhandled message type: ${typeId}`);
@@ -88,7 +32,7 @@ run(async (context: HandlerContext) => {
88
32
  } catch (error) {
89
33
  console.error("Error handling message:", error);
90
34
  }
91
- }, appConfig);
35
+ });
92
36
 
93
37
  // Handle reaction messages
94
38
  async function handleReaction(context: HandlerContext) {
@@ -121,9 +65,20 @@ async function handleTextMessage(context: HandlerContext) {
121
65
  const {
122
66
  content: { content: text },
123
67
  } = context.message;
124
- if (text.includes("@bot")) {
68
+ if (text.includes("/help")) {
69
+ await helpHandler(context);
70
+ } else if (text.startsWith("@agent")) {
125
71
  await agent(context);
126
- } else if (text.startsWith("/")) {
127
- await context.intent(text);
128
- }
72
+ } else await context.intent(text);
73
+ }
74
+ async function helpHandler(context: HandlerContext) {
75
+ const { commands = [] } = context;
76
+ const intro =
77
+ "Available experiences:\n" +
78
+ commands
79
+ .flatMap((app) => app.commands)
80
+ .map((command) => `${command.command} - ${command.description}`)
81
+ .join("\n") +
82
+ "\nUse these commands to interact with specific apps.";
83
+ context.send(intro);
129
84
  }
@@ -24,7 +24,14 @@ export async function textGeneration(userPrompt: string, systemPrompt: string) {
24
24
  role: "assistant",
25
25
  content: reply || "No response from OpenAI.",
26
26
  });
27
- return { reply: reply as string, history: messages };
27
+ const cleanedReply = reply
28
+ ?.replace(/(\*\*|__)(.*?)\1/g, "$2")
29
+ ?.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$2")
30
+ ?.replace(/^#+\s*(.*)$/gm, "$1")
31
+ ?.replace(/`([^`]+)`/g, "$1")
32
+ ?.replace(/^`|`$/g, "");
33
+
34
+ return { reply: cleanedReply as string, history: messages };
28
35
  } catch (error) {
29
36
  console.error("Failed to fetch from OpenAI:", error);
30
37
  throw error;
@@ -1,3 +1,2 @@
1
1
  KEY= # 0x... the private key of the bot wallet (with the 0x prefix)
2
- REDIS_CONNECTION_STRING= # redis db connection string
3
- MSG_LOG=true # logs the message on the console
2
+ REDIS_CONNECTION_STRING= # redis db connection string
@@ -1,12 +1,7 @@
1
1
  import { getRedisClient } from "./lib/redis.js";
2
2
  import { run, HandlerContext } from "@xmtp/message-kit";
3
3
  import { startCron } from "./lib/cron.js";
4
- import {
5
- RedisClientType,
6
- RedisModules,
7
- RedisFunctions,
8
- RedisScripts,
9
- } from "@redis/client";
4
+ import { RedisClientType } from "@redis/client";
10
5
 
11
6
  //Tracks conversation steps
12
7
  const inMemoryCacheStep = new Map<string, number>();
@@ -14,8 +9,7 @@ const inMemoryCacheStep = new Map<string, number>();
14
9
  //List of words to stop or unsubscribe.
15
10
  const stopWords = ["stop", "unsubscribe", "cancel", "list"];
16
11
 
17
- const redisClient: RedisClientType<RedisModules, RedisFunctions, RedisScripts> =
18
- await getRedisClient();
12
+ const redisClient: RedisClientType = await getRedisClient();
19
13
 
20
14
  let clientInitialized = false;
21
15
  run(async (context: HandlerContext) => {
@@ -46,6 +40,7 @@ run(async (context: HandlerContext) => {
46
40
  await context.reply(
47
41
  "You are now unsubscribed. You will no longer receive updates!.",
48
42
  );
43
+ return;
49
44
  }
50
45
 
51
46
  const cacheStep = inMemoryCacheStep.get(sender.address) || 0;
@@ -1,14 +1,9 @@
1
1
  import cron from "node-cron";
2
2
  import { Client } from "@xmtp/xmtp-js";
3
- import {
4
- RedisClientType,
5
- RedisModules,
6
- RedisFunctions,
7
- RedisScripts,
8
- } from "@redis/client";
3
+ import { RedisClientType } from "@redis/client";
9
4
 
10
5
  export async function startCron(
11
- redisClient: RedisClientType<RedisModules, RedisFunctions, RedisScripts>,
6
+ redisClient: RedisClientType,
12
7
  v2client: Client,
13
8
  ) {
14
9
  console.log("Starting daily cron");
@@ -1,4 +1,5 @@
1
1
  import { createClient } from "@redis/client";
2
+ import { RedisClientType } from "@redis/client";
2
3
 
3
4
  export const getRedisClient = async () => {
4
5
  const client = createClient({
@@ -10,5 +11,5 @@ export const getRedisClient = async () => {
10
11
  });
11
12
 
12
13
  await client.connect();
13
- return client;
14
+ return client as RedisClientType;
14
15
  };
package/index.js CHANGED
@@ -174,6 +174,7 @@ node_modules/
174
174
  .gitignore
175
175
  .cache/
176
176
  dist/
177
+ .DS_Store
177
178
 
178
179
  # Logs
179
180
  logs/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-message-kit",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "index.js",