mirror of
https://github.com/ZSCGR/CloudFlare-ImgBed.git
synced 2026-08-21 16:13:44 +08:00
支持CloudflareR2存储
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# CloudFlare-ImgBed
|
||||
|
||||
免费图片托管解决方案,基于 Cloudflare Pages 和 Telegram (文件大小不超过20MB,过大图片会**自动压缩**)。
|
||||
免费图片托管解决方案,基于 Cloudflare Pages 和 Telegram,支持 Telegram Bot 存储渠道和 Cloudflare R2 存储渠道。
|
||||
|
||||
**体验地址**:[Sanyue ImgHub (demo-cloudflare-imgbed.pages.dev)](https://demo-cloudflare-imgbed.pages.dev/)
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
免费图片托管解决方案(支持存储绝大多数常见格式的**图片、视频、动图**等),具有**后台管理、图片审查**、**登录鉴权**、**页面自定义**、**多种方式及多文件上传**、**多文件及多格式链接复制**等功能(详见[第2章](#2.Features))。
|
||||
|
||||
此外,拖拽上传的方式**并没有严格限制文件类型**,理论上你可以上传**任何**不超过20MB的文件,但是暂时不会针对图片和视频外的文件进行特殊优化和适配。
|
||||
此外,拖拽上传的方式**并没有严格限制文件类型**,理论上你可以上传**任何**文件,但是暂时不会针对图片和视频外的文件进行特殊优化和适配。
|
||||
|
||||

|
||||
|
||||
@@ -48,12 +48,27 @@
|
||||
- **人性化上传**
|
||||
|
||||
- 支持绝大多数常见**图片、视频、动图**等
|
||||
|
||||
- 支持 **Telegram Bot**, **Cloudflare R2** 等多种存储渠道一键切换
|
||||
|
||||
> Telegram Bot渠道:上传文件大小限制为20MB,提供客户端和服务端压缩功能
|
||||
>
|
||||
> Cloudflare R2渠道:上传大小不限,但超过免费额度会扣费,详见[Pricing | Cloudflare R2 docs](https://developers.cloudflare.com/r2/pricing/)
|
||||
>
|
||||
> 
|
||||
|
||||
- 支持多种上传方式(**拖拽点击、粘贴**)
|
||||
|
||||
- 粘贴上传支持**文件**和**URL**
|
||||
|
||||
- 支持批量上传(不限同时选择文件数量,但为了保证稳定性,同时处于上传状态的文件最多为10个)
|
||||
|
||||
- 上传显示实时上传进度
|
||||
|
||||
- **上传后图片无需手动点击,可直接展示在管理页面中**
|
||||
|
||||
- **过大图片在前端进行压缩,提升上传稳定性和加载性能**
|
||||
|
||||
- 支持自定义压缩质量,自定义开启前后端压缩功能
|
||||
|
||||
- **多样化复制**
|
||||
@@ -98,7 +113,7 @@
|
||||
|
||||
#### 3.1.1提前准备
|
||||
|
||||
- **Telegram的`TG_BOT_TOKEN`和`TG_CHAT_ID`**
|
||||
- 开通**Telegram Bot渠道**必须:**Telegram的`TG_BOT_TOKEN`和`TG_CHAT_ID`**
|
||||
|
||||
首先需要拥有一个Telegram账户,然后按照以下步骤获取`TG_BOT_TOKEN`和`TG_CHAT_ID`。
|
||||
|
||||
@@ -118,6 +133,20 @@
|
||||
|
||||

|
||||
|
||||
- 开通**Cloudflare R2渠道**必须:新建一个Cloudflare R2存储桶,前提是需要绑定支付方式。
|
||||
|
||||
1. 前往Cloudflare Dashboard,选择`R2 存储对象`
|
||||
|
||||

|
||||
|
||||
2. 选择`创建存储桶`,名称随意,填完后点击`创建存储桶`即可完成创建
|
||||
|
||||

|
||||
|
||||
3. 根据需求可选操作:如果**需要启用图像审查,需要开启存储桶的公网访问权限**,有两种开启方式,详见下图。无论你选择哪种方式,都需要记下完整的公网访问链接,格式为`https://xxxx.xxx`
|
||||
|
||||

|
||||
|
||||
- **部署于Cloudflare**
|
||||
|
||||
需准备一个**Cloudflare账户**,然后按照[3.1.2.1节](#3.1.2.1部署于Cloudflare)的步骤即可完成部署。
|
||||
@@ -142,7 +171,19 @@
|
||||
|
||||
3. 按照页面提示输入项目名称,选择需要连接的 git 仓库,点击`部署站点`
|
||||
|
||||
3. 将3.1.1中获取的`TG_BOT_TOKEN`和`TG_CHAT_ID`分别添加到环境变量中,对应**环境变量名为`TG_BOT_TOKEN`和`TG_CHAT_ID`**
|
||||
3. 根据**所需存储渠道**进行相关设置:
|
||||
|
||||
- `Telegram 渠道`:将3.1.1中获取的`TG_BOT_TOKEN`和`TG_CHAT_ID`分别添加到环境变量中,对应**环境变量名为`TG_BOT_TOKEN`和`TG_CHAT_ID`**
|
||||
|
||||
- `Cloudflare R2 渠道`:
|
||||
|
||||
将前面新建的存储桶绑定到项目,名称为`img_r2`
|
||||
|
||||

|
||||
|
||||
如果后续要开启公网访问,需要设置`R2PublicUrl`环境变量,值为前面记下的**R2存储桶公网访问链接**:
|
||||
|
||||

|
||||
|
||||
3. **绑定KV数据库**:
|
||||
|
||||
@@ -222,7 +263,7 @@ API格式:
|
||||
| ------------ | ------------------------------------------------------------ |
|
||||
| **接口功能** | 上传图片或视频 |
|
||||
| **请求方法** | POST |
|
||||
| **请求参数** | **Query参数**:<br />`authCode`,string类型,即为你设置的认证码<br />`serverCompress`,boolean类型,表示是否开启服务端压缩(仅针对图片文件生效)<br />**Body参数(application/form-data)**:<br />`file`,file类型,你要上传的文件 |
|
||||
| **请求参数** | **Query参数**:<br />`authCode`,string类型,即为你设置的认证码<br />`serverCompress`,boolean类型,表示是否开启服务端压缩(仅针对图片文件、Telegram上传渠道生效)<br />`uploadChannel`,string类型,取值为`telegram`和`cfr2`,分别代表telegram bot渠道和Cloudflare R2渠道,默认为telegram bot渠道<br />**Body参数(application/form-data)**:<br />`file`,file类型,你要上传的文件 |
|
||||
| **返回响应** | `data[0].src`为获得的图片链接(注意不包含域名,需要自己添加) |
|
||||
|
||||
> **请求示例**:
|
||||
@@ -358,19 +399,23 @@ API格式:
|
||||
9. ~~支持大于5MB的图片上传前自动压缩~~(2024.8.26已完成)
|
||||
10. ~~上传页面右下角工具栏样式重构,支持上传页自定义压缩(上传前+存储端)~~(2024.9.28已完成)
|
||||
11. 重构管理端,认证+显示效果优化,增加图片详情页
|
||||
12. 管理端增加访问量统计,IP记录、IP黑名单等
|
||||
12. 管理端增加访问量统计,IP记录、IP黑名单、上传IP黑名单等
|
||||
13. ~~上传页面点击链接,自动复制到剪切板~~(2024.9.27已完成)
|
||||
14. ~~上传设置记忆(上传方式、链接格式等)~~(2024.9.27已完成,**两种上传方式合并**)
|
||||
15. ~~若未设置密码,无需跳转进入登录页~~(2024.9.27已完成)
|
||||
16. ~~增加仅删除上传成功图片、上传失败图片重试~~(2024.9.28已完成)
|
||||
17. ~~优化粘贴上传时文件命名方法~~(2024.9.26已完成)
|
||||
18. 增加对R2 bucket的支持
|
||||
18. ~~增加对R2 bucket的支持~~(2024.11.5已完成)
|
||||
19. 管理端增加批量黑名单、白名单功能
|
||||
20. Telegram Channel渠道上传文件记录机器人和频道数据,便于迁移和备份
|
||||
|
||||
### 4.2Fix Bugs👻
|
||||
|
||||
1. ~~修复API上传无法直接展示在后台的问题~~(2024.7.25已修复)
|
||||
1. ~~由于telegra.ph关闭上传,迁移至TG频道上传~~(2024.9.7已修复)
|
||||
1. ~~修复未设管理员认证时管理端无限刷新的问题~~(2024.9.9已修复)
|
||||
1. ~~修复部分视频无法预览播放的问题~~(经测试,暂定为文件自身存在问题,暂无法修复)
|
||||
1. 增加新的图片审查渠道
|
||||
|
||||
## 5.Q&A
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
+71
-41
@@ -46,8 +46,37 @@ export async function onRequest(context) { // Contents of context object
|
||||
const fileName = imgRecord.metadata?.FileName || params.id;
|
||||
const encodedFileName = encodeURIComponent(fileName);
|
||||
const fileType = imgRecord.metadata?.FileType || null;
|
||||
//const TgFileID = params.id.split('.')[0]; // 文件 ID
|
||||
|
||||
// Cloudflare R2渠道
|
||||
if (imgRecord.metadata?.Channel === 'CloudflareR2') {
|
||||
// 检查是否配置了R2
|
||||
if (typeof env.img_r2 == "undefined" || env.img_r2 == null || env.img_r2 == "") {
|
||||
return new Response('Error: Please configure R2 database', { status: 500 });
|
||||
}
|
||||
|
||||
const R2DataBase = env.img_r2;
|
||||
const object = await R2DataBase.get(params.id);
|
||||
|
||||
if (object === null) {
|
||||
return new Response('Error: Failed to fetch image', { status: 500 });
|
||||
}
|
||||
|
||||
const headers = new Headers();
|
||||
object.writeHttpMetadata(headers)
|
||||
headers.set('Content-Disposition', `inline; filename="${encodedFileName}"; filename*=UTF-8''${encodedFileName}`);
|
||||
if (fileType) {
|
||||
headers.set('Content-Type', fileType);
|
||||
}
|
||||
|
||||
const newRes = new Response(object.body, {
|
||||
status: 200,
|
||||
headers,
|
||||
});
|
||||
|
||||
return await returnWithCheck(newRes, request, env, params, url);
|
||||
}
|
||||
|
||||
// Telegram及Telegraph渠道
|
||||
let TgFileID = ''; // Tg的file_id
|
||||
if (imgRecord.metadata?.Channel === 'Telegram') {
|
||||
// id为file_id + ext
|
||||
@@ -94,46 +123,7 @@ export async function onRequest(context) { // Contents of context object
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Referer header equal to the dashboard page
|
||||
if (request.headers.get('Referer') == url.origin + "/dashboard") {
|
||||
//show the image
|
||||
return newRes;
|
||||
}
|
||||
|
||||
if (typeof env.img_url == "undefined" || env.img_url == null || env.img_url == "") { } else {
|
||||
//check the record from kv
|
||||
const record = await env.img_url.getWithMetadata(params.id);
|
||||
if (record.metadata === null) {
|
||||
} else {
|
||||
//if the record is not null, redirect to the image
|
||||
if (record.metadata.ListType == "White") {
|
||||
return newRes;
|
||||
} else if (record.metadata.ListType == "Block") {
|
||||
console.log("Referer")
|
||||
console.log(request.headers.get('Referer'))
|
||||
if (typeof request.headers.get('Referer') == "undefined" || request.headers.get('Referer') == null || request.headers.get('Referer') == "") {
|
||||
return Response.redirect(url.origin + "/blockimg", 302)
|
||||
} else {
|
||||
return new Response('Error: Image Blocked', { status: 404 });
|
||||
}
|
||||
|
||||
} else if (record.metadata.Label == "adult") {
|
||||
if (typeof request.headers.get('Referer') == "undefined" || request.headers.get('Referer') == null || request.headers.get('Referer') == "") {
|
||||
return Response.redirect(url.origin + "/blockimg", 302)
|
||||
} else {
|
||||
return new Response('Error: Image Blocked', { status: 404 });
|
||||
}
|
||||
}
|
||||
//check if the env variables WhiteList_Mode are set
|
||||
if (env.WhiteList_Mode == "true") {
|
||||
//if the env variables WhiteList_Mode are set, redirect to the image
|
||||
return Response.redirect(url.origin + "/whiteliston", 302);
|
||||
} else {
|
||||
//if the env variables WhiteList_Mode are not set, redirect to the image
|
||||
return newRes;
|
||||
}
|
||||
}
|
||||
}
|
||||
return await returnWithCheck(newRes, request, env, params, url);
|
||||
}
|
||||
return newRes;
|
||||
} catch (error) {
|
||||
@@ -141,6 +131,46 @@ export async function onRequest(context) { // Contents of context object
|
||||
}
|
||||
}
|
||||
|
||||
async function returnWithCheck(response, request, env, params, url) {
|
||||
// Referer header equal to the dashboard page
|
||||
if (request.headers.get('Referer') == url.origin + "/dashboard") {
|
||||
//show the image
|
||||
return response;
|
||||
}
|
||||
|
||||
if (typeof env.img_url == "undefined" || env.img_url == null || env.img_url == "") { } else {
|
||||
//check the record from kv
|
||||
const record = await env.img_url.getWithMetadata(params.id);
|
||||
if (record.metadata === null) {
|
||||
} else {
|
||||
//if the record is not null, redirect to the image
|
||||
if (record.metadata.ListType == "White") {
|
||||
return response;
|
||||
} else if (record.metadata.ListType == "Block") {
|
||||
if (typeof request.headers.get('Referer') == "undefined" || request.headers.get('Referer') == null || request.headers.get('Referer') == "") {
|
||||
return Response.redirect(url.origin + "/blockimg", 302)
|
||||
} else {
|
||||
return new Response('Error: Image Blocked', { status: 404 });
|
||||
}
|
||||
|
||||
} else if (record.metadata.Label == "adult") {
|
||||
if (typeof request.headers.get('Referer') == "undefined" || request.headers.get('Referer') == null || request.headers.get('Referer') == "") {
|
||||
return Response.redirect(url.origin + "/blockimg", 302)
|
||||
} else {
|
||||
return new Response('Error: Image Blocked', { status: 404 });
|
||||
}
|
||||
}
|
||||
//check if the env variables WhiteList_Mode are set
|
||||
if (env.WhiteList_Mode == "true") {
|
||||
//if the env variables WhiteList_Mode are set, redirect to the image
|
||||
return Response.redirect(url.origin + "/whiteliston", 302);
|
||||
} else {
|
||||
//if the env variables WhiteList_Mode are not set, redirect to the image
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
async function getFileContent(request, max_retries = 2) {
|
||||
let retries = 0;
|
||||
while (retries <= max_retries) {
|
||||
|
||||
+189
-111
@@ -28,70 +28,7 @@ function getCookieValue(cookies, name) {
|
||||
return match ? decodeURIComponent(match[2]) : null;
|
||||
}
|
||||
|
||||
export async function onRequestPost(context) { // Contents of context object
|
||||
const { request, env, params, waitUntil, next, data } = context;
|
||||
|
||||
const url = new URL(request.url);
|
||||
const clonedRequest = await request.clone();
|
||||
|
||||
// 获得上传IP
|
||||
const uploadIp = request.headers.get("cf-connecting-ip") || request.headers.get("x-real-ip") || request.headers.get("x-forwarded-for") || request.headers.get("x-client-ip") || request.headers.get("x-host") || request.headers.get("x-originating-ip") || request.headers.get("x-cluster-client-ip") || request.headers.get("forwarded-for") || request.headers.get("forwarded") || request.headers.get("via") || request.headers.get("requester") || request.headers.get("true-client-ip") || request.headers.get("client-ip") || request.headers.get("x-remote-ip") || request.headers.get("x-originating-ip") || request.headers.get("fastly-client-ip") || request.headers.get("akamai-origin-hop") || request.headers.get("x-remote-ip") || request.headers.get("x-remote-addr") || request.headers.get("x-remote-host") || request.headers.get("x-client-ip") || request.headers.get("x-client-ips") || request.headers.get("x-client-ip")
|
||||
|
||||
await errorHandling(context);
|
||||
telemetryData(context);
|
||||
|
||||
if (typeof env.img_url == "undefined" || env.img_url == null || env.img_url == "") {
|
||||
// img_url 未定义或为空的处理逻辑
|
||||
return new Response('Error: Please configure KV database', { status: 500 });
|
||||
}
|
||||
|
||||
const formdata = await clonedRequest.formData();
|
||||
const fileType = formdata.get('file').type;
|
||||
const fileName = formdata.get('file').name;
|
||||
let fileExt = fileName.split('.').pop(); // 文件扩展名
|
||||
if (!isExtValid(fileExt)) {
|
||||
// 如果文件名中没有扩展名,尝试从文件类型中获取
|
||||
fileExt = fileType.split('/').pop();
|
||||
if (!isExtValid(fileExt)) {
|
||||
// Type中无法获取扩展名
|
||||
fileExt = 'unknown' // 默认扩展名
|
||||
}
|
||||
}
|
||||
|
||||
// 由于TG会把gif后缀的文件转为视频,所以需要修改后缀名绕过限制
|
||||
if (fileExt === 'gif') {
|
||||
const newFileName = fileName.replace(/\.gif$/, '.jpeg');
|
||||
const newFile = new File([formdata.get('file')], newFileName, { type: fileType });
|
||||
formdata.set('file', newFile);
|
||||
} else if (fileExt === 'webp') {
|
||||
const newFileName = fileName.replace(/\.webp$/, '.jpeg');
|
||||
const newFile = new File([formdata.get('file')], newFileName, { type: fileType });
|
||||
formdata.set('file', newFile);
|
||||
}
|
||||
|
||||
const fileTypeMap = {
|
||||
'image/': {'url': 'sendPhoto', 'type': 'photo'},
|
||||
'video/': {'url': 'sendVideo', 'type': 'video'},
|
||||
'audio/': {'url': 'sendAudio', 'type': 'audio'},
|
||||
'application/pdf': {'url': 'sendDocument', 'type': 'document'},
|
||||
};
|
||||
|
||||
const defaultType = {'url': 'sendDocument', 'type': 'document'};
|
||||
|
||||
let sendFunction = Object.keys(fileTypeMap).find(key => fileType.startsWith(key))
|
||||
? fileTypeMap[Object.keys(fileTypeMap).find(key => fileType.startsWith(key))]
|
||||
: defaultType;
|
||||
|
||||
// GIF 特殊处理
|
||||
if (fileType === 'image/gif' || fileType === 'image/webp' || fileExt === 'gif' || fileExt === 'webp') {
|
||||
sendFunction = {'url': 'sendAnimation', 'type': 'animation'};
|
||||
}
|
||||
|
||||
// 从参数中获取serverCompress,如果为false,则使用sendDocument接口
|
||||
if (url.searchParams.get('serverCompress') === 'false') {
|
||||
sendFunction = {'url': 'sendDocument', 'type': 'document'};
|
||||
}
|
||||
|
||||
function authCheck(env, url, request) {
|
||||
// 优先从请求 URL 获取 authCode
|
||||
let authCode = url.searchParams.get('authCode');
|
||||
// 如果 URL 中没有 authCode,从 Referer 中获取
|
||||
@@ -118,70 +55,97 @@ export async function onRequestPost(context) { // Contents of context object
|
||||
}
|
||||
}
|
||||
if (isAuthCodeDefined(env.AUTH_CODE) && !isValidAuthCode(env.AUTH_CODE, authCode)) {
|
||||
return new UnauthorizedException("error");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function onRequestPost(context) { // Contents of context object
|
||||
const { request, env, params, waitUntil, next, data } = context;
|
||||
|
||||
const url = new URL(request.url);
|
||||
const clonedRequest = await request.clone();
|
||||
|
||||
// 鉴权
|
||||
if (!authCheck(env, url, request)) {
|
||||
return UnauthorizedException('Unauthorized');
|
||||
}
|
||||
|
||||
// 构建目标 URL 时剔除 authCode 参数
|
||||
// const targetUrl = new URL(url.pathname, 'https://telegra.ph'); // telegraph接口,已失效,缅怀
|
||||
const targetUrl = new URL(`https://api.telegram.org/bot${env.TG_BOT_TOKEN}/${sendFunction.url}`); // telegram接口
|
||||
let newFormdata = new FormData();
|
||||
newFormdata.append('chat_id', env.TG_CHAT_ID);
|
||||
newFormdata.append(sendFunction.type, formdata.get('file'));
|
||||
// 获得上传IP
|
||||
const uploadIp = request.headers.get("cf-connecting-ip") || request.headers.get("x-real-ip") || request.headers.get("x-forwarded-for") || request.headers.get("x-client-ip") || request.headers.get("x-host") || request.headers.get("x-originating-ip") || request.headers.get("x-cluster-client-ip") || request.headers.get("forwarded-for") || request.headers.get("forwarded") || request.headers.get("via") || request.headers.get("requester") || request.headers.get("true-client-ip") || request.headers.get("client-ip") || request.headers.get("x-remote-ip") || request.headers.get("x-originating-ip") || request.headers.get("fastly-client-ip") || request.headers.get("akamai-origin-hop") || request.headers.get("x-remote-ip") || request.headers.get("x-remote-addr") || request.headers.get("x-remote-host") || request.headers.get("x-client-ip") || request.headers.get("x-client-ips") || request.headers.get("x-client-ip")
|
||||
// 获得上传渠道
|
||||
const urlParamUploadChannel = url.searchParams.get('uploadChannel');
|
||||
let uploadChannel = 'TelegramNew';
|
||||
switch (urlParamUploadChannel) {
|
||||
case 'telegram':
|
||||
uploadChannel = 'TelegramNew';
|
||||
break;
|
||||
case 'cfr2':
|
||||
uploadChannel = 'CloudflareR2';
|
||||
break;
|
||||
default:
|
||||
uploadChannel = 'TelegramNew';
|
||||
break;
|
||||
}
|
||||
|
||||
await errorHandling(context);
|
||||
telemetryData(context);
|
||||
|
||||
url.searchParams.forEach((value, key) => {
|
||||
if (key !== 'authCode') {
|
||||
targetUrl.searchParams.append(key, value);
|
||||
if (typeof env.img_url == "undefined" || env.img_url == null || env.img_url == "") {
|
||||
// img_url 未定义或为空的处理逻辑
|
||||
return new Response('Error: Please configure KV database', { status: 500 });
|
||||
}
|
||||
|
||||
const formdata = await clonedRequest.formData();
|
||||
const fileType = formdata.get('file').type;
|
||||
const fileName = formdata.get('file').name;
|
||||
let fileExt = fileName.split('.').pop(); // 文件扩展名
|
||||
if (!isExtValid(fileExt)) {
|
||||
// 如果文件名中没有扩展名,尝试从文件类型中获取
|
||||
fileExt = fileType.split('/').pop();
|
||||
if (!isExtValid(fileExt)) {
|
||||
// Type中无法获取扩展名
|
||||
fileExt = 'unknown' // 默认扩展名
|
||||
}
|
||||
});
|
||||
// 复制请求头并剔除 authCode
|
||||
const headers = new Headers(clonedRequest.headers);
|
||||
headers.delete('authCode');
|
||||
}
|
||||
|
||||
let res = new Response('upload error, check your environment params!', { status: 400 });
|
||||
try {
|
||||
const response = await fetch(targetUrl.href, {
|
||||
method: clonedRequest.method,
|
||||
headers: {
|
||||
"User-Agent": " Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0"
|
||||
},
|
||||
body: newFormdata,
|
||||
});
|
||||
const clonedRes = await response.clone().json(); // 等待响应克隆和解析完成
|
||||
const fileInfo = getFile(clonedRes);
|
||||
const filePath = await getFilePath(env, fileInfo.file_id);
|
||||
// CloudFlare R2 渠道
|
||||
if (uploadChannel === 'CloudflareR2') {
|
||||
// 检查R2数据库是否配置
|
||||
if (typeof env.img_r2 == "undefined" || env.img_r2 == null || env.img_r2 == "") {
|
||||
return new Response('Error: Please configure R2 database', { status: 500 });
|
||||
}
|
||||
|
||||
const R2DataBase = env.img_r2;
|
||||
|
||||
// 构建文件ID
|
||||
const time = new Date().getTime();
|
||||
const id = fileInfo.file_id;
|
||||
//const fullId = id + '.' + fileExt;
|
||||
// 构建独一无二的 ID
|
||||
const unique_index = time + Math.floor(Math.random() * 10000);
|
||||
const fullId = fileName? unique_index + '_' + fileName : unique_index + '.' + fileExt;
|
||||
// 若上传成功,将响应返回给客户端
|
||||
if (response.ok) {
|
||||
res = new Response(
|
||||
JSON.stringify([{ 'src': `/file/${fullId}` }]),
|
||||
{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 写入R2数据库
|
||||
await R2DataBase.put(fullId, formdata.get('file'));
|
||||
|
||||
// 图像审查
|
||||
const apikey = env.ModerateContentApiKey;
|
||||
|
||||
if (apikey == undefined || apikey == null || apikey == "") {
|
||||
await env.img_url.put(fullId, "", {
|
||||
metadata: { FileName: fileName, FileType: fileType, ListType: "None", Label: "None", TimeStamp: time, Channel: "TelegramNew", TgFileId: id, UploadIP: uploadIp },
|
||||
metadata: { FileName: fileName, FileType: fileType, ListType: "None", Label: "None", TimeStamp: time, Channel: "CloudflareR2", UploadIP: uploadIp },
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
const fetchResponse = await fetch(`https://api.moderatecontent.com/moderate/?key=${apikey}&url=https://api.telegram.org/file/bot${env.TG_BOT_TOKEN}/${filePath}`);
|
||||
// 检查R2公网链接是否配置
|
||||
if (typeof env.R2PublicUrl == "undefined" || env.R2PublicUrl == null || env.R2PublicUrl == "") {
|
||||
return new Response('Error: Please configure R2 public url', { status: 500 });
|
||||
}
|
||||
const R2PublicUrl = env.R2PublicUrl;
|
||||
const fetchResponse = await fetch(`https://api.moderatecontent.com/moderate/?key=${apikey}&url=${R2PublicUrl}/${fullId}`);
|
||||
if (!fetchResponse.ok) {
|
||||
throw new Error(`HTTP error! status: ${fetchResponse.status}`);
|
||||
}
|
||||
const moderate_data = await fetchResponse.json();
|
||||
await env.img_url.put(fullId, "", {
|
||||
metadata: { FileName: fileName, FileType: fileType, ListType: "None", Label: moderate_data.rating_label, TimeStamp: time, Channel: "TelegramNew", TgFileId: id, UploadIP: uploadIp },
|
||||
metadata: { FileName: fileName, FileType: fileType, ListType: "None", Label: moderate_data.rating_label, TimeStamp: time, Channel: "CloudflareR2", UploadIP: uploadIp },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Moderate Error:', error);
|
||||
@@ -189,10 +153,124 @@ export async function onRequestPost(context) { // Contents of context object
|
||||
console.log('Moderate Done');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
} finally {
|
||||
return res;
|
||||
|
||||
|
||||
// 成功上传,将文件ID返回给客户端
|
||||
return new Response(
|
||||
JSON.stringify([{ 'src': `/file/${fullId}` }]),
|
||||
{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// 由于TG会把gif后缀的文件转为视频,所以需要修改后缀名绕过限制
|
||||
if (fileExt === 'gif') {
|
||||
const newFileName = fileName.replace(/\.gif$/, '.jpeg');
|
||||
const newFile = new File([formdata.get('file')], newFileName, { type: fileType });
|
||||
formdata.set('file', newFile);
|
||||
} else if (fileExt === 'webp') {
|
||||
const newFileName = fileName.replace(/\.webp$/, '.jpeg');
|
||||
const newFile = new File([formdata.get('file')], newFileName, { type: fileType });
|
||||
formdata.set('file', newFile);
|
||||
}
|
||||
|
||||
const fileTypeMap = {
|
||||
'image/': {'url': 'sendPhoto', 'type': 'photo'},
|
||||
'video/': {'url': 'sendVideo', 'type': 'video'},
|
||||
'audio/': {'url': 'sendAudio', 'type': 'audio'},
|
||||
'application/pdf': {'url': 'sendDocument', 'type': 'document'},
|
||||
};
|
||||
|
||||
const defaultType = {'url': 'sendDocument', 'type': 'document'};
|
||||
|
||||
let sendFunction = Object.keys(fileTypeMap).find(key => fileType.startsWith(key))
|
||||
? fileTypeMap[Object.keys(fileTypeMap).find(key => fileType.startsWith(key))]
|
||||
: defaultType;
|
||||
|
||||
// GIF 特殊处理
|
||||
if (fileType === 'image/gif' || fileType === 'image/webp' || fileExt === 'gif' || fileExt === 'webp') {
|
||||
sendFunction = {'url': 'sendAnimation', 'type': 'animation'};
|
||||
}
|
||||
|
||||
// 从参数中获取serverCompress,如果为false,则使用sendDocument接口
|
||||
if (url.searchParams.get('serverCompress') === 'false') {
|
||||
sendFunction = {'url': 'sendDocument', 'type': 'document'};
|
||||
}
|
||||
|
||||
// 构建目标 URL 时剔除 authCode 参数
|
||||
// const targetUrl = new URL(url.pathname, 'https://telegra.ph'); // telegraph接口,已失效,缅怀
|
||||
const targetUrl = new URL(`https://api.telegram.org/bot${env.TG_BOT_TOKEN}/${sendFunction.url}`); // telegram接口
|
||||
let newFormdata = new FormData();
|
||||
newFormdata.append('chat_id', env.TG_CHAT_ID);
|
||||
newFormdata.append(sendFunction.type, formdata.get('file'));
|
||||
|
||||
|
||||
url.searchParams.forEach((value, key) => {
|
||||
if (key !== 'authCode') {
|
||||
targetUrl.searchParams.append(key, value);
|
||||
}
|
||||
});
|
||||
// 复制请求头并剔除 authCode
|
||||
const headers = new Headers(clonedRequest.headers);
|
||||
headers.delete('authCode');
|
||||
|
||||
let res = new Response('upload error, check your environment params about telegram channel!', { status: 400 });
|
||||
try {
|
||||
const response = await fetch(targetUrl.href, {
|
||||
method: clonedRequest.method,
|
||||
headers: {
|
||||
"User-Agent": " Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0"
|
||||
},
|
||||
body: newFormdata,
|
||||
});
|
||||
const clonedRes = await response.clone().json(); // 等待响应克隆和解析完成
|
||||
const fileInfo = getFile(clonedRes);
|
||||
const filePath = await getFilePath(env, fileInfo.file_id);
|
||||
|
||||
const time = new Date().getTime();
|
||||
const id = fileInfo.file_id;
|
||||
//const fullId = id + '.' + fileExt;
|
||||
// 构建独一无二的 ID
|
||||
const unique_index = time + Math.floor(Math.random() * 10000);
|
||||
const fullId = fileName? unique_index + '_' + fileName : unique_index + '.' + fileExt;
|
||||
// 若上传成功,将响应返回给客户端
|
||||
if (response.ok) {
|
||||
res = new Response(
|
||||
JSON.stringify([{ 'src': `/file/${fullId}` }]),
|
||||
{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}
|
||||
);
|
||||
}
|
||||
const apikey = env.ModerateContentApiKey;
|
||||
|
||||
if (apikey == undefined || apikey == null || apikey == "") {
|
||||
await env.img_url.put(fullId, "", {
|
||||
metadata: { FileName: fileName, FileType: fileType, ListType: "None", Label: "None", TimeStamp: time, Channel: "TelegramNew", TgFileId: id, UploadIP: uploadIp },
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
const fetchResponse = await fetch(`https://api.moderatecontent.com/moderate/?key=${apikey}&url=https://api.telegram.org/file/bot${env.TG_BOT_TOKEN}/${filePath}`);
|
||||
if (!fetchResponse.ok) {
|
||||
throw new Error(`HTTP error! status: ${fetchResponse.status}`);
|
||||
}
|
||||
const moderate_data = await fetchResponse.json();
|
||||
await env.img_url.put(fullId, "", {
|
||||
metadata: { FileName: fileName, FileType: fileType, ListType: "None", Label: moderate_data.rating_label, TimeStamp: time, Channel: "TelegramNew", TgFileId: id, UploadIP: uploadIp },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Moderate Error:', error);
|
||||
} finally {
|
||||
console.log('Moderate Done');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
} finally {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/logo.png"><title>Sanyue ImgHub</title><script defer="defer" src="/js/app.b73a7849.js"></script><link href="/css/app.def7ef9b.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but sanyue_imghub doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/logo.png"><title>Sanyue ImgHub</title><script defer="defer" src="/js/app.f13cc65e.js"></script><link href="/css/app.def7ef9b.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but sanyue_imghub doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Reference in New Issue
Block a user