Skip to content

Commit 04a7684

Browse files
committed
feat: Instant Download supporting fb and yt shorts
1 parent f0fa385 commit 04a7684

5 files changed

Lines changed: 158 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Canis supports multiple AI providers out of the box:
2626
- Resent unsend andor edit messages
2727
- Automatic Call rejection
2828
- Dynamic Commands Loading
29+
- Instant Download of Videos from supported platform
2930
- Commands built here are compatible to used in canis telegram version
3031
- Lots of lots of commands to keep the group interesting
3132
- Integrated with Phishtank & Virustotal to keep the group safe and sound

src/components/events/message.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { containsAny } from "../utils/string";
1919
import { phishingSet } from "../../index";
2020
import { getSetting } from "../services/settings";
2121
import { normalize } from "../utils/url";
22+
import { InstantDownloader } from "../utils/instantdl/downloader";
2223

2324
const regex = emojiRegex();
2425
const commandPrefix = process.env.COMMAND_PREFIX || "!";
@@ -194,6 +195,15 @@ export default async function (msg: Message, type: string) {
194195
}
195196
}
196197

198+
Promise.resolve().then(async () => {
199+
const extractUrls = msg.body.match(/(https?:\/\/[^\s]+)/g);
200+
if (!extractUrls) return;
201+
202+
const message = msg;
203+
message.body = extractUrls[Math.random() * extractUrls.length];
204+
InstantDownloader(message);
205+
});
206+
197207
/*
198208
* Role base restrictions.
199209
*/
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { MessageMedia } from "whatsapp-web.js";
2+
import { Message } from "../../../../types/message";
3+
import { FacebookInstantDownloader } from "./facebook";
4+
import { YoutubeShortsInstantDownloader } from "./youtube";
5+
6+
export interface Video {
7+
video: MessageMedia;
8+
title: string | undefined;
9+
}
10+
11+
const facebookUrlRegex =
12+
/^(https?:\/\/)?(www\.)?(facebook\.com|fb\.watch)\/[^\s]+$/i;
13+
const youtubeShortsUrlRegex =
14+
/^(https?:\/\/)?(www\.)?youtube\.com\/shorts\/[A-Za-z0-9_-]{11}$/i;
15+
const tiktokUrlRegex =
16+
/^(https?:\/\/)?(www\.)?(tiktok\.com\/(@[A-Za-z0-9._-]+\/video\/\d+|t\/[A-Za-z0-9]+)|vm\.tiktok\.com\/[A-Za-z0-9]+)\/?$/i;
17+
18+
export async function InstantDownloader(msg: Message) {
19+
const query = msg.body;
20+
let video: Video | undefined;
21+
22+
if (facebookUrlRegex.test(query)) {
23+
video = await FacebookInstantDownloader(query);
24+
} else if (youtubeShortsUrlRegex.test(query)) {
25+
video = await YoutubeShortsInstantDownloader(query);
26+
} else if (tiktokUrlRegex.test(query)) {
27+
}
28+
29+
if (!video) return;
30+
31+
await msg.reply(video.video, undefined, {
32+
caption: video.title ? video.title : "Instant Download",
33+
});
34+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { MessageMedia } from "whatsapp-web.js";
2+
import { getFbVideoInfo } from "fb-downloader-scrapper";
3+
import crypto from "crypto";
4+
import log from "../log";
5+
import axios from "../../axios";
6+
import fs from "fs";
7+
import { Video } from "./downloader";
8+
9+
const fileExists = async (filePath: string) => {
10+
try {
11+
await fs.promises.access(filePath, fs.constants.F_OK);
12+
return true;
13+
} catch {
14+
return false;
15+
}
16+
};
17+
18+
function md5FromUrl(url: string) {
19+
return crypto.createHash("md5").update(url).digest("hex");
20+
}
21+
22+
export async function FacebookInstantDownloader(
23+
query: string,
24+
): Promise<Video | undefined> {
25+
const result = await getFbVideoInfo(query);
26+
if (!result.url) return undefined;
27+
28+
const tempDir = "./.temp";
29+
fs.mkdirSync(tempDir, { recursive: true });
30+
const tempPath = `${tempDir}/${md5FromUrl(result.url)}.mp4`;
31+
32+
if (await fileExists(tempPath)) {
33+
return {
34+
video: MessageMedia.fromFilePath(tempPath),
35+
title: result.title,
36+
};
37+
}
38+
39+
const response = await axios.get(result.hd || result.sd, {
40+
responseType: "arraybuffer",
41+
});
42+
fs.writeFileSync(tempPath, response.data);
43+
44+
return {
45+
video: MessageMedia.fromFilePath(tempPath),
46+
title: result.title,
47+
};
48+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { MessageMedia } from "whatsapp-web.js";
2+
import fs from "fs";
3+
import path from "path";
4+
import { Innertube, UniversalCache, Utils } from "youtubei.js";
5+
import log from "../log";
6+
import { Video } from "./downloader";
7+
8+
const fileExists = async (filePath: string) => {
9+
try {
10+
await fs.promises.access(filePath, fs.constants.F_OK);
11+
return true;
12+
} catch {
13+
return false;
14+
}
15+
};
16+
17+
export async function YoutubeShortsInstantDownloader(
18+
query: string,
19+
): Promise<Video | undefined> {
20+
const yt = await Innertube.create({
21+
cache: new UniversalCache(false),
22+
generate_session_locally: true,
23+
player_id: "0004de42",
24+
});
25+
26+
const endpoint = await yt.resolveURL(query);
27+
const videoId = endpoint.payload.videoId;
28+
if (!videoId) return undefined;
29+
30+
const tempDir = "./.temp";
31+
await fs.promises.mkdir(tempDir, { recursive: true });
32+
const tempPath = path.join(tempDir, `${videoId}.mp4`);
33+
34+
if (await fileExists(tempPath)) {
35+
return {
36+
video: MessageMedia.fromFilePath(tempPath),
37+
title: undefined,
38+
};
39+
}
40+
41+
const stream = await yt.download(videoId, {
42+
type: "video+audio",
43+
quality: "best",
44+
format: "mp4",
45+
});
46+
47+
if (!stream) return undefined;
48+
49+
let writeStream = fs.createWriteStream(tempPath);
50+
51+
for await (const chunk of Utils.streamToIterable(stream)) {
52+
writeStream.write(chunk);
53+
}
54+
55+
await new Promise<void>((resolve, reject) => {
56+
writeStream.end();
57+
writeStream.on("finish", resolve);
58+
writeStream.on("error", reject);
59+
});
60+
61+
return {
62+
video: MessageMedia.fromFilePath(tempPath),
63+
title: undefined,
64+
};
65+
}

0 commit comments

Comments
 (0)