|
|
import mime from "mime"; |
|
|
import ipaddr from "ipaddr.js"; |
|
|
|
|
|
import { apiSchema } from "./schema.js"; |
|
|
import { createProxyTunnels, createStream } from "../stream/manage.js"; |
|
|
|
|
|
export function createResponse(responseType, responseData) { |
|
|
const internalError = (code) => { |
|
|
return { |
|
|
status: 500, |
|
|
body: { |
|
|
status: "error", |
|
|
error: { |
|
|
code: code || "error.api.fetch.critical.core", |
|
|
}, |
|
|
critical: true |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
try { |
|
|
let status = 200, |
|
|
response = {}; |
|
|
|
|
|
if (responseType === "error") { |
|
|
status = 400; |
|
|
} |
|
|
|
|
|
switch (responseType) { |
|
|
case "error": |
|
|
response = { |
|
|
error: { |
|
|
code: responseData?.code, |
|
|
context: responseData?.context, |
|
|
} |
|
|
} |
|
|
break; |
|
|
|
|
|
case "redirect": |
|
|
response = { |
|
|
url: responseData?.url, |
|
|
filename: responseData?.filename |
|
|
} |
|
|
break; |
|
|
|
|
|
case "tunnel": |
|
|
response = { |
|
|
url: createStream(responseData), |
|
|
filename: responseData?.filename |
|
|
} |
|
|
break; |
|
|
|
|
|
case "local-processing": |
|
|
response = { |
|
|
type: responseData?.type, |
|
|
service: responseData?.service, |
|
|
tunnel: createProxyTunnels(responseData), |
|
|
|
|
|
output: { |
|
|
type: mime.getType(responseData?.filename) || undefined, |
|
|
filename: responseData?.filename, |
|
|
metadata: responseData?.fileMetadata || undefined, |
|
|
subtitles: !!responseData?.subtitles || undefined, |
|
|
}, |
|
|
|
|
|
audio: { |
|
|
copy: responseData?.audioCopy, |
|
|
format: responseData?.audioFormat, |
|
|
bitrate: responseData?.audioBitrate, |
|
|
cover: !!responseData?.cover || undefined, |
|
|
cropCover: !!responseData?.cropCover || undefined, |
|
|
}, |
|
|
|
|
|
isHLS: responseData?.isHLS, |
|
|
} |
|
|
|
|
|
if (!response.audio.format) { |
|
|
if (response.type === "audio") { |
|
|
|
|
|
return internalError(); |
|
|
} |
|
|
delete response.audio; |
|
|
} |
|
|
|
|
|
if (!response.output.type || !response.output.filename) { |
|
|
|
|
|
return internalError(); |
|
|
} |
|
|
break; |
|
|
|
|
|
case "picker": |
|
|
response = { |
|
|
picker: responseData?.picker, |
|
|
audio: responseData?.url, |
|
|
audioFilename: responseData?.filename |
|
|
} |
|
|
break; |
|
|
|
|
|
case "critical": |
|
|
return internalError(responseData?.code); |
|
|
|
|
|
default: |
|
|
throw "unreachable" |
|
|
} |
|
|
|
|
|
return { |
|
|
status, |
|
|
body: { |
|
|
status: responseType, |
|
|
...response |
|
|
} |
|
|
} |
|
|
} catch { |
|
|
return internalError(); |
|
|
} |
|
|
} |
|
|
|
|
|
export function normalizeRequest(request) { |
|
|
|
|
|
if ("localProcessing" in request && typeof request.localProcessing === "boolean") { |
|
|
request.localProcessing = request.localProcessing ? "preferred" : "disabled"; |
|
|
} |
|
|
|
|
|
return apiSchema.safeParseAsync(request).catch(() => ( |
|
|
{ success: false } |
|
|
)); |
|
|
} |
|
|
|
|
|
export function getIP(req, prefix = 56) { |
|
|
const strippedIP = req.ip.replace(/^::ffff:/, ''); |
|
|
const ip = ipaddr.parse(strippedIP); |
|
|
if (ip.kind() === 'ipv4') { |
|
|
return strippedIP; |
|
|
} |
|
|
|
|
|
const v6Bytes = ip.toByteArray(); |
|
|
v6Bytes.fill(0, prefix / 8); |
|
|
|
|
|
return ipaddr.fromByteArray(v6Bytes).toString(); |
|
|
} |
|
|
|