Skip to content

Commit 8fe329e

Browse files
committed
feat: improve whatsapp client and added naij ai agent
1 parent 2af09f0 commit 8fe329e

18 files changed

Lines changed: 651 additions & 83 deletions

File tree

package-lock.json

Lines changed: 487 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"private": true,
66
"main": "src/index.ts",
77
"scripts": {
8-
"build": "tsc",
8+
"clean": "rimraf dist",
9+
"build": "npm run clean && tsc",
910
"start": "clear && while [ true ]; do node dist/index.js ; done",
1011
"dev": "clear && while [ true ]; do ts-node src/index.ts ; done"
1112
},
@@ -60,6 +61,7 @@
6061
"@types/speedtest-net": "^2.1.5",
6162
"@types/unbzip2-stream": "^1.4.3",
6263
"prisma": "^6.12.0",
64+
"rimraf": "^6.0.1",
6365
"ts-node": "^10.9.2",
6466
"typescript": "^5.8.3"
6567
},

src/commands/logout.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export default async function (msg: Message) {
1515
if (!/^logout$/i.test(msg.body)) return;
1616

1717
try {
18-
await client.logout();
18+
(await client()).logout();
1919
log.info("Logout", "Client logged out successfully.");
2020
} catch (error) {
2121
log.error("Logout", "Failed to log out the client.", error);

src/commands/naij.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Message } from "../../types/message";
2+
import log from "../components/utils/log";
3+
import agentHandler from "../components/ai/agentHandler";
4+
import { greetings } from "../components/utils/data";
5+
6+
export const info = {
7+
command: "naij",
8+
description: "Interact with the Naij AI agent.",
9+
usage: "naij <query>",
10+
example: "naij Who are you?",
11+
role: "user",
12+
cooldown: 5000,
13+
};
14+
15+
export default async function (msg: Message) {
16+
const query = msg.body.replace(/^naij\b\s*/i, "").trim();
17+
if (query.length === 0) {
18+
await msg.reply(greetings[Math.floor(Math.random() * greetings.length)]);
19+
return;
20+
}
21+
22+
let quotedMessage: Message | null = null;
23+
24+
if (msg.hasQuotedMsg) {
25+
quotedMessage = await msg.getQuotedMessage();
26+
}
27+
28+
const today = new Date().toLocaleDateString("en-US", {
29+
weekday: "long",
30+
year: "numeric",
31+
month: "long",
32+
day: "numeric",
33+
});
34+
35+
const prompt = `You are Naij, Today's date is ${today}.
36+
Mix of elder, street-smart padi, social media banger, hustler, comedian, and advice-giver. You fit talk pidgin, Igbo, Yoruba, Hausa small small,
37+
no need to sound proper proper English, just raw Naij vibe. Your mood dey switch anytime—fit dey funny, vex, wise, encouraging, or playful like owambe,
38+
Lagos traffic, NEPA light wahala, or Twitter clapback. You dey max 5 sentence per reply, no long grammar wahala, always capture the heart, spirit, gist,
39+
and hustle of Naij. Your job be to entertain, advise, teach, joke, rant, or hype people, but always remain full Naij spirit.
40+
User: ${query}
41+
${quotedMessage ? `\nQuoted Message: ${quotedMessage.body}` : ""}`;
42+
43+
const text = await agentHandler(prompt);
44+
45+
if (!text) {
46+
log.error("naij", "No response generated.");
47+
await msg.reply("Sorry, I couldn't generate a response. Please try again.");
48+
return;
49+
}
50+
51+
await msg.reply(text);
52+
}

src/commands/resentunsend.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ import { saveSetting } from "../components/services/settings";
33
import log from "../components/utils/log";
44

55
export const info = {
6-
command: "resentunsent",
6+
command: "resentunsend",
77
description: "Enable/disable automatically resending of unsend messages.",
8-
usage: "resentunsent <on|off>",
9-
example: "resentunsent on",
8+
usage: "resentunsend <on|off>",
9+
example: "resentunsend on",
1010
role: "admin",
1111
cooldown: 5000,
1212
};
1313

1414
export default async function (msg: Message) {
15-
const query = msg.body.replace(/^resentunsent\b\s*/i, "").trim();
15+
const query = msg.body.replace(/^resentunsend\b\s*/i, "").trim();
1616
if (query.length === 0 && !/(on|off)/.test(query)) {
1717
await msg.reply("Please provide a value its either on or off.");
1818
return;

src/commands/stalk.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default async function (msg: Message) {
2727
getUserbyLid(lid),
2828
isBlocked(lid),
2929
redis.get(`rate:${lid}`),
30-
client.getProfilePicUrl(jid).catch(() => null),
30+
(await client()).getProfilePicUrl(jid).catch(() => null),
3131
]);
3232

3333
if (!user) {

src/commands/stats.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const info = {
2020
export default async function (msg: Message) {
2121
if (!/^stats$/i.test(msg.body)) return;
2222

23+
const waClient = await client();
2324
// Node.js runtime stats
2425
const mem = process.memoryUsage();
2526
const cpu = process.cpuUsage();
@@ -65,10 +66,10 @@ export default async function (msg: Message) {
6566
si.networkInterfaces(),
6667
getUserCount(),
6768
getBlockUserCount(),
68-
client.getState(),
69-
client.getWWebVersion(),
70-
client.getContactDeviceCount(msg.from),
71-
client.getChats(),
69+
waClient.getState(),
70+
waClient.getWWebVersion(),
71+
waClient.getContactDeviceCount(msg.from),
72+
waClient.getChats(),
7273
speedTest(),
7374
]);
7475

src/commands/uptime.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ export const info = {
1515
export default async function (msg: Message) {
1616
if (!/^uptime\b/i.test(msg.body)) return;
1717

18-
const waStatus = await client.getState();
19-
const waVersion = await client.getWWebVersion();
18+
const waClient = await client();
19+
const [state, version] = await Promise.all([
20+
waClient.getState(),
21+
waClient.getWWebVersion(),
22+
]);
2023

2124
const statsMessage = `
2225
\`${timestamp(process.uptime())}\`
@@ -26,8 +29,8 @@ export default async function (msg: Message) {
2629
.loadavg()
2730
.map((n) => n.toFixed(2))
2831
.join(", ")}
29-
Status: ${waStatus}
30-
Version: ${waVersion}
32+
State: ${state}
33+
Version: ${version}
3134
Node.js: ${process.version}
3235
`;
3336

src/commands/wa.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Message } from "../../types/message"
1+
import { Message } from "../../types/message";
22
import { client } from "../components/client";
33
import log from "../components/utils/log";
44
import logService from "../components/services/log";
@@ -17,7 +17,7 @@ export default async function (msg: Message) {
1717
if (query.length !== 0) {
1818
if (!/^(status|name)$/i.test(query)) {
1919
await msg.reply(
20-
"Invalid argument. Please use one of the following:\n\nstatus or name"
20+
"Invalid argument. Please use one of the following:\n\nstatus or name",
2121
);
2222
return;
2323
}
@@ -34,7 +34,7 @@ export default async function (msg: Message) {
3434
return;
3535
}
3636
if (query === "status") {
37-
client.setStatus(quotedMsg.body);
37+
(await client()).setStatus(quotedMsg.body);
3838
await Promise.all([
3939
msg.reply("Status updated successfully."),
4040
logService(msg, "status", quotedMsg.body),
@@ -43,7 +43,7 @@ export default async function (msg: Message) {
4343
return;
4444
}
4545

46-
client.setDisplayName(quotedMsg.body);
46+
(await client()).setDisplayName(quotedMsg.body);
4747
await Promise.all([
4848
msg.reply("Name updated successfully."),
4949
logService(msg, "name", quotedMsg.body),

src/components/client.ts

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,61 +18,79 @@ import ready from "./events/ready";
1818
import revoke from "./events/revoke";
1919
import callEvent from "./events/call";
2020

21-
const loadingBar = LoadingBar("Loading Client | {bar} | {value}%");
22-
const client = new Client({
23-
puppeteer: {
24-
args: ["--no-sandbox", "--disable-setuid-sandbox"],
25-
executablePath:
26-
process.env.PUPPETEER_EXEC_PATH || "/opt/google/chrome/google-chrome",
27-
},
28-
authStrategy: new LocalAuth(),
29-
});
21+
let instance: Client | null = null;
3022
let isLoadingBarStarted = false;
23+
const loadingBar = LoadingBar("Loading Client | {bar} | {value}%");
24+
25+
async function client(): Promise<Client> {
26+
if (instance) return instance; // prevent re-creating
27+
28+
const newClient = new Client({
29+
puppeteer: {
30+
args: ["--no-sandbox", "--disable-setuid-sandbox"],
31+
executablePath:
32+
process.env.PUPPETEER_EXEC_PATH || "/opt/google/chrome/google-chrome",
33+
},
34+
authStrategy: new LocalAuth(),
35+
});
36+
37+
registerEvents(newClient);
3138

32-
client.on("loading_screen", (percent: number, message: string) => {
33-
if (!isLoadingBarStarted) {
34-
loadingBar.start(100, 0, { message });
35-
isLoadingBarStarted = true;
36-
}
39+
instance = newClient;
40+
await newClient.initialize();
41+
return newClient;
42+
}
3743

38-
if (percent >= 99) loadingBar.stop();
44+
function registerEvents(client: Client) {
45+
client.on("loading_screen", (percent: number, message: string) => {
46+
if (!isLoadingBarStarted) {
47+
loadingBar.start(100, 0, { message });
48+
isLoadingBarStarted = true;
49+
}
50+
if (percent >= 99) loadingBar.stop();
51+
loadingBar.update(percent, { message });
52+
});
3953

40-
loadingBar.update(percent, { message });
41-
});
54+
client.on("authenticated", () =>
55+
log.info("Auth", "Client authenticated successfully."),
56+
);
4257

43-
client.on("authenticated", () =>
44-
log.info("Auth", "Client authenticated successfully."),
45-
);
58+
client.on("qr", (qr: string) => {
59+
log.info("QR", "Scan this QR code with your WhatsApp app:");
60+
qrcode.generate(qr, { small: true });
61+
});
4662

47-
client.on("qr", (qr: string) => {
48-
// Generate and scan this code with your phone
49-
log.info("QR", "Scan this QR code with your WhatsApp app:");
50-
qrcode.generate(qr, { small: true });
51-
});
63+
client.on("ready", () => ready());
5264

53-
client.on("ready", () => ready());
65+
client.on("message_reaction", (react: Reaction) => reaction(client, react));
5466

55-
client.on("message_reaction", (react: Reaction) => reaction(client, react));
67+
client.on("message_create", (msg: Message) => messageEvent(msg, "create"));
5668

57-
// client.on("message", (msg) => messageEvent(msg));
58-
client.on("message_create", (msg: Message) => messageEvent(msg, "create"));
69+
client.on(
70+
"message_edit",
71+
(msg: Message, newBody: string, prevBody: string) => {
72+
msg.body = newBody;
73+
Promise.all([
74+
messageEdit(msg, newBody, prevBody),
75+
messageEvent(msg, "edit"),
76+
]);
77+
},
78+
);
5979

60-
client.on("message_edit", (msg: Message, newBody: string, prevBody: string) => {
61-
msg.body = newBody;
62-
Promise.all([messageEdit(msg, newBody, prevBody), messageEvent(msg, "edit")]);
63-
});
80+
client.on("message_revoke_everyone", (msg: Message, revoked_msg?: Message) =>
81+
revoke(msg, revoked_msg),
82+
);
6483

65-
client.on("message_revoke_everyone", (msg: Message, revoked_msg?: Message) =>
66-
revoke(msg, revoked_msg),
67-
);
84+
client.on("call", (call: Call) => callEvent(call));
6885

69-
client.on("call", (call: Call) => callEvent(call));
86+
client.on("group_join", (notif: GroupNotification) => groupJoin(notif));
87+
client.on("group_leave", (notif: GroupNotification) => groupLeave(notif));
7088

71-
client.on("group_join", (notif: GroupNotification) => groupJoin(notif));
72-
client.on("group_leave", (notif: GroupNotification) => groupLeave(notif));
73-
client.on("auth_failure", (msg: string) => {
74-
loadingBar.stop();
75-
log.error("Auth", "Authentication failed. Please try again.");
76-
});
89+
client.on("auth_failure", () => {
90+
loadingBar.stop();
91+
log.error("Auth", "Authentication failed. Please try again.");
92+
});
93+
}
7794

7895
export { client };
96+
export default client;

0 commit comments

Comments
 (0)