create-message-kit 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/examples/gm/.env.example +1 -2
- package/examples/gm/src/index.ts +2 -2
- package/examples/group/.env.example +1 -2
- package/examples/group/src/commands.ts +44 -61
- package/examples/group/src/handler/agent.ts +23 -11
- package/examples/group/src/handler/game.ts +16 -12
- package/examples/group/src/handler/loyalty.ts +13 -34
- package/examples/group/src/handler/moderation.ts +110 -0
- package/examples/group/src/handler/splitpayment.ts +62 -0
- package/examples/group/src/handler/tipping.ts +0 -3
- package/examples/group/src/handler/transaction.ts +0 -17
- package/examples/group/src/index.ts +19 -64
- package/examples/group/src/lib/openai.ts +8 -1
- package/examples/one-to-one/.env.example +1 -2
- package/examples/one-to-one/src/index.ts +3 -8
- package/examples/one-to-one/src/lib/cron.ts +2 -7
- package/examples/one-to-one/src/lib/redis.ts +2 -1
- package/index.js +1 -0
- package/package.json +1 -1
package/examples/gm/.env.example
CHANGED
@@ -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)
|
package/examples/gm/src/index.ts
CHANGED
@@ -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.
|
8
|
+
await context.send(`gm`);
|
9
9
|
});
|
@@ -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: "
|
27
|
-
|
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
|
-
|
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", "
|
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
|
-
|
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
|
-
|
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: "
|
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
|
-
|
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: "/
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
-
|
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 = `
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
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://
|
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.
|
30
|
+
context.send(gameUrl);
|
28
31
|
break;
|
32
|
+
|
29
33
|
case "help":
|
30
|
-
context.
|
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.
|
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
|
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: {
|
10
|
+
message: {
|
11
|
+
content,
|
12
|
+
content: { command },
|
13
|
+
sender,
|
14
|
+
typeId,
|
15
|
+
},
|
11
16
|
} = context;
|
12
|
-
|
13
|
-
if (
|
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
|
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
|
7
|
-
import { handler as
|
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
|
-
}
|
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("
|
68
|
+
if (text.includes("/help")) {
|
69
|
+
await helpHandler(context);
|
70
|
+
} else if (text.startsWith("@agent")) {
|
125
71
|
await agent(context);
|
126
|
-
} else
|
127
|
-
|
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
|
-
|
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,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
|
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
|
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