File size: 4,231 Bytes
11fcc5a
de4b571
 
 
11fcc5a
de4b571
 
 
 
 
 
 
 
11fcc5a
de4b571
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11fcc5a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
de4b571
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11fcc5a
de4b571
 
 
 
11fcc5a
 
 
 
 
de4b571
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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") {
                        // audio response without a format is invalid
                        return internalError();
                    }
                    delete response.audio;
                }

                if (!response.output.type || !response.output.filename) {
                    // response without a type or filename is invalid
                    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) {
    // TODO: remove after backwards compatibility period
    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();
}