diff options
| author | Jan Tuomi <jans.tuomi@gmail.com> | 2024-03-13 21:21:05 +0200 |
|---|---|---|
| committer | Jan Tuomi <jans.tuomi@gmail.com> | 2024-03-14 17:20:01 +0200 |
| commit | c890ea83d6e49702f0e3201b29c472f095193133 (patch) | |
| tree | fc95bc2502b468a12a8e4a574fd8e3ca5021df5a /src | |
| parent | 0dda3ef940ce63196df949a8e31712b15ce35cf7 (diff) | |
Initial commit
Diffstat (limited to 'src')
| -rw-r--r-- | src/index.ts | 35 | ||||
| -rw-r--r-- | src/instagram_task.ts | 130 | ||||
| -rw-r--r-- | src/slack_reply_task.ts | 0 | ||||
| -rw-r--r-- | src/tasks.ts | 24 | ||||
| -rw-r--r-- | src/utils.ts | 23 |
5 files changed, 212 insertions, 0 deletions
diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..f184d13 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,35 @@ +import { addTask, getTask } from "./tasks"; +import { runDownloadInstagramTask } from "./instagram_task"; +import { runWithRetries, wait } from "./utils"; + +// TESTING DATA +addTask({ + type: "download_instagram", + url: "https://www.instagram.com/reel/C33u39RBF5p/?igsh=MXZuZ2drN2wya2JmZg==", + replyId: "123", +}); + +(async () => { + const MAX_RETRIES = 5; + // Main loop + while (true) { + const task = getTask(); + if (task) { + try { + await runWithRetries(async () => { + if (task.type === "download_instagram") { + await runDownloadInstagramTask(task); + } else if (task.type === "slack_reply") { + console.log("TODO slack reply task"); + } else { + console.log("Unknown task type, skipping", task); + } + }, MAX_RETRIES); + } catch (e) { + console.error("Task failed after max retries, skipping task.", task, e); + } + } else { + await wait(1000); + } + } +})(); diff --git a/src/instagram_task.ts b/src/instagram_task.ts new file mode 100644 index 0000000..6ab9838 --- /dev/null +++ b/src/instagram_task.ts @@ -0,0 +1,130 @@ +import puppeteer, { Browser, Page, TimeoutError } from "puppeteer"; +import { InstagramTask, addTask } from "./tasks"; +import fs from "fs"; +import { wait } from "./utils"; + +export const runDownloadInstagramTask = async (task: InstagramTask) => { + 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://snapinsta.app/"); + await page.type("input#url", task.url); + await page.click("button[type=submit]"); + + 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; + } + } + + const downloadButton = await page.waitForSelector( + ".download-content a[data-event=click_download_btn]", + { 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]; + + addTask({ + type: "slack_reply", + replyId: task.replyId, + message: "Here's the video you requested", + filePath: `./downloads/${downloadedFile}`, + }); + } finally { + await page.close(); + await browser.close(); + } +}; diff --git a/src/slack_reply_task.ts b/src/slack_reply_task.ts new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/slack_reply_task.ts diff --git a/src/tasks.ts b/src/tasks.ts new file mode 100644 index 0000000..388dde8 --- /dev/null +++ b/src/tasks.ts @@ -0,0 +1,24 @@ +export interface InstagramTask { + type: "download_instagram"; + url: string; + replyId: string; +} + +export interface SlackReplyTask { + type: "slack_reply"; + replyId: string; + message: string; + filePath: string; +} + +export type Task = InstagramTask | SlackReplyTask; + +const taskQueue: Task[] = []; + +export const addTask = (task: Task) => { + taskQueue.push(task); +}; + +export const getTask = (): Task | undefined => { + return taskQueue.shift(); +}; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..b9b5cfa --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,23 @@ +export const wait = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); + +export const runWithRetries = async <T>( + fn: () => Promise<T>, + retries: number, + backoff: number = 2000 +): Promise<T> => { + let attempts = 0; + while (true) { + await wait(attempts * backoff); + try { + return await fn(); + } catch (e) { + if (attempts >= retries) { + throw e; + } + + console.error("Error:", e); + attempts++; + } + } +}; |
