Skip to content

Commit

Permalink
Implement a v2
Browse files Browse the repository at this point in the history
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
Gratenes committed Aug 11, 2024
1 parent fab6aef commit 0b3739b
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 67 deletions.
66 changes: 51 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,63 @@
# m3u8 CloudflareWorker Proxy

### Install Method 1:
## Install Method 1
[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](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:
[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](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")}
> ```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
},
"private": true,
"scripts": {
"start": "wrangler dev",
"dev": "wrangler dev",
"deploy": "wrangler publish"
}
}
68 changes: 17 additions & 51 deletions src/index.ts
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
})
}
55 changes: 55 additions & 0 deletions src/logic/v1.ts
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",
},
});
}
86 changes: 86 additions & 0 deletions src/logic/v2.ts
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
}
7 changes: 7 additions & 0 deletions src/utils/index.ts
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);
}
}

0 comments on commit 0b3739b

Please sign in to comment.