Cf-tmdb
基于 Cloudflare Workers 的轻量级 TMDB 代理
项目简介
一个基于 Cloudflare Workers 的轻量级 TMDB 代理,适合国内直连网络访问 TMDB,让 Emby 不借助 VPN 网络也能正常刮削图片与元数据。利用 Cloudflare 全球边缘节点免费workers套餐,刮削拉取速度飞快,具体刮削速度自行体验感受。
提前准备
1. 域名托管到 Cloudflare
- 需要有一个域名,并将其 DNS 托管到 Cloudflare
- 点击前往 Cloudflare 官网
2. Emby 媒体服务器配置
请 下载 安装替代 TMDB 配置 功能的神医助手插件。
安装方式:下载 StrmAssistant.dll 文件保存在 emby容器的 plugins文件夹内和其他.dll文件放置一起 → 重启 emby生效
- 点击前往神医助手 Wiki 页面
- emby服务器 控制台 左下角 点击 神医助手 → 元数据增强 → 打开 代替TMDB配置 → 两个代替地址填空 填入 Worker 自定义域名 → 保存 即可体验秒出海报。
- 目前 使用代替 TMDB 配置 在神医助手pro版属于无需收费激活2.0版本,请觉得不错的朋友可以付费激活3.0版体验其他功能,推荐朋友学习此教程也请推荐神医助手3.0 pro版激活其他功能。
下载 StrmAssistant.dll 需要网络环境进github,待神医助手提供国内网络可下载平台和付费激活渠道。
功能特点
- 不需要申请 TMDB API 密钥
- 多源图片代理,包含 TMDB fanart.tv
- 支持 Emby 或其他需填用 `api.tmdb.org` 和 `image.tmdb.org` 的工具
- Cloudflare 全球加速 刮削速度更快
使用说明
- 托管域名到 Cloudflare
- 点击 Workers 和 Pages → 创建应用程序 → 从 Hello World 开始 开始使用 → Worker 名称 随意填 → 部署 → 找到 编辑代码 → 把下面的代码 复制替换掉 workers.js 的代码 → 右上角 → 部署,等屏幕下方出现绿色就部署成功
- 添加 Worker 自定义域地址,(进入此 Worker 项目主页 → 设置 → 域与路由 → 添加 → 自定义域 → 你托管域名的子域名 例如:abc.com 子域名可以 tmdb.abc.com → 添加域 自定义域名就是 https://tmdb.abc.com)填写到需要填 api.tmdb.org 和 image.tmdb.org 填空中,替代 TMDB 官方 API 地址。
- 对于 Emby 推荐使用神医助手来简化 TMDB 配置。
- 找剧集或电影刷新元数据或者搜索图像
设置好的自定义域名代理因有 Cloudflare Workers 免费套餐限制,个人刮削使用完全足够,请不要分享代理自定义域名给他人使用,可推荐他人自主安装 Worker 使用自己的 Cloudflare Worker 免费套餐。
部署代码
将以下代码复制到 Cloudflare Workers 中:
const API_ORIGIN = 'https://api.themoviedb.org';
const IMAGE_ORIGIN = 'https://image.tmdb.org';
const API_CACHE_TTL = 600;
const IMAGE_CACHE_TTL = 86400;
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const pathname = url.pathname;
const search = url.search;
if (pathname.startsWith('/3/') || pathname.startsWith('/4/')) {
const targetUrl = `${API_ORIGIN}${pathname}${search}`;
return handleAPIRequest(request, targetUrl);
}
if (pathname.startsWith('/t/p/')) {
const targetUrl = `${IMAGE_ORIGIN}${pathname}${search}`;
return handleImageRequest(request, targetUrl, ctx);
}
return new Response('Cf-tmdb Worker is running\n\nUsage:\n- API: /3/movie/550\n- Image: /t/p/w500/8uO0gUM8aNqYLs1OsTBQiXu0fEv.jpg', {
status: 200,
headers: {
'Content-Type': 'text/plain; charset=utf-8'
}
});
}
};
async function handleAPIRequest(request, targetUrl) {
try {
const headers = new Headers();
for (const [key, value] of request.headers) {
if (!['host', 'cookie', 'authorization'].includes(key.toLowerCase())) {
headers.set(key, value);
}
}
headers.set('Accept', 'application/json');
headers.set('User-Agent', 'Mozilla/5.0 (compatible; Cf-tmdb-Proxy/1.0)');
const response = await fetch(targetUrl, {
method: request.method,
headers: headers,
body: request.method !== 'GET' && request.method !== 'HEAD' ? request.body : undefined
});
const responseHeaders = new Headers(response.headers);
responseHeaders.set('Access-Control-Allow-Origin', '*');
responseHeaders.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
responseHeaders.set('Access-Control-Allow-Headers', '*');
responseHeaders.set('Cache-Control', `public, max-age=${API_CACHE_TTL}`);
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: responseHeaders
});
} catch (error) {
return new Response(JSON.stringify({
error: 'API proxy failed',
message: error.message
}), {
status: 500,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
}
});
}
}
async function handleImageRequest(request, targetUrl, ctx) {
try {
const cache = caches.default;
const cacheKey = new Request(targetUrl, request);
let response = await cache.match(cacheKey);
if (response) {
return response;
}
response = await fetch(targetUrl, {
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; Cf-tmdb-Proxy/1.0)'
}
});
if (!response.ok) {
return response;
}
const responseHeaders = new Headers(response.headers);
responseHeaders.set('Access-Control-Allow-Origin', '*');
responseHeaders.set('Cache-Control', `public, max-age=${IMAGE_CACHE_TTL}`);
responseHeaders.delete('set-cookie');
responseHeaders.delete('vary');
const cachedResponse = new Response(response.body, {
status: response.status,
headers: responseHeaders
});
ctx.waitUntil(cache.put(cacheKey, cachedResponse.clone()));
return cachedResponse;
} catch (error) {
return new Response(JSON.stringify({
error: 'Image proxy failed',
message: error.message
}), {
status: 500,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
}
});
}
}
此 Worker 玩法由网友摸鱼出来,用 AI 助手编写代码。此 Workers 可随意复制,代码随意修改创作,随意利用您的想法和专业知识创作升级代码的功能,本网站不负责解答任何问题和承担责任。
Cloudflare Workers 免费套餐限制
请注意自主修改创作需了解 Cloudflare Workers 的免费套餐限制,防止超过限制被短暂限制 Worker 当天请求,需第二天重置计数后才可重新请求数据。
请求数
每天最多 100,000 次请求,每分钟约 1000 请求
子请求
每个 Worker 调用中最多 50 个子请求,每次调用最多同时 6 条外部连接
脚本大小
压缩后最大 3MB,环境变量最多 64 个,每个最大 5 KB
内存
每个执行环境最多 128 MB 内存
CPU 时间
每次调用最多 10 毫秒 CPU 时间,可配置调高到最多 5 分钟
Cache API
每次请求最多 50 次 Cache 操作,单个缓存对象最大 512 MB
Worker 数量
免费账户最多 100 个 Worker 脚本,每个账号最多 5 个 Cron Trigger
KV 存储
KV 存储总量 1 GB,读取操作每天 100,000 次
Worker 代理个人家庭 Emby 刮削请求在免费套餐每日限制范围内,切勿修改请求太高。