-
Notifications
You must be signed in to change notification settings - Fork 200
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adjust the methods of scraping to be more reliable. This was done by adding a new function to handle v2 logic with the new pathname /v2 Updated the readme to have documentation for the /v2 changes
- Loading branch information
Showing
6 changed files
with
217 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,63 @@ | ||
# m3u8 CloudflareWorker Proxy | ||
|
||
### Install Method 1: | ||
## Install Method 1 | ||
[](https://deploy.workers.cloudflare.com/?url=https://github.com/Gratenes/m3u8CloudflareWorkerProxy) | ||
|
||
## Example V2 | ||
|
||
### JavaScript Example | ||
```js | ||
const m3u8url = "https://vz-cea98c59-23c.b-cdn.net/c309129c-27b6-4e43-8254-62a15c77c5ee/842x480/video.m3u8"; | ||
const proxyUrl = "https://proxy.example.com/v2"; | ||
|
||
const proxiedUrl = `${proxyUrl}/v2?url=${encodeURIComponent(m3u8url)}`; | ||
|
||
// Alternative Method using URLSearchParams | ||
const searchParams = new URLSearchParams(); | ||
searchParams.set("url", m3u8url); | ||
|
||
const proxiedUrl2 = `${proxyUrl}/v2?${searchParams.toString()}`; | ||
|
||
// Setting headers | ||
searchParams.set("headers", JSON.stringify({ | ||
Range: "bytes=0-500" | ||
})); | ||
``` | ||
|
||
### cURL Example | ||
```bash | ||
curl --request GET \ | ||
--url 'https://proxy.example.com/v2?headers=%7B%0A%09%22Range%22%3A%20%22bytes%3D0-499%22%0A%7D&url=https%3A%2F%2Fvz-cea98c59-23c.b-cdn.net%2Fc309129c-27b6-4e43-8254-62a15c77c5ee%2F842x480%2Fvideo.m3u8' | ||
``` | ||
|
||
## Install Method 2 | ||
```bash | ||
git clone https://github.com/Gratenes/m3u8CloudflareWorkerProxy.git m3u8proxy | ||
cd m3u8proxy | ||
npx wrangler login | ||
npx wrangler publish | ||
``` | ||
|
||
### Install Method 2: | ||
[](https://deploy.workers.cloudflare.com/?url=https://github.com/Gratenes/m3u8CloudflareWorkerProxy) | ||
## Devlopment Guide | ||
```bash | ||
git clone https://github.com/Gratenes/m3u8CloudflareWorkerProxy.git m3u8proxy | ||
cd m3u8proxy | ||
npm i | ||
npm run dev | ||
``` | ||
|
||
#### Cloudflare Workers Docs | ||
For more details, refer to the [Cloudflare Workers documentation](https://developers.cloudflare.com/workers/get-started/guide/). | ||
|
||
### Example: | ||
```js | ||
const url = 'https://example.url.example/?url=Link.m3u8&origin=url.example' | ||
|
||
// If either your url or link has parameter's, encode via encodeURIComponent(link) | ||
const encodedUrl = `https://m3u8.proxy.example/ | ||
?url=${encodeURIComponent("https://thisdomain.works/file.m3u8")} | ||
&referer=${encodeURIComponent("https://thisdomain.works")} | ||
&origin=${encodeURIComponent("https://thisdomain.works")} | ||
` | ||
``` | ||
--- | ||
|
||
> #### Cloudflare Workers Docs: https://developers.cloudflare.com/workers/get-started/guide/ | ||
> #### Example (Deprecated V1) | ||
> ```js | ||
> const url = 'https://proxy.example.com/?url=Link.m3u8&origin=url.example'; | ||
> | ||
> // Encode parameters using encodeURIComponent | ||
> const encodedUrl = `https://proxy.example.com/ | ||
> ?url=${encodeURIComponent("https://example.com/file.m3u8")} | ||
> &referer=${encodeURIComponent("https://example.com")} | ||
> &origin=${encodeURIComponent("https://example.com")} | ||
> ``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,7 @@ | |
}, | ||
"private": true, | ||
"scripts": { | ||
"start": "wrangler dev", | ||
"dev": "wrangler dev", | ||
"deploy": "wrangler publish" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,61 +1,27 @@ | ||
import {M3u8ProxyV1} from "./logic/v1"; | ||
import {M3u8ProxyV2} from "./logic/v2"; | ||
|
||
addEventListener("fetch", (event) => { | ||
event.respondWith(respondfetch(event.request)); | ||
}); | ||
|
||
async function respondfetch(request) { | ||
try { | ||
const url = new URL(request.url); | ||
const refererUrl = decodeURIComponent(url.searchParams.get("referer") || ""); | ||
const targetUrl = decodeURIComponent(url.searchParams.get("url") || ""); | ||
const originUrl = decodeURIComponent(url.searchParams.get("origin") || ""); | ||
const proxyAll = decodeURIComponent(url.searchParams.get("all") || ""); | ||
|
||
if (!targetUrl) { | ||
return new Response("Invalid URL", { status: 400 }); | ||
} | ||
async function respondfetch(request: Request) { | ||
const url = new URL(request.url); | ||
const pathname = url.pathname; | ||
|
||
const response = await fetch(targetUrl, { | ||
if (pathname === "/") return M3u8ProxyV1(request) | ||
if (pathname === "/v2") { | ||
if (request.method == "OPTIONS") return new Response(null, { | ||
status: 204, // No Content | ||
headers: { | ||
"Access-Control-Allow-Origin": "*", | ||
"Access-Control-Allow-Methods": "GET, HEAD, POST, PUT, DELETE, OPTIONS", | ||
"Access-Control-Allow-Headers": "Content-Type", | ||
Referer: refererUrl || "", | ||
Origin: originUrl || "", | ||
}, | ||
'Access-Control-Allow-Methods': 'GET', | ||
'Access-Control-Allow-Headers': 'Content-Type, Authorization', | ||
} | ||
}); | ||
|
||
let modifiedM3u8; | ||
if (targetUrl.includes(".m3u8")) { | ||
modifiedM3u8 = await response.text(); | ||
const targetUrlTrimmed = `${encodeURIComponent( | ||
targetUrl.replace(/([^/]+\.m3u8)$/, "").trim() | ||
)}`; | ||
const encodedUrl = encodeURIComponent(refererUrl); | ||
const encodedOrigin = encodeURIComponent(originUrl); | ||
modifiedM3u8 = modifiedM3u8.split("\n").map((line) => { | ||
if (line.startsWith("#") || line.trim() == '') { | ||
return line; | ||
} | ||
else if(proxyAll == 'yes' && line.startsWith('http')){ //https://yourproxy.com/?url=https://somevideo.m3u8&all=yes | ||
return `${url.origin}?url=${line}`; | ||
} | ||
return `?url=${targetUrlTrimmed}${line}${originUrl ? `&origin=${encodedOrigin}` : "" | ||
}${refererUrl ? `&referer=${encodedUrl}` : "" | ||
}`; | ||
}).join("\n"); | ||
} | ||
|
||
return new Response(modifiedM3u8 || response.body, { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers: { | ||
"Access-Control-Allow-Origin": "*", | ||
"Content-Type": | ||
response.headers?.get("Content-Type") || | ||
"application/vnd.apple.mpegurl", | ||
}, | ||
}); | ||
} catch (e) { | ||
return new Response(e.message, { status: 500 }); | ||
return M3u8ProxyV2(request) | ||
} | ||
return new Response("Not Found", { | ||
status: 404 | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
|
||
|
||
export const M3u8ProxyV1 = async (request: Request<unknown>) => { | ||
const url = new URL(request.url); | ||
const refererUrl = decodeURIComponent(url.searchParams.get("referer") || ""); | ||
const targetUrl = decodeURIComponent(url.searchParams.get("url") || ""); | ||
const originUrl = decodeURIComponent(url.searchParams.get("origin") || ""); | ||
const proxyAll = decodeURIComponent(url.searchParams.get("all") || ""); | ||
|
||
if (!targetUrl) { | ||
return new Response("Invalid URL", { status: 400 }); | ||
} | ||
|
||
const response = await fetch(targetUrl, { | ||
headers: { | ||
"Access-Control-Allow-Origin": "*", | ||
"Access-Control-Allow-Methods": "GET, HEAD, POST, PUT, DELETE, OPTIONS", | ||
"Access-Control-Allow-Headers": "Content-Type", | ||
Referer: refererUrl || "", | ||
Origin: originUrl || "", | ||
}, | ||
}); | ||
|
||
let modifiedM3u8; | ||
if (targetUrl.includes(".m3u8")) { | ||
modifiedM3u8 = await response.text(); | ||
const targetUrlTrimmed = `${encodeURIComponent( | ||
targetUrl.replace(/([^/]+\.m3u8)$/, "").trim() | ||
)}`; | ||
const encodedUrl = encodeURIComponent(refererUrl); | ||
const encodedOrigin = encodeURIComponent(originUrl); | ||
modifiedM3u8 = modifiedM3u8.split("\n").map((line) => { | ||
if (line.startsWith("#") || line.trim() == '') { | ||
return line; | ||
} | ||
else if(proxyAll == 'yes' && line.startsWith('http')){ //https://yourproxy.com/?url=https://somevideo.m3u8&all=yes | ||
return `${url.origin}?url=${line}`; | ||
} | ||
return `?url=${targetUrlTrimmed}${line}${originUrl ? `&origin=${encodedOrigin}` : "" | ||
}${refererUrl ? `&referer=${encodedUrl}` : "" | ||
}`; | ||
}).join("\n"); | ||
} | ||
|
||
return new Response(modifiedM3u8 || response.body, { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers: { | ||
"Access-Control-Allow-Origin": "*", | ||
"Content-Type": | ||
response.headers?.get("Content-Type") || | ||
"application/vnd.apple.mpegurl", | ||
}, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import {getUrl} from "../utils"; | ||
|
||
|
||
export const M3u8ProxyV2 = async (request: Request<unknown>): Promise<Response> => { | ||
const url = new URL(request.url) | ||
|
||
const scrapeUrlString = url.searchParams.get("url") | ||
const scrapeHeadersString = url.searchParams.get("headers") | ||
|
||
let scrapeHeadersObject: ScrapeHeaders = null | ||
if (scrapeHeadersString) { | ||
try { | ||
scrapeHeadersObject = JSON.parse(scrapeHeadersString) | ||
} catch (e) { | ||
console.log(e) | ||
console.log("[M3u8 Proxy V2] Malformed scrape headers, could no parse using DEFAULT headers") | ||
scrapeHeadersObject = null | ||
} | ||
} | ||
|
||
if (!scrapeUrlString) { | ||
return new Response(JSON.stringify({ | ||
success: false, | ||
message: "no scrape url provided" | ||
}), { | ||
status: 400, | ||
}) | ||
} | ||
|
||
const scrapeUrl = new URL(scrapeUrlString) | ||
const headers: { | ||
[key: string]: string | ||
} = { | ||
"Access-Control-Allow-Origin": "*", | ||
"Access-Control-Allow-Methods": "GET, HEAD, POST, PUT, DELETE, OPTIONS", | ||
"Access-Control-Allow-Headers": "Content-Type", | ||
...(typeof scrapeHeadersObject == "object" ? scrapeHeadersObject : {}), | ||
} | ||
|
||
console.log(headers) | ||
|
||
const response = await fetch(scrapeUrlString, { | ||
headers: headers, | ||
}) | ||
|
||
|
||
// get the content type of the response | ||
const responseContentType = response.headers.get('Content-Type') | ||
let responseBody: BodyInit | null = response.body | ||
|
||
if (responseContentType && (responseContentType.includes("application/vnd.") || responseContentType.includes("video/MP2T"))) { | ||
const m3u8File = await response.text() | ||
const m3u8FileChunks = m3u8File.split("\n") | ||
const m3u8AdjustedChunks: string[] = [] | ||
for (const line of m3u8FileChunks) { | ||
// lines that start with #'s are non data lines (they hold info like bitrate and other stuff) | ||
if (line.startsWith("#") || !line.trim()) { | ||
m3u8AdjustedChunks.push(line) | ||
continue; | ||
} | ||
|
||
const url = getUrl(line, scrapeUrl.protocol + "//" + scrapeUrl.hostname) | ||
const searchParams = new URLSearchParams() | ||
|
||
searchParams.set('url', url.toString()) | ||
if (scrapeHeadersString) searchParams.set('headers', scrapeHeadersString) | ||
|
||
m3u8AdjustedChunks.push(`/v2?${searchParams.toString()}`) | ||
} | ||
|
||
responseBody = m3u8AdjustedChunks.join("\n") | ||
} | ||
|
||
const responseHeaders = new Headers(response.headers) | ||
responseHeaders.set("Access-Control-Allow-Origin", "*") | ||
|
||
return new Response(responseBody, { | ||
status: response.status, | ||
statusText: response.statusText, | ||
headers: responseHeaders, | ||
}) | ||
} | ||
|
||
type ScrapeHeaders = string | null | { | ||
[key: string]: string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export function getUrl(input: string, fallbackUrl: string): URL { | ||
try { | ||
return new URL(input) | ||
} catch (e) { | ||
return new URL(input, fallbackUrl); | ||
} | ||
} |