Skip to content

Commit

Permalink
支持CloudflareR2存储
Browse files Browse the repository at this point in the history
  • Loading branch information
MarSeventh committed Nov 5, 2024
1 parent 4e13283 commit a149d88
Show file tree
Hide file tree
Showing 23 changed files with 326 additions and 173 deletions.
59 changes: 52 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -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/)

Expand Down Expand Up @@ -32,7 +32,7 @@

免费图片托管解决方案(支持存储绝大多数常见格式的**图片、视频、动图**等),具有**后台管理、图片审查****登录鉴权****页面自定义****多种方式及多文件上传****多文件及多格式链接复制**等功能(详见[第2章](#2.Features))。

此外,拖拽上传的方式**并没有严格限制文件类型**,理论上你可以上传**任何**不超过20MB的文件,但是暂时不会针对图片和视频外的文件进行特殊优化和适配。
此外,拖拽上传的方式**并没有严格限制文件类型**,理论上你可以上传**任何**文件,但是暂时不会针对图片和视频外的文件进行特殊优化和适配。

![CloudFlare](https://alist.sanyue.site/d/imgbed/202410011443570.png)

Expand All @@ -48,12 +48,27 @@
- **人性化上传**

- 支持绝大多数常见**图片、视频、动图**

- 支持 **Telegram Bot**, **Cloudflare R2** 等多种存储渠道一键切换

> Telegram Bot渠道:上传文件大小限制为20MB,提供客户端和服务端压缩功能
>
> Cloudflare R2渠道:上传大小不限,但超过免费额度会扣费,详见[Pricing | Cloudflare R2 docs](https://developers.cloudflare.com/r2/pricing/)
>
> ![](https://alist.sanyue.site/d/imgbed/202411052346701.png)
- 支持多种上传方式(**拖拽点击、粘贴**

- 粘贴上传支持**文件****URL**

- 支持批量上传(不限同时选择文件数量,但为了保证稳定性,同时处于上传状态的文件最多为10个)

- 上传显示实时上传进度

- **上传后图片无需手动点击,可直接展示在管理页面中**

- **过大图片在前端进行压缩,提升上传稳定性和加载性能**

- 支持自定义压缩质量,自定义开启前后端压缩功能

- **多样化复制**
Expand Down Expand Up @@ -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`

Expand All @@ -118,6 +133,20 @@

![](https://alist.sanyue.site/d/imgbed/202409071751619.png)

- 开通**Cloudflare R2渠道**必须:新建一个Cloudflare R2存储桶,前提是需要绑定支付方式。

1. 前往Cloudflare Dashboard,选择`R2 存储对象`

![](https://alist.sanyue.site/d/imgbed/202411052318204.png)

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

![](https://alist.sanyue.site/d/imgbed/202411052319402.png)

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

![image-20241105232759131](https://alist.sanyue.site/d/imgbed/202411052327191.png)

- **部署于Cloudflare**

需准备一个**Cloudflare账户**,然后按照[3.1.2.1节](#3.1.2.1部署于Cloudflare)的步骤即可完成部署。
Expand All @@ -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`

![](https://alist.sanyue.site/d/imgbed/202411052323183.png)

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

![](https://alist.sanyue.site/d/imgbed/202411052330663.png)

3. **绑定KV数据库**

Expand Down Expand Up @@ -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`为获得的图片链接(注意不包含域名,需要自己添加) |
> **请求示例**:
Expand Down Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion css/201.7bac494a.css

This file was deleted.

Binary file removed css/201.7bac494a.css.gz
Binary file not shown.
1 change: 1 addition & 0 deletions css/727.c97f0290.css

Large diffs are not rendered by default.

Binary file added css/727.c97f0290.css.gz
Binary file not shown.
110 changes: 70 additions & 40 deletions functions/file/[id].js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -94,53 +123,54 @@ 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;
}
return await returnWithCheck(newRes, request, env, params, url);
}
return newRes;
} catch (error) {
return new Response('Error: ' + error, { status: 500 });
}
}

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) {
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 {
//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 });
}
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;
}
} 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;
}
}
return newRes;
} catch (error) {
return new Response('Error: ' + error, { status: 500 });
}
}

async function getFileContent(request, max_retries = 2) {
let retries = 0;
while (retries <= max_retries) {
Expand Down
Loading

0 comments on commit a149d88

Please sign in to comment.