File size: 7,350 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
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import { AIOStreams, errorResponse, validateConfig } from '@aiostreams/addon';
import manifest from '@aiostreams/addon/src/manifest';
import { Config, StreamRequest } from '@aiostreams/types';
import { unminifyConfig } from '@aiostreams/utils';

const HEADERS = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET,HEAD,POST,OPTIONS',
};

const PROXY_URL = 'https://warp-proxy.bolabaden.org';

// Proxy utility function
async function fetchWithProxy(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
  try {
    // Convert input to string URL
    const targetUrl = typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;

    // First, try to use the proxy
    const proxyUrl = `${PROXY_URL}/fetch?url=${encodeURIComponent(targetUrl)}`;
    const proxyResponse = await fetch(proxyUrl, {
      ...init,
      // Add API key and other headers
      headers: {
        ...init?.headers,
        'User-Agent': 'AIOStreams-CloudflareWorker/1.0',
        'X-API-Key': 'sk_IQys9kpENSiYY8lFuCslok3PauKBRSzeGprmvPfiMWAM9neeXoSqCZW7pMlWKbqPrwtF33kh1F73vf7D4PBpVfZJ1reHEL8d6ny6J03Ho',
      },
    });

    // If proxy responds successfully, return the response
    if (proxyResponse.ok) {
      return proxyResponse;
    }

    // If proxy fails, fall back to direct request
    console.warn(`Proxy failed with status ${proxyResponse.status}, falling back to direct request`);
    return await fetch(input, init);
  } catch (error) {
    // If proxy is completely unreachable, fall back to direct request
    console.warn('Proxy unreachable, falling back to direct request:', error);
    return await fetch(input, init);
  }
}

function createJsonResponse(data: any): Response {
  return new Response(JSON.stringify(data, null, 4), {
    headers: HEADERS,
  });
}

function createResponse(message: string, status: number): Response {
  return new Response(message, {
    status,
    headers: HEADERS,
  });
}

export default {
  async fetch(request, env, ctx): Promise<Response> {
    try {
      const url = new URL(decodeURIComponent(request.url));
      const components = url.pathname.split('/').splice(1);

      // handle static asset requests
      if (components.includes('_next') || components.includes('assets')) {
        return env.ASSETS.fetch(request);
      }

      if (url.pathname === '/icon.ico') {
        return env.ASSETS.fetch(request);
      }

      // redirect to /configure if root path is requested
      if (url.pathname === '/') {
        return Response.redirect(url.origin + '/configure', 301);
      }

      // handle /encrypt-user-data POST requests
      if (components.includes('encrypt-user-data')) {
        const data = (await request.json()) as { data: string };
        if (!data) {
          return createResponse('Invalid Request', 400);
        }
        const dataToEncode = data.data;
        try {
          console.log(
            `Received /encrypt-user-data request with Data: ${dataToEncode}`
          );
          const encodedData = Buffer.from(dataToEncode).toString('base64');
          return createJsonResponse({ data: encodedData, success: true });
        } catch (error: any) {
          console.error(error);
          return createJsonResponse({ error: error.message, success: false });
        }
      }
      // handle /configure and /:config/configure requests
      if (components.includes('configure')) {
        if (components.length === 1) {
          return env.ASSETS.fetch(request);
        } else {
          // display configure page with config still in url
          return env.ASSETS.fetch(
            new Request(url.origin + '/configure', request)
          );
        }
      }

      // handle /manifest.json and /:config/manifest.json requests
      if (components.includes('manifest.json')) {
        if (components.length === 1) {
          return createJsonResponse(manifest());
        } else {
          return createJsonResponse(manifest(undefined, true));
        }
      }

      if (components.includes('stream')) {
        // when /stream is requested without config
        let config = decodeURIComponent(components[0]);
        console.log(`components: ${components}`);
        if (components.length < 4) {
          return createJsonResponse(
            errorResponse(
              'You must configure this addon first',
              url.origin,
              '/configure'
            )
          );
        }
        console.log(`Received /stream request with Config: ${config}`);
        const decodedPath = decodeURIComponent(url.pathname);

        const streamMatch = /stream\/(movie|series)\/([^/]+)\.json/.exec(
          decodedPath
        );
        if (!streamMatch) {
          let path = decodedPath.replace(`/${config}`, '');
          console.error(`Invalid request: ${path}`);
          return createResponse('Invalid request', 400);
        }

        const [type, id] = streamMatch.slice(1);
        console.log(`Received /stream request with Type: ${type}, ID: ${id}`);

        let decodedConfig: Config;

        if (config.startsWith('E-') || config.startsWith('E2-')) {
          return createResponse('Encrypted Config Not Supported', 400);
        }
        try {
          decodedConfig = unminifyConfig(
            JSON.parse(Buffer.from(config, 'base64').toString('utf-8'))
          );
        } catch (error: any) {
          console.error(error);
          return createJsonResponse(
            errorResponse(
              'Unable to parse config, please reconfigure or create an issue on GitHub',
              url.origin,
              '/configure'
            )
          );
        }
        const { valid, errorMessage, errorCode } =
          validateConfig(decodedConfig);
        if (!valid) {
          console.error(`Invalid config: ${errorMessage}`);
          return createJsonResponse(
            errorResponse(errorMessage ?? 'Unknown', url.origin, '/configure')
          );
        }

        if (type !== 'movie' && type !== 'series') {
          return createResponse('Invalid Request', 400);
        }

        let streamRequest: StreamRequest = { id, type };

        decodedConfig.requestingIp =
          request.headers.get('X-Forwarded-For') ||
          request.headers.get('X-Real-IP') ||
          request.headers.get('CF-Connecting-IP') ||
          request.headers.get('X-Client-IP') ||
          undefined;

        // Temporarily replace global fetch with proxy-enabled fetch for AIOStreams
        const originalFetch = globalThis.fetch;
        globalThis.fetch = fetchWithProxy;

        try {
          const aioStreams = new AIOStreams(decodedConfig);
          const streams = await aioStreams.getStreams(streamRequest);
          return createJsonResponse({ streams });
        } finally {
          // Restore original fetch
          globalThis.fetch = originalFetch;
        }
      }

      const notFound = await env.ASSETS.fetch(
        new Request(url.origin + '/404', request)
      );
      return new Response(notFound.body, { ...notFound, status: 404 });
    } catch (e) {
      console.error(e);
      return new Response('Internal Server Error', {
        status: 500,
        headers: {
          'Content-Type': 'text/plain',
        },
      });
    }
  },
} satisfies ExportedHandler<Env>;