summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJan Tuomi <jans.tuomi@gmail.com>2024-03-15 15:25:20 +0200
committerJan Tuomi <jans.tuomi@gmail.com>2024-03-15 15:25:20 +0200
commite3d978605a3c2d31a1fb9fe8d43eacf02a377c67 (patch)
tree178da62e57246c1174266591174a014a1b62686d /src
parent10cbbd6fa6120db38479a1be12c3ee17f80df316 (diff)
Add TikTok support
Diffstat (limited to 'src')
-rw-r--r--src/index.ts62
-rw-r--r--src/tasks.ts3
-rw-r--r--src/tiktok_task.ts132
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();
+ }
+};