diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/index.ts | 62 | ||||
| -rw-r--r-- | src/tasks.ts | 3 | ||||
| -rw-r--r-- | src/tiktok_task.ts | 132 |
3 files changed, 175 insertions, 22 deletions
diff --git a/src/index.ts b/src/index.ts index 938b5f0..1bf8d6e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ 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 @@ -35,21 +36,6 @@ app.command("/veeti", async ({ command, ack, respond, client }) => { console.log("Received command: /veeti", command.text); const url = command.text.trim(); - if (!url.includes("instagram.com")) { - void respond({ - text: "Only instagram.com URLs are supported at the moment", - response_type: "ephemeral", - }); - return; - } - - // send a message only visible to the user who sent the command - void respond({ - text: "Sharing instagram video, this can take a minute...", - thread_ts: command.ts, - response_type: "ephemeral", - }); - const respondWithFile = async (filePath: string) => { try { console.log("Uploading file", filePath); @@ -84,12 +70,44 @@ app.command("/veeti", async ({ command, ack, respond, client }) => { }); }; - addTask({ - type: "download_instagram", - url, - respondWithFile, - respondTaskFailed, - }); + if ( + url.startsWith("https://instagram.com") || + url.startsWith("https://www.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.startsWith("https://www.tiktok.com") || + url.startsWith("https://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", + }); + } }); const taskLoop = async () => { @@ -102,6 +120,8 @@ const taskLoop = async () => { await runWithRetries(async () => { if (task.type === "download_instagram") { await runDownloadInstagramTask(task); + } else if (task.type === "download_tiktok") { + await runDownloadTiktokTask(task); } else { console.log("Unknown task type, skipping", task); } diff --git a/src/tasks.ts b/src/tasks.ts index 1b0ce9b..8fb1b2f 100644 --- a/src/tasks.ts +++ b/src/tasks.ts @@ -1,7 +1,8 @@ import { InstagramTask } from "./instagram_task"; +import { TikTokTask } from "./tiktok_task"; // union of possible task types -export type Task = InstagramTask; +export type Task = InstagramTask | TikTokTask; const taskQueue: Task[] = []; diff --git a/src/tiktok_task.ts b/src/tiktok_task.ts new file mode 100644 index 0000000..f9099b4 --- /dev/null +++ b/src/tiktok_task.ts @@ -0,0 +1,132 @@ +import fs from "fs"; +import puppeteer, { TimeoutError } from "puppeteer"; +import { wait } from "./utils"; + +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 puppeteer.launch({ + headless: false, + args: ["--shm-size=1gb"], + }); + 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; + } + } + + 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(); + await browser.close(); + } +}; |
