File size: 2,866 Bytes
0bfe2e3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import { BaseProxy, ProxyStream } from './base';
import { createLogger, maskSensitiveInfo, Env } from '../utils';

const logger = createLogger('stremthru');

export class StremThruProxy extends BaseProxy {
  protected generateProxyUrl(endpoint: string): URL {
    const proxyUrl = new URL(this.config.url.replace(/\/$/, ''));
    proxyUrl.pathname = `${proxyUrl.pathname === '/' ? '' : proxyUrl.pathname}${endpoint}`;
    return proxyUrl;
  }

  protected getPublicIpEndpoint(): string {
    return '/v0/health/__debug__';
  }

  protected getPublicIpFromResponse(data: any): string | null {
    return typeof data.data?.ip?.exposed === 'object'
      ? data.data.ip.exposed['*'] || data.data.ip.machine
      : data.data?.ip?.machine || null;
  }

  protected getHeaders(): Record<string, string> {
    const headers: Record<string, string> = {
      'Content-Type': 'application/x-www-form-urlencoded',
    };

    if (Env.ENCRYPT_STREMTHRU_URLS) {
      headers['X-StremThru-Authorization'] = `Basic ${this.config.credentials}`;
    }

    return headers;
  }

  protected async generateStreamUrls(
    streams: ProxyStream[]
  ): Promise<string[] | null> {
    const proxyUrl = this.generateProxyUrl('/v0/proxy');

    if (!Env.ENCRYPT_STREMTHRU_URLS) {
      proxyUrl.searchParams.set('token', this.config.credentials);
    }

    const data = new URLSearchParams();

    streams.forEach((stream, i) => {
      data.append('url', stream.url);
      let req_headers = '';
      if (stream.headers?.request) {
        for (const [key, value] of Object.entries(stream.headers.request)) {
          req_headers += `${key}: ${value}\n`;
        }
      }
      data.append(`req_headers[${i}]`, req_headers);
      if (stream.filename) {
        data.append(`filename[${i}]`, stream.filename);
      }
    });

    if (Env.LOG_SENSITIVE_INFO) {
      logger.debug(`POST ${proxyUrl.toString()}`);
    } else {
      logger.debug(
        `POST ${proxyUrl.protocol}://${maskSensitiveInfo(proxyUrl.hostname)}${proxyUrl.port ? `:${proxyUrl.port}` : ''}/v0/proxy`
      );
    }

    const response = await fetch(proxyUrl.toString(), {
      method: 'POST',
      headers: this.getHeaders(),
      body: data,
      signal: AbortSignal.timeout(30000),
    });

    if (!response.ok) {
      throw new Error(`${response.status}: ${response.statusText}`);
    }

    let responseData: any;
    try {
      responseData = await response.json();
    } catch (error) {
      const text = await response.text();
      logger.debug(`Response body: ${text}`);
      throw new Error('Failed to parse JSON response from StremThru');
    }

    if (responseData.error) {
      throw new Error(responseData.error);
    }

    if (responseData.data?.items) {
      return responseData.data.items;
    } else {
      throw new Error('No URLs were returned from StremThru');
    }
  }
}