Skip to content

Commit 7d4a064

Browse files
committed
feat: implement screenshot command to capture webpage images and handle URL validation
1 parent 2736771 commit 7d4a064

8 files changed

Lines changed: 96 additions & 31 deletions

File tree

src/commands/play.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,27 @@ export default async function play(msg: Message) {
3636
return;
3737
}
3838

39-
await msg.reply(`Download in progress... "${audio.title}"`);
39+
// Only allow audios shorter than 10 minutes (600 seconds)
40+
if (audio.length && audio.length.seconds > 600) {
41+
await msg.reply(
42+
"Sorry, only videos shorter than 10 minutes can be downloaded."
43+
);
44+
return;
45+
}
46+
47+
await msg.react("👍");
4048

4149
const stream = await yt.download(audio.id, {
4250
type: "video+audio",
4351
quality: "best",
4452
format: "mp4",
4553
});
4654

55+
if (!stream) {
56+
await msg.reply("Failed to download the audio stream.");
57+
return;
58+
}
59+
4760
const tempDir = "./.temp";
4861
await fs.mkdirSync(tempDir, { recursive: true });
4962

src/commands/screenshot.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Message, MessageMedia } from "whatsapp-web.js";
2+
import puppeteer from "puppeteer";
3+
import fs from "fs/promises";
4+
5+
export const info = {
6+
command: "screenshot",
7+
description: "Take a screenshot of a webpage.",
8+
usage: "screenshot <url>",
9+
example: "screenshot https://example.com",
10+
role: "user",
11+
cooldown: 5000,
12+
};
13+
14+
export default async function (msg: Message) {
15+
const query = msg.body.replace(/^screenshot\b\s*/i, "").trim();
16+
if (query.length === 0) {
17+
await msg.reply("Please provide a URL to take a screenshot of.");
18+
return;
19+
}
20+
21+
// Validate URL
22+
if (!/^https?:\/\//i.test(query)) {
23+
await msg.reply(
24+
"Please provide a valid URL starting with http:// or https://"
25+
);
26+
return;
27+
}
28+
29+
const browser = await puppeteer.launch();
30+
const page = await browser.newPage();
31+
32+
await page.goto(query, {
33+
waitUntil: "networkidle2",
34+
timeout: 60000,
35+
});
36+
37+
const tempDir = "./.temp";
38+
await fs.mkdir(tempDir, { recursive: true });
39+
40+
const path = `${tempDir}/${Date.now()}.png`;
41+
42+
await page.screenshot({ path });
43+
44+
await browser.close();
45+
46+
const media = MessageMedia.fromFilePath(path);
47+
await msg.reply(media);
48+
await fs.unlink(path);
49+
}

src/commands/stats.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,13 @@ export default async function (msg: Message) {
3232
networkInterfaces,
3333
userCount,
3434
blockUserCount,
35-
whatsAppVersion,
36-
whatsAppState,
3735
] = await Promise.all([
3836
si.graphics(),
3937
si.osInfo(),
4038
si.shell(),
4139
si.networkInterfaces(),
4240
getUserCount(),
43-
getBlockUserCount(),
44-
client.getWWebVersion(),
45-
client.getState()
41+
getBlockUserCount()
4642
]);
4743

4844
const statsMessage = `
@@ -56,18 +52,10 @@ export default async function (msg: Message) {
5652
1024 ** 3
5753
).toFixed(2)} GB
5854
VRam: ${gpuInfo.controllers.map((c) => c.vram).join(", ")} MB
59-
Load Avg: ${os
60-
.loadavg()
61-
.map((n) => n.toFixed(2))
62-
.join(", ")}
63-
Process: #${process.pid} ${process.title}
6455
Shell: ${shell}
6556
Network: ${networkInterfaces
6657
.map((iface) => `${iface.iface} ${iface.speed} Mbps`)
6758
.join(", ")}
68-
Node.js: ${process.version}
69-
WA: ${whatsAppVersion}
70-
WA State: ${whatsAppState}
7159
Commands: ${Object.keys(commands).length}
7260
Users: ${userCount}
7361
Blocked Users: ${blockUserCount}

src/commands/uptime.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Message } from "whatsapp-web.js";
2-
import log from "../components/utils/log";
32
import os from "os";
4-
import si from "systeminformation";
3+
import timestamp from "../components/utils/timestamp";
4+
import { client } from "../components/client";
55

66
export const info = {
77
command: "uptime",
@@ -15,12 +15,20 @@ export const info = {
1515
export default async function (msg: Message) {
1616
if (!/^uptime\b/i.test(msg.body)) return;
1717

18-
const uptimeMinutes = Math.floor(process.uptime() / 60);
18+
const waStatus = await client.getState();
19+
const waVersion = await client.getWWebVersion();
20+
1921
const statsMessage = `
20-
\`${uptimeMinutes} minutes\`
22+
\`${timestamp(process.uptime())}\`
2123
2224
ID: #${process.pid}
23-
Platform: ${os.platform()} ${os.arch()}
25+
LA: ${os
26+
.loadavg()
27+
.map((n) => n.toFixed(2))
28+
.join(", ")}
29+
Status: ${waStatus}
30+
Version: ${waVersion}
31+
Node.js: ${process.version}
2432
`;
2533

