create-message-kit 1.0.15 → 1.0.17
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 +12 -3
- package/package.json +2 -2
- package/templates/agent/.env.example +2 -0
- package/{examples/one-to-one → templates/agent}/package.json +2 -4
- package/templates/agent/src/handler/ens.ts +211 -0
- package/templates/agent/src/index.ts +14 -0
- package/{examples/group → templates/agent}/src/lib/openai.ts +42 -4
- package/templates/agent/src/lib/resolver.ts +126 -0
- package/templates/agent/src/prompt.ts +81 -0
- package/templates/agent/src/skills.ts +93 -0
- package/templates/gm/.env.example +1 -0
- package/{examples → templates}/gm/package.json +1 -0
- package/templates/group/.env.example +3 -0
- package/{examples → templates}/group/package.json +1 -0
- package/{examples → templates}/group/src/handler/agent.ts +18 -10
- package/{examples → templates}/group/src/handler/game.ts +2 -2
- package/{examples → templates}/group/src/handler/loyalty.ts +3 -11
- package/{examples → templates}/group/src/handler/splitpayment.ts +18 -11
- package/{examples → templates}/group/src/handler/tipping.ts +10 -6
- package/{examples → templates}/group/src/handler/transaction.ts +11 -40
- package/templates/group/src/index.ts +57 -0
- package/templates/group/src/lib/openai.ts +137 -0
- package/templates/group/src/lib/resolver.ts +126 -0
- package/{examples/group/src/commands.ts → templates/group/src/skills.ts} +24 -26
- package/examples/gm/.env.example +0 -1
- package/examples/group/.env.example +0 -3
- package/examples/group/src/index.ts +0 -68
- package/examples/one-to-one/.env.example +0 -2
- package/examples/one-to-one/src/index.ts +0 -72
- package/examples/one-to-one/src/lib/cron.ts +0 -34
- package/examples/one-to-one/src/lib/redis.ts +0 -15
- /package/{examples → templates}/gm/src/index.ts +0 -0
- /package/{examples → templates}/group/src/lib/stack.ts +0 -0
@@ -1,14 +1,15 @@
|
|
1
|
-
import { HandlerContext,
|
1
|
+
import { HandlerContext, AbstractedMember } from "@xmtp/message-kit";
|
2
2
|
import { textGeneration } from "../lib/openai.js";
|
3
3
|
|
4
4
|
export async function handler(context: HandlerContext) {
|
5
5
|
if (!process?.env?.OPEN_AI_API_KEY) {
|
6
|
-
console.
|
6
|
+
console.warn("No OPEN_AI_API_KEY found in .env");
|
7
7
|
return;
|
8
8
|
}
|
9
9
|
|
10
10
|
const {
|
11
11
|
message: {
|
12
|
+
sender,
|
12
13
|
content: { content, params },
|
13
14
|
},
|
14
15
|
} = context;
|
@@ -16,11 +17,13 @@ export async function handler(context: HandlerContext) {
|
|
16
17
|
const systemPrompt = generateSystemPrompt(context);
|
17
18
|
try {
|
18
19
|
let userPrompt = params?.prompt ?? content;
|
19
|
-
console.log("userPrompt", userPrompt);
|
20
20
|
|
21
|
-
const { reply } = await textGeneration(
|
22
|
-
|
23
|
-
|
21
|
+
const { reply } = await textGeneration(
|
22
|
+
sender.address,
|
23
|
+
userPrompt,
|
24
|
+
systemPrompt,
|
25
|
+
);
|
26
|
+
context.skill(reply);
|
24
27
|
} catch (error) {
|
25
28
|
console.error("Error during OpenAI call:", error);
|
26
29
|
await context.reply("An error occurred while processing your request.");
|
@@ -30,7 +33,7 @@ export async function handler(context: HandlerContext) {
|
|
30
33
|
function generateSystemPrompt(context: HandlerContext) {
|
31
34
|
const {
|
32
35
|
members,
|
33
|
-
|
36
|
+
skills,
|
34
37
|
message: { sender },
|
35
38
|
} = context;
|
36
39
|
|
@@ -39,10 +42,15 @@ function generateSystemPrompt(context: HandlerContext) {
|
|
39
42
|
|
40
43
|
You are a helpful bot agent that lives inside a web3 messaging group that helps interpret user requests and execute commands.
|
41
44
|
#### Users
|
42
|
-
${JSON.stringify(
|
45
|
+
${JSON.stringify(
|
46
|
+
members?.map((member: AbstractedMember) => ({
|
47
|
+
...member,
|
48
|
+
username: `@${member.accountAddresses[0]}`,
|
49
|
+
})),
|
50
|
+
)}\n
|
43
51
|
#### Commands
|
44
|
-
${JSON.stringify(
|
45
|
-
The message was sent by @${sender?.
|
52
|
+
${JSON.stringify(skills)}\n
|
53
|
+
The message was sent by @${sender?.address}
|
46
54
|
|
47
55
|
### Examples
|
48
56
|
prompt /agent tip alix and bo
|
@@ -17,8 +17,8 @@ export async function handler(context: HandlerContext) {
|
|
17
17
|
}
|
18
18
|
// URLs for each game type
|
19
19
|
const gameUrls: { [key: string]: string } = {
|
20
|
-
wordle: "https://framedl.xyz
|
21
|
-
slot: "https://slot-machine-frame.vercel.app
|
20
|
+
wordle: "https://framedl.xyz",
|
21
|
+
slot: "https://slot-machine-frame.vercel.app",
|
22
22
|
};
|
23
23
|
// Respond with the appropriate game URL or an error message
|
24
24
|
switch (params.game) {
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { HandlerContext,
|
1
|
+
import { HandlerContext, AbstractedMember } from "@xmtp/message-kit";
|
2
2
|
import { getStackClient } from "../lib/stack.js";
|
3
3
|
|
4
4
|
export async function handler(context: HandlerContext, fake?: boolean) {
|
@@ -6,15 +6,8 @@ export async function handler(context: HandlerContext, fake?: boolean) {
|
|
6
6
|
const {
|
7
7
|
members,
|
8
8
|
group,
|
9
|
-
|
10
|
-
message: {
|
11
|
-
content,
|
12
|
-
content: { command },
|
13
|
-
sender,
|
14
|
-
typeId,
|
15
|
-
},
|
9
|
+
message: { sender, typeId, content },
|
16
10
|
} = context;
|
17
|
-
console.log(command);
|
18
11
|
if (typeId === "text" && group) {
|
19
12
|
const { command } = content;
|
20
13
|
if (command === "points") {
|
@@ -40,14 +33,13 @@ export async function handler(context: HandlerContext, fake?: boolean) {
|
|
40
33
|
} else if (typeId === "group_updated" && group) {
|
41
34
|
const { initiatedByInboxId, addedInboxes } = content;
|
42
35
|
const adminAddress = members?.find(
|
43
|
-
(member:
|
36
|
+
(member: AbstractedMember) => member.inboxId === initiatedByInboxId,
|
44
37
|
);
|
45
38
|
if (addedInboxes && addedInboxes.length > 0) {
|
46
39
|
//if add someone to the group
|
47
40
|
await stack?.track("referral", {
|
48
41
|
points: 10,
|
49
42
|
account: adminAddress?.address ?? "",
|
50
|
-
uniqueId: adminAddress?.username ?? "",
|
51
43
|
});
|
52
44
|
}
|
53
45
|
}
|
@@ -1,14 +1,15 @@
|
|
1
1
|
import { HandlerContext } from "@xmtp/message-kit";
|
2
2
|
import { vision, textGeneration } from "../lib/openai.js";
|
3
|
+
import { getUserInfo } from "../lib/resolver.js";
|
3
4
|
|
4
5
|
export async function handler(context: HandlerContext) {
|
5
6
|
if (!process?.env?.OPEN_AI_API_KEY) {
|
6
|
-
console.
|
7
|
+
console.warn("No OPEN_AI_API_KEY found in .env");
|
7
8
|
return;
|
8
9
|
}
|
9
10
|
const {
|
10
11
|
members,
|
11
|
-
|
12
|
+
skills,
|
12
13
|
message: {
|
13
14
|
typeId,
|
14
15
|
content: { attachment },
|
@@ -16,8 +17,12 @@ export async function handler(context: HandlerContext) {
|
|
16
17
|
},
|
17
18
|
} = context;
|
18
19
|
|
20
|
+
if (!members) {
|
21
|
+
return;
|
22
|
+
}
|
23
|
+
let senderInfo = await getUserInfo(sender.address);
|
19
24
|
if (attachment && typeId === "remoteStaticAttachment") {
|
20
|
-
const { data
|
25
|
+
const { data } = attachment;
|
21
26
|
const response = await vision(
|
22
27
|
data,
|
23
28
|
"This image is the bill of a restaurant dinner. Return the total. If you can't find the total, return 'undefined'.",
|
@@ -31,9 +36,6 @@ export async function handler(context: HandlerContext) {
|
|
31
36
|
}
|
32
37
|
if (response) {
|
33
38
|
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
39
|
|
38
40
|
## Instructions:
|
39
41
|
When you receive the totals you should split the bill between the members of the group and send to each one a transaction frame
|
@@ -44,18 +46,23 @@ export async function handler(context: HandlerContext) {
|
|
44
46
|
Example:
|
45
47
|
[
|
46
48
|
"This are the details: Total: $49.52. Tip (20%): $9.90",
|
47
|
-
"All users owe X USDC to @${
|
48
|
-
"/send @${
|
49
|
+
"All users owe X USDC to @${senderInfo?.converseUsername}. Pay here:",
|
50
|
+
"/send @${senderInfo?.converseUsername} $9.90"
|
49
51
|
]
|
50
52
|
`;
|
51
53
|
|
52
54
|
//I want the reply to be an array of messages so the bot feels like is sending multuple ones
|
53
|
-
const { reply } = await textGeneration(
|
55
|
+
const { reply } = await textGeneration(
|
56
|
+
sender.address,
|
57
|
+
response,
|
58
|
+
prompt,
|
59
|
+
true,
|
60
|
+
);
|
54
61
|
let splitMessages = JSON.parse(reply);
|
55
62
|
for (const message of splitMessages) {
|
56
63
|
let msg = message as string;
|
57
|
-
if (msg.startsWith("/")) await context.
|
58
|
-
else await context.
|
64
|
+
if (msg.startsWith("/")) await context.skill(msg);
|
65
|
+
else await context.send(msg);
|
59
66
|
}
|
60
67
|
}
|
61
68
|
}
|
@@ -1,4 +1,5 @@
|
|
1
|
-
import { HandlerContext,
|
1
|
+
import { HandlerContext, AbstractedMember } from "@xmtp/message-kit";
|
2
|
+
import { getUserInfo } from "../lib/resolver.js";
|
2
3
|
|
3
4
|
export async function handler(context: HandlerContext) {
|
4
5
|
const {
|
@@ -6,16 +7,15 @@ export async function handler(context: HandlerContext) {
|
|
6
7
|
getMessageById,
|
7
8
|
message: { content, sender, typeId },
|
8
9
|
} = context;
|
10
|
+
console.log(sender);
|
9
11
|
const msg = await getMessageById(content.reference);
|
10
12
|
const replyReceiver = members?.find(
|
11
13
|
(member) => member.inboxId === msg?.senderInboxId,
|
12
14
|
);
|
13
15
|
let amount: number = 0,
|
14
|
-
receivers:
|
16
|
+
receivers: AbstractedMember[] = [];
|
15
17
|
// Handle different types of messages
|
16
18
|
if (typeId === "reply" && replyReceiver) {
|
17
|
-
// Process reply messages/
|
18
|
-
//ha
|
19
19
|
const { content: reply } = content;
|
20
20
|
|
21
21
|
if (reply.includes("degen")) {
|
@@ -31,18 +31,22 @@ export async function handler(context: HandlerContext) {
|
|
31
31
|
// Process text commands starting with "/tip"
|
32
32
|
const {
|
33
33
|
params: { amount: extractedAmount, username },
|
34
|
-
content: text,
|
35
34
|
} = content;
|
36
35
|
amount = extractedAmount || 10; // Default amount if not specified
|
37
|
-
|
36
|
+
|
37
|
+
receivers = await Promise.all(
|
38
|
+
username.map((username: string) => getUserInfo(username)),
|
39
|
+
);
|
38
40
|
}
|
39
41
|
}
|
40
42
|
if (!sender || receivers.length === 0 || amount === 0) {
|
41
43
|
context.reply("Sender or receiver or amount not found.");
|
42
44
|
return;
|
43
45
|
}
|
46
|
+
console.log(receivers);
|
44
47
|
const receiverAddresses = receivers.map((receiver) => receiver.address);
|
45
48
|
// Process sending tokens to each receiver
|
49
|
+
|
46
50
|
context.sendTo(
|
47
51
|
`You received ${amount} tokens from ${sender.address}.`,
|
48
52
|
receiverAddresses,
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { HandlerContext } from "@xmtp/message-kit";
|
2
|
+
import { getUserInfo } from "../lib/resolver.js";
|
2
3
|
|
3
4
|
// Main handler function for processing commands
|
4
5
|
export async function handler(context: HandlerContext) {
|
@@ -7,26 +8,23 @@ export async function handler(context: HandlerContext) {
|
|
7
8
|
content: { command, params },
|
8
9
|
},
|
9
10
|
} = context;
|
10
|
-
const baseUrl = "https://base-frame
|
11
|
+
const baseUrl = "https://base-tx-frame.vercel.app/transaction";
|
11
12
|
|
12
13
|
switch (command) {
|
13
14
|
case "send":
|
14
15
|
// Destructure and validate parameters for the send command
|
15
16
|
const { amount: amountSend, token: tokenSend, username } = params; // [!code hl] // [!code focus]
|
16
|
-
|
17
|
-
if (!amountSend || !tokenSend || !
|
17
|
+
let senderInfo = await getUserInfo(username);
|
18
|
+
if (!amountSend || !tokenSend || !senderInfo) {
|
18
19
|
context.reply(
|
19
20
|
"Missing required parameters. Please provide amount, token, and username.",
|
20
21
|
);
|
21
22
|
return;
|
22
23
|
}
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
receiver: username[0]?.address,
|
28
|
-
});
|
29
|
-
context.reply(`${url_send}`);
|
24
|
+
let name = senderInfo.converseUsername || senderInfo.address;
|
25
|
+
|
26
|
+
let sendUrl = `${baseUrl}/?transaction_type=send&amount=${amountSend}&token=${tokenSend}&receiver=${senderInfo.address}`;
|
27
|
+
context.send(`${sendUrl}`);
|
30
28
|
break;
|
31
29
|
case "swap":
|
32
30
|
// Destructure and validate parameters for the swap command
|
@@ -38,13 +36,9 @@ export async function handler(context: HandlerContext) {
|
|
38
36
|
);
|
39
37
|
return;
|
40
38
|
}
|
41
|
-
|
42
|
-
let
|
43
|
-
|
44
|
-
token_from,
|
45
|
-
token_to,
|
46
|
-
});
|
47
|
-
context.reply(`${url_swap}`);
|
39
|
+
|
40
|
+
let swapUrl = `${baseUrl}/?transaction_type=swap&token_from=${token_from}&token_to=${token_to}&amount=${amount}`;
|
41
|
+
context.send(`${swapUrl}`);
|
48
42
|
break;
|
49
43
|
case "show": // [!code hl] // [!code focus]
|
50
44
|
// Show the base URL without the transaction path
|
@@ -55,26 +49,3 @@ export async function handler(context: HandlerContext) {
|
|
55
49
|
context.reply("Unknown command. Use help to see all available commands.");
|
56
50
|
}
|
57
51
|
}
|
58
|
-
|
59
|
-
// Function to generate a URL with query parameters for transactions
|
60
|
-
function generateFrameURL(
|
61
|
-
baseUrl: string,
|
62
|
-
transaction_type: string,
|
63
|
-
params: { [key: string]: string | number | string[] | undefined },
|
64
|
-
) {
|
65
|
-
// Filter out undefined parameters
|
66
|
-
let filteredParams: {
|
67
|
-
[key: string]: string | number | string[] | undefined;
|
68
|
-
} = {};
|
69
|
-
|
70
|
-
for (const key in params) {
|
71
|
-
if (params[key] !== undefined) {
|
72
|
-
filteredParams[key] = params[key];
|
73
|
-
}
|
74
|
-
}
|
75
|
-
let queryParams = new URLSearchParams({
|
76
|
-
transaction_type,
|
77
|
-
...filteredParams,
|
78
|
-
}).toString();
|
79
|
-
return `${baseUrl}?${queryParams}`;
|
80
|
-
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import { run, HandlerContext } from "@xmtp/message-kit";
|
2
|
+
import { handler as splitpayment } from "./handler/splitpayment.js";
|
3
|
+
|
4
|
+
// Main function to run the app
|
5
|
+
run(
|
6
|
+
async (context: HandlerContext) => {
|
7
|
+
const {
|
8
|
+
message: { typeId },
|
9
|
+
} = context;
|
10
|
+
switch (typeId) {
|
11
|
+
case "reply":
|
12
|
+
handleReply(context);
|
13
|
+
break;
|
14
|
+
case "remoteStaticAttachment":
|
15
|
+
handleAttachment(context);
|
16
|
+
break;
|
17
|
+
}
|
18
|
+
if (!context.group) {
|
19
|
+
context.send("This is a group bot, add this address to a group");
|
20
|
+
}
|
21
|
+
},
|
22
|
+
{ attachments: true },
|
23
|
+
);
|
24
|
+
async function handleReply(context: HandlerContext) {
|
25
|
+
const {
|
26
|
+
v2client,
|
27
|
+
getReplyChain,
|
28
|
+
version,
|
29
|
+
message: {
|
30
|
+
content: { reference },
|
31
|
+
},
|
32
|
+
} = context;
|
33
|
+
|
34
|
+
const { chain, isSenderInChain } = await getReplyChain(
|
35
|
+
reference,
|
36
|
+
version,
|
37
|
+
v2client.address,
|
38
|
+
);
|
39
|
+
//await context.skill(chain);
|
40
|
+
}
|
41
|
+
|
42
|
+
// Handle attachment messages
|
43
|
+
async function handleAttachment(context: HandlerContext) {
|
44
|
+
await splitpayment(context);
|
45
|
+
}
|
46
|
+
|
47
|
+
export async function helpHandler(context: HandlerContext) {
|
48
|
+
const { skills } = context;
|
49
|
+
const intro =
|
50
|
+
"Available experiences:\n" +
|
51
|
+
skills
|
52
|
+
?.flatMap((app) => app.skills)
|
53
|
+
.map((skill) => `${skill.command} - ${skill.description}`)
|
54
|
+
.join("\n") +
|
55
|
+
"\nUse these commands to interact with specific apps.";
|
56
|
+
context.send(intro);
|
57
|
+
}
|
@@ -0,0 +1,137 @@
|
|
1
|
+
import dotenv from "dotenv";
|
2
|
+
dotenv.config();
|
3
|
+
|
4
|
+
import OpenAI from "openai";
|
5
|
+
const openai = new OpenAI({
|
6
|
+
apiKey: process.env.OPEN_AI_API_KEY,
|
7
|
+
});
|
8
|
+
|
9
|
+
export type ChatHistoryEntry = { role: string; content: string };
|
10
|
+
export type ChatHistories = Record<string, ChatHistoryEntry[]>;
|
11
|
+
|
12
|
+
let chatHistories: ChatHistories = {};
|
13
|
+
export async function textGeneration(
|
14
|
+
address: string,
|
15
|
+
userPrompt: string,
|
16
|
+
systemPrompt: string,
|
17
|
+
isGroup: boolean = false,
|
18
|
+
) {
|
19
|
+
let messages = chatHistories[address] || [];
|
20
|
+
if (messages.length === 0) {
|
21
|
+
messages.push({
|
22
|
+
role: "system",
|
23
|
+
content: systemPrompt,
|
24
|
+
});
|
25
|
+
}
|
26
|
+
messages.push({
|
27
|
+
role: "user",
|
28
|
+
content: userPrompt,
|
29
|
+
});
|
30
|
+
try {
|
31
|
+
const response = await openai.chat.completions.create({
|
32
|
+
model: "gpt-4o",
|
33
|
+
messages: messages as any,
|
34
|
+
});
|
35
|
+
const reply = response.choices[0].message.content;
|
36
|
+
messages.push({
|
37
|
+
role: "assistant",
|
38
|
+
content: reply || "No response from OpenAI.",
|
39
|
+
});
|
40
|
+
const cleanedReply = responseParser(reply as string);
|
41
|
+
if (!isGroup) chatHistories[address] = messages;
|
42
|
+
return { reply: cleanedReply, history: messages };
|
43
|
+
} catch (error) {
|
44
|
+
console.error("Failed to fetch from OpenAI:", error);
|
45
|
+
throw error;
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
// New method to interpret an image
|
50
|
+
export async function vision(imageData: Uint8Array, systemPrompt: string) {
|
51
|
+
const base64Image = Buffer.from(imageData).toString("base64");
|
52
|
+
const dataUrl = `data:image/jpeg;base64,${base64Image}`;
|
53
|
+
|
54
|
+
// Create a new thread for each vision request
|
55
|
+
const visionMessages = [
|
56
|
+
{
|
57
|
+
role: "system",
|
58
|
+
content: systemPrompt,
|
59
|
+
},
|
60
|
+
{
|
61
|
+
role: "user",
|
62
|
+
content: [
|
63
|
+
{ type: "text", text: systemPrompt },
|
64
|
+
{
|
65
|
+
type: "image_url",
|
66
|
+
image_url: {
|
67
|
+
url: dataUrl,
|
68
|
+
},
|
69
|
+
},
|
70
|
+
],
|
71
|
+
},
|
72
|
+
];
|
73
|
+
|
74
|
+
try {
|
75
|
+
const response = await openai.chat.completions.create({
|
76
|
+
model: "gpt-4o",
|
77
|
+
messages: visionMessages as any,
|
78
|
+
});
|
79
|
+
return response.choices[0].message.content;
|
80
|
+
} catch (error) {
|
81
|
+
console.error("Failed to interpret image with OpenAI:", error);
|
82
|
+
throw error;
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
export async function processResponseWithskill(
|
87
|
+
address: string,
|
88
|
+
reply: string,
|
89
|
+
context: any,
|
90
|
+
) {
|
91
|
+
let messages = reply
|
92
|
+
.split("\n")
|
93
|
+
.map((message: string) => responseParser(message))
|
94
|
+
.filter((message): message is string => message.length > 0);
|
95
|
+
|
96
|
+
console.log(messages);
|
97
|
+
for (const message of messages) {
|
98
|
+
if (message.startsWith("/")) {
|
99
|
+
const response = await context.skill(message);
|
100
|
+
if (response && response.message) {
|
101
|
+
let msg = responseParser(response.message);
|
102
|
+
|
103
|
+
chatHistories[address].push({
|
104
|
+
role: "system",
|
105
|
+
content: msg,
|
106
|
+
});
|
107
|
+
|
108
|
+
await context.send(response.message);
|
109
|
+
}
|
110
|
+
} else {
|
111
|
+
await context.send(message);
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
115
|
+
export function responseParser(message: string) {
|
116
|
+
let trimmedMessage = message;
|
117
|
+
// Remove bold and underline markdown
|
118
|
+
trimmedMessage = trimmedMessage?.replace(/(\*\*|__)(.*?)\1/g, "$2");
|
119
|
+
// Remove markdown links, keeping only the URL
|
120
|
+
trimmedMessage = trimmedMessage?.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$2");
|
121
|
+
// Remove markdown headers
|
122
|
+
trimmedMessage = trimmedMessage?.replace(/^#+\s*(.*)$/gm, "$1");
|
123
|
+
// Remove inline code formatting
|
124
|
+
trimmedMessage = trimmedMessage?.replace(/`([^`]+)`/g, "$1");
|
125
|
+
// Remove single backticks at the start or end of the message
|
126
|
+
trimmedMessage = trimmedMessage?.replace(/^`|`$/g, "");
|
127
|
+
// Remove leading and trailing whitespace
|
128
|
+
trimmedMessage = trimmedMessage?.replace(/^\s+|\s+$/g, "");
|
129
|
+
// Remove any remaining leading or trailing whitespace
|
130
|
+
trimmedMessage = trimmedMessage.trim();
|
131
|
+
|
132
|
+
return trimmedMessage;
|
133
|
+
}
|
134
|
+
|
135
|
+
export const clearChatHistories = () => {
|
136
|
+
chatHistories = {};
|
137
|
+
};
|
@@ -0,0 +1,126 @@
|
|
1
|
+
import { Client } from "@xmtp/xmtp-js";
|
2
|
+
import { isAddress } from "viem";
|
3
|
+
|
4
|
+
export const converseEndpointURL =
|
5
|
+
"https://converse-website-git-endpoit-ephemerahq.vercel.app";
|
6
|
+
//export const converseEndpointURL = "http://localhost:3000";
|
7
|
+
|
8
|
+
export type InfoCache = Map<string, UserInfo>;
|
9
|
+
export type ConverseProfile = {
|
10
|
+
address: string | null;
|
11
|
+
onXmtp: boolean;
|
12
|
+
avatar: string | null;
|
13
|
+
formattedName: string | null;
|
14
|
+
name: string | null;
|
15
|
+
};
|
16
|
+
export type UserInfo = {
|
17
|
+
ensDomain?: string | undefined;
|
18
|
+
address?: string | undefined;
|
19
|
+
converseUsername?: string | undefined;
|
20
|
+
ensInfo?: EnsData | undefined;
|
21
|
+
avatar?: string | undefined;
|
22
|
+
};
|
23
|
+
export interface EnsData {
|
24
|
+
address?: string;
|
25
|
+
avatar?: string;
|
26
|
+
avatar_small?: string;
|
27
|
+
converse?: string;
|
28
|
+
avatar_url?: string;
|
29
|
+
contentHash?: string;
|
30
|
+
description?: string;
|
31
|
+
ens?: string;
|
32
|
+
ens_primary?: string;
|
33
|
+
github?: string;
|
34
|
+
resolverAddress?: string;
|
35
|
+
twitter?: string;
|
36
|
+
url?: string;
|
37
|
+
wallets?: {
|
38
|
+
eth?: string;
|
39
|
+
};
|
40
|
+
}
|
41
|
+
|
42
|
+
let infoCache: InfoCache = new Map();
|
43
|
+
|
44
|
+
export const clearInfoCache = () => {
|
45
|
+
infoCache.clear();
|
46
|
+
};
|
47
|
+
export const getUserInfo = async (
|
48
|
+
key: string,
|
49
|
+
clientAddress?: string,
|
50
|
+
): Promise<UserInfo | null> => {
|
51
|
+
let data: UserInfo = infoCache.get(key) || {
|
52
|
+
ensDomain: undefined,
|
53
|
+
address: undefined,
|
54
|
+
converseUsername: undefined,
|
55
|
+
ensInfo: undefined,
|
56
|
+
};
|
57
|
+
//console.log("Getting user info", key, clientAddress);
|
58
|
+
if (isAddress(clientAddress || "")) {
|
59
|
+
data.address = clientAddress;
|
60
|
+
} else if (isAddress(key || "")) {
|
61
|
+
data.address = key;
|
62
|
+
} else if (key.includes(".eth")) {
|
63
|
+
data.ensDomain = key;
|
64
|
+
} else if (key == "@user" || key == "@me" || key == "@bot") {
|
65
|
+
data.address = clientAddress;
|
66
|
+
data.ensDomain = key.replace("@", "") + ".eth";
|
67
|
+
data.converseUsername = key.replace("@", "");
|
68
|
+
} else if (key == "@alix") {
|
69
|
+
data.address = "0x3a044b218BaE80E5b9E16609443A192129A67BeA";
|
70
|
+
data.converseUsername = "alix";
|
71
|
+
} else if (key == "@bo") {
|
72
|
+
data.address = "0xbc3246461ab5e1682baE48fa95172CDf0689201a";
|
73
|
+
data.converseUsername = "bo";
|
74
|
+
} else {
|
75
|
+
data.converseUsername = key;
|
76
|
+
}
|
77
|
+
|
78
|
+
let keyToUse = data.address || data.ensDomain || data.converseUsername;
|
79
|
+
let cacheData = keyToUse && infoCache.get(keyToUse);
|
80
|
+
if (cacheData) {
|
81
|
+
//console.log("Getting user info", keyToUse, cacheData);
|
82
|
+
return cacheData;
|
83
|
+
} else {
|
84
|
+
//console.log("Getting user info", keyToUse, data);
|
85
|
+
}
|
86
|
+
|
87
|
+
if (keyToUse?.includes(".eth")) {
|
88
|
+
const response = await fetch(`https://ensdata.net/${keyToUse}`);
|
89
|
+
const ensData: EnsData = (await response.json()) as EnsData;
|
90
|
+
//console.log("Ens data", ensData);
|
91
|
+
if (ensData) {
|
92
|
+
data.ensInfo = ensData;
|
93
|
+
data.ensDomain = ensData?.ens;
|
94
|
+
data.address = ensData?.address;
|
95
|
+
}
|
96
|
+
} else if (keyToUse) {
|
97
|
+
keyToUse = keyToUse.replace("@", "");
|
98
|
+
const response = await fetch(`${converseEndpointURL}/profile/${keyToUse}`, {
|
99
|
+
method: "POST",
|
100
|
+
headers: {
|
101
|
+
"Content-Type": "application/json",
|
102
|
+
Accept: "application/json",
|
103
|
+
},
|
104
|
+
body: JSON.stringify({
|
105
|
+
peer: keyToUse,
|
106
|
+
}),
|
107
|
+
});
|
108
|
+
const converseData = (await response.json()) as ConverseProfile;
|
109
|
+
if (process.env.MSG_LOG)
|
110
|
+
console.log("Converse data", keyToUse, converseData);
|
111
|
+
data.converseUsername =
|
112
|
+
converseData?.formattedName || converseData?.name || undefined;
|
113
|
+
data.address = converseData?.address || undefined;
|
114
|
+
data.avatar = converseData?.avatar || undefined;
|
115
|
+
}
|
116
|
+
if (data.address) infoCache.set(data.address, data);
|
117
|
+
return data;
|
118
|
+
};
|
119
|
+
export const isOnXMTP = async (
|
120
|
+
client: Client,
|
121
|
+
domain: string | undefined,
|
122
|
+
address: string | undefined,
|
123
|
+
) => {
|
124
|
+
if (domain == "fabri.eth") return false;
|
125
|
+
if (address) return (await client.canMessage([address])).length > 0;
|
126
|
+
};
|