create-message-kit 1.2.14 → 1.2.16
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/index.js +19 -17
- package/package.json +1 -1
- package/templates/coinbase-agent/.cursorrules +290 -0
- package/templates/coinbase-agent/.env.example +4 -0
- package/templates/coinbase-agent/.yarnrc.yml +9 -0
- package/templates/coinbase-agent/package.json +22 -0
- package/templates/coinbase-agent/src/index.ts +31 -0
- package/templates/coinbase-agent/src/plugins/learnweb3.ts +96 -0
- package/templates/coinbase-agent/src/plugins/redis.ts +15 -0
- package/templates/coinbase-agent/src/prompt.ts +64 -0
- package/templates/coinbase-agent/src/skills/drip.ts +83 -0
- package/templates/coinbase-agent/src/skills/mint.ts +99 -0
- package/templates/coinbase-agent/src/skills/pay.ts +35 -0
- package/templates/coinbase-agent/src/skills/swap.ts +52 -0
- package/templates/ens/.env.example +1 -0
- package/templates/ens/.yarnrc.yml +5 -0
- package/templates/ens/src/index.ts +1 -1
- package/templates/ens/src/prompt.ts +1 -14
- package/templates/faucet/.cursorrules +290 -0
- package/templates/faucet/.env.example +5 -0
- package/templates/faucet/.yarnrc.yml +9 -0
- package/templates/faucet/package.json +22 -0
- package/templates/faucet/src/index.ts +39 -0
- package/templates/faucet/src/plugins/learnweb3.ts +96 -0
- package/templates/faucet/src/plugins/redis.ts +15 -0
- package/templates/faucet/src/prompt.ts +11 -0
- package/templates/faucet/src/skills/faucet.ts +140 -0
- package/templates/gated-group/.cursorrules +290 -0
- package/templates/gated-group/.env.example +3 -0
- package/templates/gated-group/.yarnrc.yml +9 -0
- package/templates/gated-group/package.json +23 -0
- package/templates/gated-group/src/index.ts +17 -0
- package/templates/gated-group/src/plugins/alchemy.ts +27 -0
- package/templates/gated-group/src/plugins/xmtp.ts +137 -0
- package/templates/gated-group/src/prompt.ts +11 -0
- package/templates/gated-group/src/skills/gated.ts +88 -0
- package/templates/gm/.cursorrules +290 -0
- package/templates/gm/.env.example +2 -0
- package/templates/gm/.yarnrc.yml +9 -0
- package/templates/gm/package.json +20 -0
- package/templates/gm/src/index.ts +8 -0
- package/templates/playground/.cursorrules +290 -0
- package/templates/playground/.env.example +6 -0
- package/templates/playground/.yarnrc.yml +9 -0
- package/templates/playground/package.json +26 -0
- package/templates/playground/src/index.ts +40 -0
- package/templates/playground/src/plugins/alchemy.ts +27 -0
- package/templates/playground/src/plugins/minilog.ts +26 -0
- package/templates/playground/src/plugins/usdc.ts +99 -0
- package/templates/playground/src/plugins/xmtp.ts +137 -0
- package/templates/playground/src/prompt.ts +11 -0
- package/templates/playground/src/skills/broadcast.ts +39 -0
- package/templates/playground/src/skills/cash.ts +122 -0
- package/templates/playground/src/skills/cryptoPrice.ts +63 -0
- package/templates/playground/src/skills/dalle.ts +61 -0
- package/templates/playground/src/skills/gated.ts +88 -0
- package/templates/playground/src/skills/search.ts +159 -0
- package/templates/playground/src/skills/todo.ts +79 -0
- package/templates/playground/src/skills/token.ts +57 -0
- package/templates/playground/src/skills/web.ts +83 -0
- package/templates/playground/src/skills/wordle.ts +96 -0
- package/templates/simple/.env.example +1 -0
- package/templates/simple/.yarnrc.yml +5 -0
- package/templates/simple/src/index.ts +1 -1
- package/templates/simple/src/prompt.ts +2 -0
- package/templates/thegeneralstore/.cursorrules +290 -0
- package/templates/thegeneralstore/.env.example +9 -0
- package/templates/thegeneralstore/.yarnrc.yml +9 -0
- package/templates/thegeneralstore/package.json +24 -0
- package/templates/thegeneralstore/src/data/db.json +812 -0
- package/templates/thegeneralstore/src/index.ts +37 -0
- package/templates/thegeneralstore/src/plugins/learnweb3.ts +96 -0
- package/templates/thegeneralstore/src/plugins/lowdb.ts +11 -0
- package/templates/thegeneralstore/src/plugins/notion.ts +89 -0
- package/templates/thegeneralstore/src/plugins/redis.ts +15 -0
- package/templates/thegeneralstore/src/prompt.md +51 -0
- package/templates/thegeneralstore/src/prompt.ts +3 -0
- package/templates/thegeneralstore/src/skills/faucet.ts +114 -0
- package/templates/thegeneralstore/src/skills/notion.ts +17 -0
- package/templates/thegeneralstore/src/skills/poap.ts +48 -0
- package/templates/thegeneralstore/src/skills.ts +37 -0
- package/templates/toss/.cursorrules +290 -0
- package/templates/toss/.env.example +7 -0
- package/templates/toss/.yarnrc.yml +9 -0
- package/templates/toss/package.json +21 -0
- package/templates/toss/src/index.ts +33 -0
- package/templates/toss/src/plugins/helpers.ts +185 -0
- package/templates/toss/src/plugins/redis.ts +78 -0
- package/templates/toss/src/prompt.ts +54 -0
- package/templates/toss/src/skills/toss.ts +489 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
import { XMTPContext, Skill } from "@xmtp/message-kit";
|
2
|
+
|
3
|
+
export const broadcast: Skill[] = [
|
4
|
+
{
|
5
|
+
skill: "send",
|
6
|
+
adminOnly: true,
|
7
|
+
handler: handler,
|
8
|
+
examples: ["/send Hello everyone, the event is starting now!"],
|
9
|
+
description: "Send updates to all subscribers.",
|
10
|
+
params: {
|
11
|
+
message: {
|
12
|
+
type: "prompt",
|
13
|
+
},
|
14
|
+
},
|
15
|
+
},
|
16
|
+
];
|
17
|
+
|
18
|
+
async function handler(context: XMTPContext) {
|
19
|
+
const {
|
20
|
+
message: {
|
21
|
+
content: {
|
22
|
+
params: { message },
|
23
|
+
},
|
24
|
+
},
|
25
|
+
} = context;
|
26
|
+
|
27
|
+
const fakeSubscribers = ["0x93E2fc3e99dFb1238eB9e0eF2580EFC5809C7204"];
|
28
|
+
await context.send("This is how your message will look like:");
|
29
|
+
await context.send(message);
|
30
|
+
const emailResponse = await context.awaitResponse(
|
31
|
+
"Are you sure you want to send this broadcast?\nType 'yes' to confirm.",
|
32
|
+
["yes", "no"],
|
33
|
+
);
|
34
|
+
if (emailResponse === "yes") {
|
35
|
+
await context.send("Sending broadcast...");
|
36
|
+
await context.sendTo(message, fakeSubscribers);
|
37
|
+
await context.send("Broadcast sent!");
|
38
|
+
}
|
39
|
+
}
|
@@ -0,0 +1,122 @@
|
|
1
|
+
import { XMTPContext } from "@xmtp/message-kit";
|
2
|
+
import type { Skill } from "@xmtp/message-kit";
|
3
|
+
import { USDCWallet } from "../plugins/usdc.js";
|
4
|
+
|
5
|
+
export const cash: Skill[] = [
|
6
|
+
{
|
7
|
+
skill: "balance",
|
8
|
+
handler: balanceHandler,
|
9
|
+
examples: ["/balance"],
|
10
|
+
description: "Check your balance.",
|
11
|
+
},
|
12
|
+
{
|
13
|
+
skill: "fund",
|
14
|
+
handler: fundHandler,
|
15
|
+
examples: ["/fund 1", "/fund 10"],
|
16
|
+
params: {
|
17
|
+
amount: {
|
18
|
+
type: "number",
|
19
|
+
default: "",
|
20
|
+
},
|
21
|
+
},
|
22
|
+
description: "Fund your wallet. Returns a url to fund your wallet.",
|
23
|
+
},
|
24
|
+
{
|
25
|
+
skill: "transfer",
|
26
|
+
handler: transferHandler,
|
27
|
+
examples: ["/transfer 0x40f08f0f853d1c42c61815652b7ccd5a50f0be09 1"],
|
28
|
+
params: {
|
29
|
+
address: {
|
30
|
+
type: "address",
|
31
|
+
default: "",
|
32
|
+
},
|
33
|
+
amount: {
|
34
|
+
type: "number",
|
35
|
+
default: "",
|
36
|
+
},
|
37
|
+
},
|
38
|
+
description: "Transfer USDC to another address.",
|
39
|
+
},
|
40
|
+
];
|
41
|
+
|
42
|
+
async function balanceHandler(context: XMTPContext) {
|
43
|
+
const {
|
44
|
+
message: { sender },
|
45
|
+
} = context;
|
46
|
+
const usdcWallet = new USDCWallet(sender.address);
|
47
|
+
const { usdc } = await usdcWallet.checkBalances();
|
48
|
+
await context.send(
|
49
|
+
`Your balance is ${usdc} USDC. let me know if you want check again or to fund your wallet.`,
|
50
|
+
);
|
51
|
+
}
|
52
|
+
|
53
|
+
async function fundHandler(context: XMTPContext) {
|
54
|
+
try {
|
55
|
+
const {
|
56
|
+
message: {
|
57
|
+
sender,
|
58
|
+
content: {
|
59
|
+
params: { amount },
|
60
|
+
},
|
61
|
+
},
|
62
|
+
} = context;
|
63
|
+
const usdcWallet = new USDCWallet(sender.address);
|
64
|
+
const { usdc } = await usdcWallet.checkBalances();
|
65
|
+
const MAX_USDC = 10;
|
66
|
+
|
67
|
+
if (usdc >= MAX_USDC) {
|
68
|
+
await context.send(`Your balance is maxed out at ${MAX_USDC} USDC.`);
|
69
|
+
return;
|
70
|
+
}
|
71
|
+
|
72
|
+
const remainingLimit = MAX_USDC - usdc;
|
73
|
+
let fundAmount: number;
|
74
|
+
|
75
|
+
if (!amount) {
|
76
|
+
const options = Array.from(
|
77
|
+
{ length: Math.floor(remainingLimit) },
|
78
|
+
(_, i) => (i + 1).toString(),
|
79
|
+
);
|
80
|
+
const response = await context.awaitResponse(
|
81
|
+
`Please specify the amount of USDC to prefund (1 to ${remainingLimit}):`,
|
82
|
+
options,
|
83
|
+
);
|
84
|
+
fundAmount = parseInt(response);
|
85
|
+
} else {
|
86
|
+
fundAmount = parseInt(amount);
|
87
|
+
}
|
88
|
+
|
89
|
+
if (isNaN(fundAmount) || fundAmount <= 0 || fundAmount > remainingLimit) {
|
90
|
+
await context.send(
|
91
|
+
`Invalid amount. Please specify a value between 1 and ${remainingLimit} USDC.`,
|
92
|
+
);
|
93
|
+
return;
|
94
|
+
}
|
95
|
+
|
96
|
+
await context.requestPayment(fundAmount, "USDC", usdcWallet.agentAddress);
|
97
|
+
await context.send(
|
98
|
+
"After funding, let me know so I can check your balance.",
|
99
|
+
);
|
100
|
+
} catch (error) {
|
101
|
+
await context.send(
|
102
|
+
"An error occurred while processing your request. Please try again.",
|
103
|
+
);
|
104
|
+
}
|
105
|
+
}
|
106
|
+
|
107
|
+
async function transferHandler(context: XMTPContext) {
|
108
|
+
const {
|
109
|
+
message: {
|
110
|
+
sender,
|
111
|
+
content: {
|
112
|
+
params: { address, amount },
|
113
|
+
},
|
114
|
+
},
|
115
|
+
} = context;
|
116
|
+
const usdcWallet = new USDCWallet(sender.address);
|
117
|
+
if (amount > 10) {
|
118
|
+
await context.send("You can only transfer up to 10 USDC at a time.");
|
119
|
+
return;
|
120
|
+
}
|
121
|
+
await usdcWallet.transferUsdc(address, amount);
|
122
|
+
}
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import { XMTPContext } from "@xmtp/message-kit";
|
2
|
+
import type { Skill } from "@xmtp/message-kit";
|
3
|
+
|
4
|
+
// Example skill structure
|
5
|
+
export const cryptoPrice: Skill[] = [
|
6
|
+
{
|
7
|
+
skill: "price",
|
8
|
+
handler: handler,
|
9
|
+
examples: ["/price BTC USD", "/price ETH EUR"],
|
10
|
+
description: "Get the current exchange rate for a cryptocurrency.",
|
11
|
+
params: {
|
12
|
+
crypto: {
|
13
|
+
type: "string",
|
14
|
+
},
|
15
|
+
currency: {
|
16
|
+
type: "string",
|
17
|
+
default: "USD",
|
18
|
+
},
|
19
|
+
},
|
20
|
+
},
|
21
|
+
];
|
22
|
+
|
23
|
+
async function handler(context: XMTPContext) {
|
24
|
+
const {
|
25
|
+
message: {
|
26
|
+
content: {
|
27
|
+
params: { crypto, currency },
|
28
|
+
},
|
29
|
+
},
|
30
|
+
} = context;
|
31
|
+
|
32
|
+
try {
|
33
|
+
const response = await fetch(
|
34
|
+
`https://min-api.cryptocompare.com/data/price?fsym=${crypto}&tsyms=${currency}`
|
35
|
+
);
|
36
|
+
|
37
|
+
if (!response.ok) {
|
38
|
+
return {
|
39
|
+
code: response.status,
|
40
|
+
message: `Unable to fetch exchange rate: ${response.statusText}`,
|
41
|
+
};
|
42
|
+
}
|
43
|
+
|
44
|
+
const data = await response.json() as Record<string, number>;
|
45
|
+
|
46
|
+
if (!data[currency]) {
|
47
|
+
return {
|
48
|
+
code: 400,
|
49
|
+
message: `Could not find exchange rate for ${crypto}/${currency}`,
|
50
|
+
};
|
51
|
+
}
|
52
|
+
|
53
|
+
return {
|
54
|
+
code: 200,
|
55
|
+
message: `1 ${crypto} = ${data[currency]} ${currency}`,
|
56
|
+
};
|
57
|
+
} catch (error: any) {
|
58
|
+
return {
|
59
|
+
code: 500,
|
60
|
+
message: `Error fetching exchange rate: ${error.message}`,
|
61
|
+
};
|
62
|
+
}
|
63
|
+
}
|
@@ -0,0 +1,61 @@
|
|
1
|
+
import { XMTPContext } from "@xmtp/message-kit";
|
2
|
+
|
3
|
+
import type { Skill } from "@xmtp/message-kit";
|
4
|
+
import OpenAI from "openai";
|
5
|
+
|
6
|
+
const openai = new OpenAI({
|
7
|
+
apiKey: process.env.OPENAI_API_KEY,
|
8
|
+
});
|
9
|
+
|
10
|
+
export const dalle: Skill[] = [
|
11
|
+
{
|
12
|
+
skill: "image",
|
13
|
+
handler: handler,
|
14
|
+
description: "Generate an image based on a prompt.",
|
15
|
+
examples: ["/image dog with a ball"],
|
16
|
+
params: {
|
17
|
+
prompt: {
|
18
|
+
type: "prompt",
|
19
|
+
},
|
20
|
+
},
|
21
|
+
},
|
22
|
+
];
|
23
|
+
|
24
|
+
export async function handler(context: XMTPContext) {
|
25
|
+
const {
|
26
|
+
message: {
|
27
|
+
sender,
|
28
|
+
content: {
|
29
|
+
params: { prompt },
|
30
|
+
},
|
31
|
+
},
|
32
|
+
} = context;
|
33
|
+
|
34
|
+
if (!prompt) {
|
35
|
+
return {
|
36
|
+
code: 400,
|
37
|
+
message: "Missing required parameters. Please provide a prompt.",
|
38
|
+
};
|
39
|
+
}
|
40
|
+
|
41
|
+
try {
|
42
|
+
const response = await openai.images.generate({
|
43
|
+
prompt: prompt,
|
44
|
+
n: 1,
|
45
|
+
size: "1024x1024",
|
46
|
+
});
|
47
|
+
|
48
|
+
const imageUrl = response.data[0].url;
|
49
|
+
console.log(imageUrl);
|
50
|
+
const message = `Here is the image generated for the prompt "${prompt}": ${imageUrl}`;
|
51
|
+
context.send(message);
|
52
|
+
} catch (error) {
|
53
|
+
// @ts-ignore
|
54
|
+
const message = `Failed to generate image. Error: ${error?.message}
|
55
|
+
}`;
|
56
|
+
return {
|
57
|
+
code: 500,
|
58
|
+
message,
|
59
|
+
};
|
60
|
+
}
|
61
|
+
}
|
@@ -0,0 +1,88 @@
|
|
1
|
+
import { XMTPContext, Skill, V3Client } from "@xmtp/message-kit";
|
2
|
+
import { createGroup } from "../plugins/xmtp.js";
|
3
|
+
import express from "express";
|
4
|
+
import { checkNft } from "../plugins/alchemy.js";
|
5
|
+
import { addToGroup } from "../plugins/xmtp.js";
|
6
|
+
export const gated: Skill[] = [
|
7
|
+
{
|
8
|
+
skill: "create",
|
9
|
+
examples: ["/create"],
|
10
|
+
handler: handler,
|
11
|
+
adminOnly: true,
|
12
|
+
description: "Create a new group.",
|
13
|
+
},
|
14
|
+
];
|
15
|
+
|
16
|
+
async function handler(context: XMTPContext) {
|
17
|
+
const {
|
18
|
+
message: {
|
19
|
+
sender,
|
20
|
+
content: { skill },
|
21
|
+
},
|
22
|
+
client,
|
23
|
+
} = context;
|
24
|
+
|
25
|
+
if (skill === "create") {
|
26
|
+
const group = await createGroup(
|
27
|
+
client,
|
28
|
+
sender.address,
|
29
|
+
client.accountAddress,
|
30
|
+
);
|
31
|
+
|
32
|
+
await context.send(
|
33
|
+
`Group created!\n- ID: ${group?.id}\n- Group Frame URL: https://converse.xyz/group/${group?.id}: \n- This url will deelink to the group inside Converse\n- Once in the other group you can share the invite with your friends.`,
|
34
|
+
);
|
35
|
+
return;
|
36
|
+
} else {
|
37
|
+
await context.send(
|
38
|
+
"👋 Welcome to the Gated Bot Group!\nTo get started, type /create to set up a new group. 🚀\nThis example will check if the user has a particular nft and add them to the group if they do.\nOnce your group is created, you'll receive a unique Group ID and URL.\nShare the URL with friends to invite them to join your group!",
|
39
|
+
);
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
export function startGatedGroupServer(client: V3Client) {
|
44
|
+
async function addWalletToGroup(
|
45
|
+
walletAddress: string,
|
46
|
+
groupId: string,
|
47
|
+
): Promise<string> {
|
48
|
+
const verified = await checkNft(walletAddress, "XMTPeople");
|
49
|
+
if (!verified) {
|
50
|
+
console.log("User cant be added to the group");
|
51
|
+
return "not verified";
|
52
|
+
} else {
|
53
|
+
try {
|
54
|
+
const added = await addToGroup(groupId, client, walletAddress);
|
55
|
+
if (added.code === 200) {
|
56
|
+
console.log(`Added wallet address: ${walletAddress} to the group`);
|
57
|
+
return "success";
|
58
|
+
} else {
|
59
|
+
return added.message;
|
60
|
+
}
|
61
|
+
} catch (error: any) {
|
62
|
+
console.log(error.message);
|
63
|
+
return "error";
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
// Endpoint to add wallet address to a group from an external source
|
69
|
+
const app = express();
|
70
|
+
app.use(express.json());
|
71
|
+
app.post("/add-wallet", async (req, res) => {
|
72
|
+
try {
|
73
|
+
const { walletAddress, groupId } = req.body;
|
74
|
+
const result = await addWalletToGroup(walletAddress, groupId);
|
75
|
+
res.status(200).send(result);
|
76
|
+
} catch (error: any) {
|
77
|
+
res.status(400).send(error.message);
|
78
|
+
}
|
79
|
+
});
|
80
|
+
// Start the servfalcheer
|
81
|
+
const PORT = process.env.PORT || 3000;
|
82
|
+
const url = process.env.URL || `http://localhost:${PORT}`;
|
83
|
+
app.listen(PORT, () => {
|
84
|
+
console.warn(
|
85
|
+
`Use this endpoint to add a wallet to a group indicated by the groupId\n${url}/add-wallet <body: {walletAddress, groupId}>`,
|
86
|
+
);
|
87
|
+
});
|
88
|
+
}
|
@@ -0,0 +1,159 @@
|
|
1
|
+
import { XMTPContext } from "@xmtp/message-kit";
|
2
|
+
import type { Skill } from "@xmtp/message-kit";
|
3
|
+
import OpenAI from "openai";
|
4
|
+
|
5
|
+
const openai = new OpenAI({
|
6
|
+
apiKey: process.env.OPENAI_API_KEY,
|
7
|
+
});
|
8
|
+
|
9
|
+
export const search: Skill[] = [
|
10
|
+
{
|
11
|
+
skill: "search",
|
12
|
+
examples: [
|
13
|
+
"/search what is the capital of France?",
|
14
|
+
"/search latest news about ethereum",
|
15
|
+
],
|
16
|
+
handler: handler,
|
17
|
+
description:
|
18
|
+
"Search the internet and get summarized information from top results.",
|
19
|
+
params: {
|
20
|
+
query: {
|
21
|
+
type: "prompt",
|
22
|
+
},
|
23
|
+
},
|
24
|
+
},
|
25
|
+
];
|
26
|
+
|
27
|
+
export async function handler(context: XMTPContext) {
|
28
|
+
const {
|
29
|
+
message: {
|
30
|
+
content: {
|
31
|
+
params: { query },
|
32
|
+
},
|
33
|
+
},
|
34
|
+
} = context;
|
35
|
+
|
36
|
+
try {
|
37
|
+
// Search and get top results
|
38
|
+
const searchResults = await searchDuckDuckGo(query);
|
39
|
+
if (!searchResults.length) {
|
40
|
+
return {
|
41
|
+
code: 404,
|
42
|
+
message: "No results found for your query.",
|
43
|
+
};
|
44
|
+
}
|
45
|
+
|
46
|
+
// Fetch content from each result
|
47
|
+
const contents = await Promise.all(
|
48
|
+
searchResults.slice(0, 3).map(async (url) => {
|
49
|
+
try {
|
50
|
+
const response = await fetch(url, {
|
51
|
+
headers: {
|
52
|
+
"User-Agent":
|
53
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
54
|
+
},
|
55
|
+
});
|
56
|
+
const html = await response.text();
|
57
|
+
return extractContent(html);
|
58
|
+
} catch (error) {
|
59
|
+
console.error(`Error fetching ${url}:`, error);
|
60
|
+
return "";
|
61
|
+
}
|
62
|
+
}),
|
63
|
+
);
|
64
|
+
|
65
|
+
// Combine and summarize the content
|
66
|
+
const summary = await getAISummary(contents.join("\n"), query);
|
67
|
+
|
68
|
+
return {
|
69
|
+
code: 200,
|
70
|
+
message: summary,
|
71
|
+
};
|
72
|
+
} catch (error: any) {
|
73
|
+
return {
|
74
|
+
code: 500,
|
75
|
+
message: `Search failed: ${error.message}`,
|
76
|
+
};
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
async function searchDuckDuckGo(query: string): Promise<string[]> {
|
81
|
+
try {
|
82
|
+
// DuckDuckGo's lite version is more friendly to programmatic access
|
83
|
+
const response = await fetch(
|
84
|
+
`https://duckduckgo.com/lite?q=${encodeURIComponent(query)}`,
|
85
|
+
{
|
86
|
+
headers: {
|
87
|
+
"User-Agent":
|
88
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
89
|
+
},
|
90
|
+
},
|
91
|
+
);
|
92
|
+
|
93
|
+
const html = await response.text();
|
94
|
+
|
95
|
+
// Extract URLs from the results
|
96
|
+
const urlRegex = /<a class="result-link" href="([^"]+)"/g;
|
97
|
+
const urls: string[] = [];
|
98
|
+
let match;
|
99
|
+
|
100
|
+
while ((match = urlRegex.exec(html)) !== null) {
|
101
|
+
if (match[1] && !match[1].includes("duckduckgo.com")) {
|
102
|
+
urls.push(match[1]);
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
// If no results from regex, try alternative pattern
|
107
|
+
if (urls.length === 0) {
|
108
|
+
const altRegex = /rel="nofollow" href="([^"]+)"/g;
|
109
|
+
while ((match = altRegex.exec(html)) !== null) {
|
110
|
+
if (match[1] && !match[1].includes("duckduckgo.com")) {
|
111
|
+
urls.push(match[1]);
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
return urls;
|
117
|
+
} catch (error) {
|
118
|
+
console.error("Search failed:", error);
|
119
|
+
return [];
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
function extractContent(html: string): string {
|
124
|
+
// Remove scripts and style elements
|
125
|
+
html = html
|
126
|
+
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
|
127
|
+
.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, "");
|
128
|
+
|
129
|
+
// Get text content
|
130
|
+
const text = html
|
131
|
+
.replace(/<[^>]+>/g, " ")
|
132
|
+
.replace(/\s+/g, " ")
|
133
|
+
.trim()
|
134
|
+
.slice(0, 2000); // Get more content for search results
|
135
|
+
|
136
|
+
return text;
|
137
|
+
}
|
138
|
+
|
139
|
+
async function getAISummary(content: string, query: string): Promise<string> {
|
140
|
+
const completion = await openai.chat.completions.create({
|
141
|
+
messages: [
|
142
|
+
{
|
143
|
+
role: "system",
|
144
|
+
content:
|
145
|
+
"You are a helpful assistant that provides clear, accurate summaries (2-3 sentences) of search results.",
|
146
|
+
},
|
147
|
+
{
|
148
|
+
role: "user",
|
149
|
+
content: `Based on the following search results, please provide a comprehensive answer to the query: "${query}"\n\nSearch results:\n${content}`,
|
150
|
+
},
|
151
|
+
],
|
152
|
+
model: "gpt-4o-mini",
|
153
|
+
});
|
154
|
+
|
155
|
+
return (
|
156
|
+
completion.choices[0].message.content ||
|
157
|
+
"Could not generate a summary of the search results."
|
158
|
+
);
|
159
|
+
}
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import { Resend } from "resend";
|
2
|
+
import { XMTPContext } from "@xmtp/message-kit";
|
3
|
+
import type { Skill } from "@xmtp/message-kit";
|
4
|
+
|
5
|
+
const resend = new Resend(process.env.RESEND_API_KEY); // Replace with your Resend API key
|
6
|
+
|
7
|
+
export const todo: Skill[] = [
|
8
|
+
{
|
9
|
+
skill: "todo",
|
10
|
+
handler: handler,
|
11
|
+
examples: ["/todo"],
|
12
|
+
description:
|
13
|
+
"Summarize your TODOs and send an email with the summary. Receives no parameters.",
|
14
|
+
},
|
15
|
+
];
|
16
|
+
|
17
|
+
export async function handler(context: XMTPContext) {
|
18
|
+
const {
|
19
|
+
message: {
|
20
|
+
content: { previousMsg },
|
21
|
+
},
|
22
|
+
} = context;
|
23
|
+
|
24
|
+
let email = "";
|
25
|
+
if (!previousMsg) {
|
26
|
+
await context.send("You need to do it on a reply.");
|
27
|
+
return;
|
28
|
+
}
|
29
|
+
let intents = 2;
|
30
|
+
while (intents > 0) {
|
31
|
+
const emailResponse = await context.awaitResponse(
|
32
|
+
"Please provide your email address to receive the to-dos summary:",
|
33
|
+
);
|
34
|
+
email = emailResponse;
|
35
|
+
|
36
|
+
// Basic email validation
|
37
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
38
|
+
if (!emailRegex.test(email)) {
|
39
|
+
await context.send(
|
40
|
+
"Invalid email format. Please try again with a valid email address.",
|
41
|
+
);
|
42
|
+
intents--;
|
43
|
+
continue;
|
44
|
+
}
|
45
|
+
break;
|
46
|
+
}
|
47
|
+
if (intents == 0) {
|
48
|
+
await context.send(
|
49
|
+
"I couldn't get your email address. Please try again later.",
|
50
|
+
);
|
51
|
+
return;
|
52
|
+
}
|
53
|
+
try {
|
54
|
+
let { reply } = await context.textGeneration(
|
55
|
+
email,
|
56
|
+
"Make this summary concise and to the point to be sent in an email.\n msg: " +
|
57
|
+
previousMsg,
|
58
|
+
"You are an expert at summarizing to-dos. Return in format html and just the content inside the body tag. Dont return `html` or `body` tags",
|
59
|
+
);
|
60
|
+
if (typeof reply === "string") {
|
61
|
+
let content = {
|
62
|
+
from: "bot@mail.coin-toss.xyz",
|
63
|
+
to: email,
|
64
|
+
subject: "Your summary from Converse",
|
65
|
+
html: `
|
66
|
+
<h3>Your Converse Summary</h3>
|
67
|
+
<p>${reply}</p>
|
68
|
+
`,
|
69
|
+
};
|
70
|
+
await resend.emails.send(content);
|
71
|
+
await context.send(`✅ Summary sent successfully to ${email}`);
|
72
|
+
} else {
|
73
|
+
await context.send("❌ Message not found.");
|
74
|
+
}
|
75
|
+
} catch (error) {
|
76
|
+
await context.send("❌ Failed to send email. Please try again later.");
|
77
|
+
console.error("Error sending email:", error);
|
78
|
+
}
|
79
|
+
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import { XMTPContext } from "@xmtp/message-kit";
|
2
|
+
import type { Skill } from "@xmtp/message-kit";
|
3
|
+
|
4
|
+
export const token: Skill[] = [
|
5
|
+
{
|
6
|
+
skill: "token",
|
7
|
+
handler: handler,
|
8
|
+
examples: ["/token bitcoin", "/token ethereum"],
|
9
|
+
description: "Get real time price of a any token.",
|
10
|
+
params: {
|
11
|
+
symbol: {
|
12
|
+
type: "string",
|
13
|
+
},
|
14
|
+
},
|
15
|
+
},
|
16
|
+
];
|
17
|
+
export async function handler(context: XMTPContext) {
|
18
|
+
const {
|
19
|
+
message: {
|
20
|
+
content: {
|
21
|
+
params: { symbol },
|
22
|
+
},
|
23
|
+
},
|
24
|
+
} = context;
|
25
|
+
const response = await fetch(
|
26
|
+
`https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=${symbol}`,
|
27
|
+
);
|
28
|
+
if (!response.ok) {
|
29
|
+
context.send("Token not found");
|
30
|
+
context.send("try with its full name, instead of btc it would be bitcoin");
|
31
|
+
return;
|
32
|
+
}
|
33
|
+
const data = (await response.json()) as any;
|
34
|
+
const token = data[0];
|
35
|
+
|
36
|
+
const tokenInfo = {
|
37
|
+
name: token.name,
|
38
|
+
symbol: token.symbol.toUpperCase(),
|
39
|
+
price: token.current_price,
|
40
|
+
image: token.image,
|
41
|
+
link: `https://www.coingecko.com/en/coins/${token.id}`,
|
42
|
+
};
|
43
|
+
|
44
|
+
let frame = {
|
45
|
+
title: tokenInfo.name,
|
46
|
+
buttons: [
|
47
|
+
{ content: "Buy", action: "link", target: tokenInfo.link },
|
48
|
+
{
|
49
|
+
content: `Price (${tokenInfo.price})`,
|
50
|
+
action: "link",
|
51
|
+
target: tokenInfo.link,
|
52
|
+
},
|
53
|
+
],
|
54
|
+
image: tokenInfo.image,
|
55
|
+
};
|
56
|
+
await context.sendCustomFrame(frame);
|
57
|
+
}
|