2634
await msg.reply(statsMessage);

src/commands/video.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,25 @@ export default async function (msg: Message) {
3737
return;
3838
}
3939

40-
const title = video.title.toString();
41-
await msg.reply(`Download in progress... "${title}"`);
40+
// Only allow videos shorter than 10 minutes (600 seconds)
41+
if (video.length && video.length.seconds > 600) {
42+
await msg.reply("Sorry, only videos shorter than 10 minutes can be downloaded.");
43+
return;
44+
}
45+
46+
await msg.react("👍");
4247

4348
const stream = await yt.download(video.video_id, {
4449
type: "video+audio",
4550
quality: "best",
4651
format: "mp4",
4752
});
4853

54+
if (!stream) {
55+
await msg.reply("Failed to download the video stream.");
56+
return;
57+
}
58+
4959
const tempDir = "./.temp";
5060
await fs.promises.mkdir(tempDir, { recursive: true });
5161
const tempPath = path.join(tempDir, `${video.video_id}.mp4`);
@@ -63,11 +73,11 @@ export default async function (msg: Message) {
6373
const media = new MessageMedia(
6474
"audio/mpeg",
6575
audioBuffer.toString("base64"),
66-
`${title}.mp4`
76+
`${video.title}.mp4`
6777
);
6878

6979
await msg.reply(media, msg.from, {
70-
caption: `${title}`,
80+
caption: `${video.title}`,
7181
});
7282
await fs.promises.unlink(tempPath);
7383
await fs.promises.unlink(tempPath + ".mp4");

src/components/utils/loader.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ import fs from "fs";
33
import path from "path";
44
import { commands } from "../../index";
55

6-
export const command = "load";
7-
export const role = "admin";
8-
96
const commandsPath = path.join(__dirname, "..", "..", "commands");
107

118
export default function (file: string, customPath?: string) {

src/components/utils/rateLimiter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import log from "./log";
33

44
const LIMIT = 3;
55
const BASE_WINDOW_MS = 60 * 1000;
6-
const PENALTY_INCREMENT_MS = 60 * 1000;
6+
const PENALTY_INCREMENT_MS = 10 * 1000;
77

88
function getKey(number: string) {
99
return `rate:${number}`;

src/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ const botName = process.env.PROJECT_CANIS_ALIAS || "Canis";
1717
const autoReload = process.env.AUTO_RELOAD === "true";
1818
const commandsPath = path.join(__dirname, "commands");
1919

20-
log.info("Bot", `Welcome to ${botName}!`);
21-
log.info("Bot", `Command prefix: ${commandPrefix}`);
20+
log.info("Bot", `Initiating ${botName}...`);
21+
log.info("Bot", `prefix: ${commandPrefix}`);
2222

2323
const commands: Record<
2424
string,
@@ -33,11 +33,11 @@ const commands: Record<
3333
}
3434
> = {};
3535

36-
fs.readdirSync(commandsPath).forEach((file) => Loader(file));
36+
fs.readdirSync(commandsPath).forEach((file: string) => Loader(file));
3737

3838
// Watch for changes
3939
if (autoReload)
40-
fs.watch(commandsPath, (eventType, filename) => {
40+
fs.watch(commandsPath, (eventType: string, filename: string | null) => {
4141
if (filename && /\.js$|\.ts$/.test(filename)) {
4242
try {
4343
Loader(filename);

0 commit comments

Comments
 (0)