import { strict as assert } from "node:assert"; import { env } from "../config.js"; import { createResponse } from "../processing/request.js"; import { testers } from "./service-patterns.js"; import matchAction from "./match-action.js"; import { friendlyServiceName } from "./service-alias.js"; import bilibili from "./services/bilibili.js"; import reddit from "./services/reddit.js"; import twitter from "./services/twitter.js"; import youtube from "./services/youtube.js"; import vk from "./services/vk.js"; import ok from "./services/ok.js"; import tiktok from "./services/tiktok.js"; import tumblr from "./services/tumblr.js"; import vimeo from "./services/vimeo.js"; import soundcloud from "./services/soundcloud.js"; import instagram from "./services/instagram.js"; import vine from "./services/vine.js"; import pinterest from "./services/pinterest.js"; import streamable from "./services/streamable.js"; import twitch from "./services/twitch.js"; import rutube from "./services/rutube.js"; import dailymotion from "./services/dailymotion.js"; import snapchat from "./services/snapchat.js"; import loom from "./services/loom.js"; import facebook from "./services/facebook.js"; import bluesky from "./services/bluesky.js"; let freebind; export default async function({ host, patternMatch, params }) { const { url } = params; assert(url instanceof URL); let dispatcher, requestIP; if (env.freebindCIDR) { if (!freebind) { freebind = await import('freebind'); } requestIP = freebind.ip.random(env.freebindCIDR); dispatcher = freebind.dispatcherFromIP(requestIP, { strict: false }); } try { let r, isAudioOnly = params.downloadMode === "audio", isAudioMuted = params.downloadMode === "mute"; if (!testers[host]) { return createResponse("error", { code: "error.api.service.unsupported" }); } if (!(testers[host](patternMatch))) { return createResponse("error", { code: "error.api.link.unsupported", context: { service: friendlyServiceName(host), } }); } switch (host) { case "twitter": r = await twitter({ id: patternMatch.id, index: patternMatch.index - 1, toGif: !!params.twitterGif, alwaysProxy: params.alwaysProxy, dispatcher }); break; case "vk": r = await vk({ userId: patternMatch.userId, videoId: patternMatch.videoId, quality: params.videoQuality }); break; case "ok": r = await ok({ id: patternMatch.id, quality: params.videoQuality }); break; case "bilibili": r = await bilibili(patternMatch); break; case "youtube": let fetchInfo = { id: patternMatch.id.slice(0, 11), quality: params.videoQuality, format: params.youtubeVideoCodec, isAudioOnly, isAudioMuted, dubLang: params.youtubeDubLang, dispatcher } if (url.hostname === "music.youtube.com" || isAudioOnly) { fetchInfo.quality = "max"; fetchInfo.format = "vp9"; fetchInfo.isAudioOnly = true; fetchInfo.isAudioMuted = false; } r = await youtube(fetchInfo); break; case "reddit": r = await reddit({ sub: patternMatch.sub, id: patternMatch.id, user: patternMatch.user }); break; case "tiktok": r = await tiktok({ postId: patternMatch.postId, id: patternMatch.id, fullAudio: params.tiktokFullAudio, isAudioOnly, h265: params.tiktokH265, alwaysProxy: params.alwaysProxy, }); break; case "tumblr": r = await tumblr({ id: patternMatch.id, user: patternMatch.user, url }); break; case "vimeo": r = await vimeo({ id: patternMatch.id.slice(0, 11), password: patternMatch.password, quality: params.videoQuality, isAudioOnly, }); break; case "soundcloud": isAudioOnly = true; isAudioMuted = false; r = await soundcloud({ url, author: patternMatch.author, song: patternMatch.song, format: params.audioFormat, shortLink: patternMatch.shortLink || false, accessKey: patternMatch.accessKey || false }); break; case "instagram": r = await instagram({ ...patternMatch, quality: params.videoQuality, alwaysProxy: params.alwaysProxy, dispatcher }) break; case "vine": r = await vine({ id: patternMatch.id }); break; case "pinterest": r = await pinterest({ id: patternMatch.id, shortLink: patternMatch.shortLink || false }); break; case "streamable": r = await streamable({ id: patternMatch.id, quality: params.videoQuality, isAudioOnly, }); break; case "twitch": r = await twitch({ clipId: patternMatch.clip || false, quality: params.videoQuality, isAudioOnly, }); break; case "rutube": r = await rutube({ id: patternMatch.id, yappyId: patternMatch.yappyId, key: patternMatch.key, quality: params.videoQuality, isAudioOnly, }); break; case "dailymotion": r = await dailymotion(patternMatch); break; case "snapchat": r = await snapchat({ ...patternMatch, alwaysProxy: params.alwaysProxy, }); break; case "loom": r = await loom({ id: patternMatch.id }); break; case "facebook": r = await facebook({ ...patternMatch }); break; case "bsky": r = await bluesky({ ...patternMatch, alwaysProxy: params.alwaysProxy }); break; default: return createResponse("error", { code: "error.api.service.unsupported" }); } if (r.isAudioOnly) { isAudioOnly = true; isAudioMuted = false; } if (r.error && r.critical) { return createResponse("critical", { code: `error.api.${r.error}`, }) } if (r.error) { let context; switch(r.error) { case "content.too_long": context = { limit: env.durationLimit / 60, } break; case "fetch.fail": case "fetch.rate": case "fetch.critical": case "link.unsupported": case "content.video.unavailable": context = { service: friendlyServiceName(host), } break; } return createResponse("error", { code: `error.api.${r.error}`, context, }) } return matchAction({ r, host, audioFormat: params.audioFormat, isAudioOnly, isAudioMuted, disableMetadata: params.disableMetadata, filenameStyle: params.filenameStyle, twitterGif: params.twitterGif, requestIP, audioBitrate: params.audioBitrate, alwaysProxy: params.alwaysProxy, }) } catch { return createResponse("error", { code: "error.api.fetch.critical", context: { service: friendlyServiceName(host), } }) } }