summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/index.ts35
-rw-r--r--src/instagram_task.ts130
-rw-r--r--src/slack_reply_task.ts0
-rw-r--r--src/tasks.ts24
-rw-r--r--src/utils.ts23
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++;
+ }
+ }
+};