media-scraper
Version:
TypeScript-first multi-platform social media scraper without API keys
82 lines (81 loc) • 3.34 kB
JavaScript
import { $fetch } from "ofetch";
import { parseURL } from "ufo";
import { redditHeaders } from "../utils/helpers.mjs";
import { redditRegex } from "../utils/regex.mjs";
export default async (url) => {
const match = url.match(redditRegex);
if (!match) throw new Error("Invalid Reddit URL");
const { protocol, host, pathname } = parseURL(url);
const jsonData = await $fetch(`${protocol}//${host}${pathname}/.json`, {
headers: redditHeaders
}).catch(() => null);
const { data } = jsonData.find((item) => item?.data?.children?.[0]?.kind === "t3")?.data?.children?.[0];
const crosspostData = data?.crosspost_parent_list?.[0];
const buildVideoObject = async (videoData2) => {
if (videoData2?.is_video || videoData2?.url?.includes(".gif")) {
let finalURL;
if (videoData2?.url?.includes(".gif")) {
finalURL = videoData2?.media?.reddit_video?.fallback_url;
} else {
const dash = videoData2?.media?.reddit_video?.dash_url;
const xmlString = await $fetch(dash, { responseType: "text" }).catch(() => null);
const dashAudio = xmlString?.match(/<AdaptationSet[^>]+contentType="audio"[^>]*>[\s\S]+?<BaseURL>(.*?)<\/BaseURL>/)?.[1];
const fallback_audio = `${videoData2?.url}/${dashAudio}`;
const fallback_video = videoData2?.media?.reddit_video?.fallback_url;
const merge = await $fetch("https://redvid.io/download-link", {
query: {
token: {
video_url: fallback_video,
audio_url: fallback_audio,
id: data?.id
}
}
}).catch(() => null);
finalURL = merge?.url ? `https://redvid.io${merge?.url}` : null;
}
if (!finalURL) return null;
return {
width: videoData2?.media?.reddit_video?.width,
height: videoData2?.media?.reddit_video?.height,
duration: videoData2?.media?.reddit_video?.duration ? videoData2?.media.reddit_video.duration * 1e3 : void 0,
url: finalURL,
type: videoData2?.url?.includes(".gif") ? "gif" : videoData2?.is_video ? "video" : void 0
};
}
};
const videoData = crosspostData || data;
const buildedData = await buildVideoObject(videoData);
const authorData = await $fetch(`${protocol}//${host}/user/${videoData?.author}/about.json`, {
headers: redditHeaders
}).catch(() => null);
return {
id: data?.id,
caption: data?.title?.trim(),
permalink_url: `${protocol}//${host}${data?.permalink}`,
thumbnail_url: data?.thumbnail?.replace(/&/g, "&"),
short_url: data?.url,
author: {
id: data?.author_fullname?.replace("t2_", ""),
name: data?.author,
username: data?.author,
avatar_url: authorData?.data?.snoovatar_img,
url: data?.author ? `https://www.reddit.com/user/${data?.author}/` : void 0
},
up_count: data?.ups,
comment_count: data?.num_comments,
created_at: data?.created_utc,
...buildedData && {
video: buildedData
},
...videoData?.gallery_data?.items && {
gallery: videoData?.gallery_data?.items?.map((item) => {
const metaData = videoData?.media_metadata?.[item?.media_id];
return {
url: metaData?.s?.u.replace(/&/g, "&"),
width: metaData?.s?.x,
height: metaData?.s?.y
};
})
}
};
};