summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/index.ts61
-rw-r--r--src/instagram_task.ts134
-rw-r--r--src/tasks.ts44
-rw-r--r--src/tiktok_task.ts147
-rw-r--r--src/utils.ts17
5 files changed, 73 insertions, 330 deletions
diff --git a/src/index.ts b/src/index.ts
index 0b13af9..a246292 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,10 +1,13 @@
-import { addTask, getTask, numOfTasksInQueue } from "./tasks";
-import { runDownloadInstagramTask } from "./instagram_task";
+import {
+ addTask,
+ getTask,
+ numOfTasksInQueue,
+ runDownloadUrlTask,
+} from "./tasks";
import { runWithRetries, wait } from "./utils";
import { AppOptions, App as SlackApp } from "@slack/bolt";
import fs from "fs";
import dotenv from "dotenv";
-import { runDownloadTiktokTask } from "./tiktok_task";
dotenv.config();
const QUEUE_MAX_LENGTH = process.env.QUEUE_MAX_LENGTH
@@ -36,12 +39,10 @@ app.command("/veeti", async ({ command, ack, respond, client }) => {
console.log("Received command: /veeti", command.text);
const url = command.text.trim();
- const respondWithFile = async (filePath: string) => {
+ const respondWithFile = async (fileName: string) => {
try {
- console.log("Uploading file", filePath);
- const fileContent = fs.readFileSync(filePath);
- const extension = filePath.split(".").pop();
- const fileName = `veeti.${extension}`;
+ console.log("Uploading file", fileName);
+ const fileContent = fs.readFileSync(`downloads/${fileName}`);
console.log("Sending message to channel", command.channel_id);
await client.filesUploadV2({
channel_id: command.channel_id,
@@ -70,38 +71,12 @@ app.command("/veeti", async ({ command, ack, respond, client }) => {
});
};
- if (url.includes("instagram.com")) {
- void respond({
- text: "Sharing instagram video, this can take a minute...",
- thread_ts: command.ts,
- response_type: "ephemeral",
- });
-
- addTask({
- type: "download_instagram",
- url,
- respondWithFile,
- respondTaskFailed,
- });
- } else if (url.includes("tiktok.com")) {
- void respond({
- text: "Sharing tiktok video, this can take a minute...",
- thread_ts: command.ts,
- response_type: "ephemeral",
- });
-
- addTask({
- type: "download_tiktok",
- url,
- respondWithFile,
- respondTaskFailed,
- });
- } else {
- void respond({
- text: "Invalid URL, please provide an Instagram or TikTok video URL.",
- response_type: "ephemeral",
- });
- }
+ addTask({
+ type: "download_url",
+ url,
+ respondWithFile,
+ respondTaskFailed,
+ });
});
const taskLoop = async () => {
@@ -112,10 +87,8 @@ const taskLoop = async () => {
if (task) {
try {
await runWithRetries(async () => {
- if (task.type === "download_instagram") {
- await runDownloadInstagramTask(task);
- } else if (task.type === "download_tiktok") {
- await runDownloadTiktokTask(task);
+ if (task.type === "download_url") {
+ await runDownloadUrlTask(task);
} else {
console.log("Unknown task type, skipping", task);
}
diff --git a/src/instagram_task.ts b/src/instagram_task.ts
deleted file mode 100644
index 2c6c6c8..0000000
--- a/src/instagram_task.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-import fs from "fs";
-import puppeteer, { TimeoutError } from "puppeteer";
-import { wait } from "./utils";
-import { getBrowser } from "./browser";
-
-export interface InstagramTask {
- type: "download_instagram";
- url: string;
- respondWithFile: (filePath: string) => Promise<void>;
- respondTaskFailed: (err: unknown) => Promise<void>;
-}
-
-export const runDownloadInstagramTask = async (task: InstagramTask) => {
- const browser = await getBrowser();
- const page = await browser.newPage();
- const client = await page.createCDPSession();
- await client.send("Page.setDownloadBehavior", {
- behavior: "allow",
- downloadPath: "./downloads",
- });
-
- try {
- console.log("Starting task:", task.url);
-
- const downloadDirFiles = fs.readdirSync("./downloads");
- console.log("Emptying downloads folder");
- downloadDirFiles.forEach((file) => {
- fs.unlinkSync(`./downloads/${file}`);
- });
-
- await page.goto("https://snapins.ai/");
-
- try {
- const consent = await page.waitForSelector("button.fc-cta-consent", {
- timeout: 3000,
- });
- if (consent) {
- console.log("Looks like a cookie consent dialog, clicking agree");
- await consent.click();
- }
- } catch (e) {
- if (e instanceof TimeoutError) {
- console.log("No cookie consent dialog found");
- } else {
- throw e;
- }
- }
-
- console.log("Waiting for a bit");
- await wait(1000);
- console.log("Done waiting");
-
- // try {
- // const adModalClose = await page.waitForSelector("#adOverlay button", {
- // timeout: 3000,
- // });
- // console.log("Looks like an ad modal, clicking close");
- // await adModalClose?.click();
- // } catch (e) {
- // if (e instanceof TimeoutError) {
- // console.log("No ad modal");
- // } else {
- // throw e;
- // }
- // }
-
- try {
- const secondAdModalClose = await page.waitForSelector("#dismiss-button", {
- timeout: 3000,
- });
- console.log("Looks like a second ad modal, clicking close");
- await secondAdModalClose?.click();
- } catch (e) {
- if (e instanceof TimeoutError) {
- console.log("No second ad modal");
- } else {
- throw e;
- }
- }
-
- await page.type("input#input-url", task.url);
- await page.click("#submit-btn");
-
- const downloadButton = await page.waitForSelector("a[download]", {
- timeout: 10_000,
- });
- if (!downloadButton) {
- throw new Error("Could not find download button");
- }
-
- console.log("Found download button, pressing enter");
- await downloadButton?.scrollIntoView();
- await downloadButton?.focus();
- await downloadButton?.press("Enter");
-
- console.log("Waiting to see if one more ad pops up");
- await wait(1000);
-
- // Click outside shadow-dommed ad modal to close it and start the download
- console.log(
- "Clicking outside the possible ad modal to close it and start the download",
- );
- await page.mouse.click(10, 10);
-
- console.log("Waiting for download to start");
- await wait(3000);
-
- let downloadTimeout = 5 * 60 * 1000; // 5 minutes
- let downloadedFiles = fs.readdirSync("./downloads");
- while (downloadedFiles.some((file) => file.endsWith(".crdownload"))) {
- if (downloadTimeout <= 0) {
- throw new Error("Download timed out");
- }
-
- console.log("Download still in progress, waiting");
- await wait(1000);
- downloadTimeout -= 1000;
- downloadedFiles = fs.readdirSync("./downloads");
- }
-
- console.log("Downloaded files", downloadedFiles);
- if (downloadedFiles.length === 0) {
- throw new Error("No files downloaded");
- } else if (downloadedFiles.length > 1) {
- throw new Error("More than one file downloaded, not sure what to do");
- }
-
- const downloadedFile = downloadedFiles[0];
-
- await task.respondWithFile(`./downloads/${downloadedFile}`);
- } finally {
- await page.close();
- }
-};
diff --git a/src/tasks.ts b/src/tasks.ts
index 8fb1b2f..0646da5 100644
--- a/src/tasks.ts
+++ b/src/tasks.ts
@@ -1,8 +1,44 @@
-import { InstagramTask } from "./instagram_task";
-import { TikTokTask } from "./tiktok_task";
+import { asyncExec } from "./utils";
+import fs from "fs";
+export const checkIfYtdlpIsInstalled = async () => {
+ try {
+ await asyncExec("yt-dlp --version");
+ return true;
+ } catch (e) {
+ console.error("yt-dlp is not installed or available in PATH", e);
+ return false;
+ }
+};
+
+export const runDownloadUrlTask = async (task: Task) => {
+ try {
+ const filename = "veeti.mp4";
+ // remove the file if it exists
+ try {
+ fs.rmSync(filename);
+ } catch (e: any) {
+ if (e.code !== "ENOENT") {
+ console.error("Error deleting file", e);
+ }
+ }
-// union of possible task types
-export type Task = InstagramTask | TikTokTask;
+ const { stdout } = await asyncExec(
+ `yt-dlp -o "downloads/${filename}" "${task.url}"`,
+ );
+ console.log(stdout);
+ task.respondWithFile(filename);
+ } catch (e) {
+ console.error("Error downloading URL", e);
+ throw e;
+ }
+};
+
+export type Task = {
+ url: string;
+ type: "download_url";
+ respondWithFile: (file: string) => void;
+ respondTaskFailed: (err: unknown) => void;
+};
const taskQueue: Task[] = [];
diff --git a/src/tiktok_task.ts b/src/tiktok_task.ts
deleted file mode 100644
index 15e30e0..0000000
--- a/src/tiktok_task.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-import fs from "fs";
-import puppeteer, { TimeoutError } from "puppeteer";
-import { wait } from "./utils";
-import { getBrowser } from "./browser";
-
-export interface TikTokTask {
- type: "download_tiktok";
- url: string;
- respondWithFile: (filePath: string) => Promise<void>;
- respondTaskFailed: (err: unknown) => Promise<void>;
-}
-
-export const runDownloadTiktokTask = async (task: TikTokTask) => {
- const browser = await getBrowser();
- const page = await browser.newPage();
- const client = await page.createCDPSession();
- await client.send("Page.setDownloadBehavior", {
- behavior: "allow",
- downloadPath: "./downloads",
- });
-
- try {
- console.log("Starting task:", task.url);
-
- const downloadDirFiles = fs.readdirSync("./downloads");
- console.log("Emptying downloads folder");
- downloadDirFiles.forEach((file) => {
- fs.unlinkSync(`./downloads/${file}`);
- });
-
- await page.goto("https://snaptik.app/");
-
- try {
- const consent = await page.waitForSelector("button.fc-cta-consent", {
- timeout: 3000,
- });
- if (consent) {
- console.log("Looks like a cookie consent dialog, clicking agree");
- await consent.click();
- }
- } catch (e) {
- if (e instanceof TimeoutError) {
- console.log("No cookie consent dialog found");
- } else {
- throw e;
- }
- }
-
- try {
- const cont = await page.waitForSelector("button.continue-web", {
- timeout: 3000,
- });
- if (cont) {
- console.log(
- "Looks like a 'continue using the web' dialog, clicking continue",
- );
- await cont.click();
- }
- } catch (e) {
- if (e instanceof TimeoutError) {
- console.log("No 'continue using the web' dialog found");
- } else {
- throw e;
- }
- }
-
- await page.type("input#url", task.url);
- await page.click('button[type="submit"]');
-
- console.log("Waiting for a bit");
- await wait(1000);
- console.log("Done waiting");
-
- // try {
- // const adModalClose = await page.waitForSelector("#adOverlay button", {
- // timeout: 3000,
- // });
- // console.log("Looks like an ad modal, clicking close");
- // await adModalClose?.click();
- // } catch (e) {
- // if (e instanceof TimeoutError) {
- // console.log("No ad modal");
- // } else {
- // throw e;
- // }
- // }
-
- // try {
- // const secondAdModalClose = await page.waitForSelector("#dismiss-button", {
- // timeout: 3000,
- // });
- // console.log("Looks like a second ad modal, clicking close");
- // await secondAdModalClose?.click();
- // } catch (e) {
- // if (e instanceof TimeoutError) {
- // console.log("No second ad modal");
- // } else {
- // throw e;
- // }
- // }
-
- const downloadButton = await page.waitForSelector(
- "a[data-event=server01_file]",
- { timeout: 10_000 },
- );
- if (!downloadButton) {
- throw new Error("Could not find download button");
- }
-
- console.log("Found download button, pressing enter");
- await downloadButton?.scrollIntoView();
- await downloadButton?.focus();
- await downloadButton?.press("Enter");
-
- console.log("Waiting to see if one more ad pops up");
- await wait(1000);
-
- // Click outside shadow-dommed ad modal to close it and start the download
- // console.log(
- // "Clicking outside the possible ad modal to close it and start the download"
- // );
- // await page.mouse.click(10, 10);
-
- console.log("Waiting for download to start");
- await wait(3000);
-
- let downloadedFiles = fs.readdirSync("./downloads");
- while (downloadedFiles.some((file) => file.endsWith(".crdownload"))) {
- console.log("Download still in progress, waiting");
- await wait(1000);
- downloadedFiles = fs.readdirSync("./downloads");
- }
-
- console.log("Downloaded files", downloadedFiles);
- if (downloadedFiles.length === 0) {
- throw new Error("No files downloaded");
- } else if (downloadedFiles.length > 1) {
- throw new Error("More than one file downloaded, not sure what to do");
- }
-
- const downloadedFile = downloadedFiles[0];
-
- await task.respondWithFile(`./downloads/${downloadedFile}`);
- } finally {
- await page.close();
- }
-};
diff --git a/src/utils.ts b/src/utils.ts
index b9b5cfa..fd8a26d 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,10 +1,25 @@
+import { exec } from "child_process";
+
+export const asyncExec = (command: string) => {
+ return new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
+ exec(command, (error, stdout, stderr) => {
+ if (error) {
+ console.error(`exec error: ${error}`);
+ reject(error);
+ } else {
+ resolve({ stdout, stderr });
+ }
+ });
+ });
+};
+
export const wait = (ms: number) =>
new Promise((resolve) => setTimeout(resolve, ms));
export const runWithRetries = async <T>(
fn: () => Promise<T>,
retries: number,
- backoff: number = 2000
+ backoff: number = 2000,
): Promise<T> => {
let attempts = 0;
while (true) {