Skip to content

Commit 2b806d7

Browse files
authored
Create worker.js
1 parent 927c139 commit 2b806d7

File tree

1 file changed

+381
-0
lines changed

1 file changed

+381
-0
lines changed

worker.js

+381
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
// 在代码开始的地方配置域名
2+
const domain = 'example.com';
3+
4+
// 监听 fetch 事件
5+
addEventListener('fetch', event => {
6+
event.respondWith(handleRequest(event.request));
7+
});
8+
9+
// 处理请求的函数
10+
async function handleRequest(request) {
11+
const { pathname } = new URL(request.url);
12+
13+
if (pathname === '/') {
14+
return handleRootRequest();
15+
} else if (pathname === '/upload' && request.method === 'POST') {
16+
return handleUploadRequest(request);
17+
} else {
18+
// 构建新的请求 URL
19+
const url = new URL(request.url);
20+
url.hostname = 'telegra.ph';
21+
22+
// 发起原始请求并返回响应
23+
return fetch(url, request);
24+
}
25+
}
26+
27+
// 处理根路径请求的函数
28+
function handleRootRequest() {
29+
const html =
30+
`
31+
<!DOCTYPE html>
32+
<html lang="zh-CN">
33+
<head>
34+
<meta charset="UTF-8">
35+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, viewport-fit=cover">
36+
<meta name="description" content="基于Cloudflare Workers的图床服务">
37+
<meta name="keywords" content="Workers图床, Cloudflare, Workers, JIASU.IN, 图床">
38+
<title>JIASU.IN-基于Workers的图床服务</title>
39+
<link rel="icon" href="https://p1.meituan.net/csc/c195ee91001e783f39f41ffffbbcbd484286.ico" type="image/x-icon">
40+
<!-- Twitter Bootstrap CSS -->
41+
<link href="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/twitter-bootstrap/4.6.1/css/bootstrap.min.css" type="text/css" rel="stylesheet" />
42+
<!-- Bootstrap FileInput CSS -->
43+
<link href="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/bootstrap-fileinput/5.2.7/css/fileinput.min.css" type="text/css" rel="stylesheet" />
44+
<!-- Toastr CSS -->
45+
<link href="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/toastr.js/2.1.4/toastr.min.css" type="text/css" rel="stylesheet" />
46+
<!-- jQuery -->
47+
<script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/3.6.0/jquery.min.js" type="application/javascript"></script>
48+
<!-- Bootstrap FileInput JS -->
49+
<script src="https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/bootstrap-fileinput/5.2.7/js/fileinput.min.js" type="application/javascript"></script>
50+
<!-- Bootstrap FileInput Chinese Locale JS -->
51+
<script src="https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/bootstrap-fileinput/5.2.7/js/locales/zh.min.js" type="application/javascript"></script>
52+
<!-- Toastr JS -->
53+
<script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/toastr.js/2.1.4/toastr.min.js" type="application/javascript"></script>
54+
<style>
55+
@import url('https://fonts.googleapis.com/css2?family=Long+Cang&display=swap');
56+
57+
.title {
58+
font-family: "Long Cang", cursive;
59+
font-weight: 400;
60+
font-style: normal;
61+
font-size: 2em; /* 调整字体大小 */
62+
text-align: center;
63+
margin-top: 20px; /* 调整距离顶部的距离 */
64+
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); /* 添加阴影效果 */
65+
}
66+
</style>
67+
</head>
68+
<body>
69+
<div class="card">
70+
<div class="title">JIASU.IN</div>
71+
<div class="card-body">
72+
<!-- 表单 -->
73+
<form id="uploadForm" action="/upload" method="post" enctype="multipart/form-data">
74+
<!-- 接口选择下拉菜单 -->
75+
<div class="form-group mb-3">
76+
<select class="custom-select" id="interfaceSelector" name="interface">
77+
<option value="tg">tg</option>
78+
</select>
79+
</div>
80+
<!-- 文件选择 -->
81+
<div class="form-group mb-3">
82+
<input id="fileInput" name="file" type="file" class="form-control-file" data-browse-on-zone-click="true">
83+
</div>
84+
<!-- 添加按钮组 -->
85+
<div class="form-group mb-3" style="display: none;"> <!-- 初始隐藏 -->
86+
<button type="button" class="btn btn-light mr-2" id="urlBtn">URL</button>
87+
<button type="button" class="btn btn-light mr-2" id="bbcodeBtn">BBCode</button>
88+
<button type="button" class="btn btn-light" id="markdownBtn">Markdown</button>
89+
</div>
90+
<!-- 文件链接文本框 -->
91+
<div class="form-group mb-3" style="display: none;"> <!-- 初始隐藏 -->
92+
<textarea class="form-control" id="fileLink" readonly></textarea>
93+
</div>
94+
<!-- 上传中的提示 -->
95+
<div id="uploadingText" style="display: none; text-align: center;">文件上传中...</div>
96+
</form>
97+
</div>
98+
<p style="font-size: 14px; text-align: center;">
99+
项目开源于 GitHub - <a href="https://github.com/0-RTT/telegraph" target="_blank" rel="noopener noreferrer">0-RTT/telegraph</a>
100+
</p>
101+
</div>
102+
103+
<script>
104+
$(document).ready(function () {
105+
let originalImageURL = '';
106+
107+
// 初始化文件上传
108+
initFileInput();
109+
110+
// 文件上传初始化函数
111+
function initFileInput() {
112+
$("#fileInput").fileinput({
113+
theme: 'fa',
114+
language: 'zh',
115+
dropZoneEnabled: true,
116+
browseOnZoneClick: true,
117+
dropZoneTitle: "拖拽文件到这里...",
118+
dropZoneClickTitle: "",
119+
browseClass: "btn btn-light",
120+
uploadClass: "btn btn-light",
121+
removeClass: "btn btn-light",
122+
showUpload: false,
123+
layoutTemplates: {
124+
actionZoom: '',
125+
},
126+
}).on('filebatchselected', handleFileSelection)
127+
.on('fileclear', handleFileClear); // 添加移除按钮点击事件处理程序
128+
}
129+
130+
// 处理文件选择事件
131+
async function handleFileSelection() {
132+
const selectedInterface = $('#interfaceSelector').val();
133+
const file = $('#fileInput')[0].files[0];
134+
135+
if ((selectedInterface === 'tg') && file && file.type === 'image/gif' && file.size > 5 * 1024 * 1024) {
136+
toastr.error('GIF 文件必须≤5MB');
137+
return;
138+
}
139+
140+
// 对于 GIF 文件,直接返回原始文件,不进行压缩处理
141+
if (file.type === 'image/gif') {
142+
originalImageURL = URL.createObjectURL(file);
143+
$('#fileLink').val(originalImageURL);
144+
$('.form-group').show();
145+
adjustTextareaHeight($('#fileLink')[0]);
146+
return;
147+
}
148+
149+
// 判断是否需要压缩
150+
const compressedFile = await compressImage(file);
151+
152+
// 文件选择后提交表单
153+
try {
154+
$('#uploadingText').show();
155+
const formData = new FormData($('#uploadForm')[0]);
156+
formData.set('file', compressedFile, compressedFile.name); // 替换原始文件为压缩后的文件
157+
const uploadResponse = await fetch('/upload', { method: 'POST', body: formData });
158+
originalImageURL = await handleUploadResponse(uploadResponse);
159+
$('#fileLink').val(originalImageURL);
160+
$('.form-group').show();
161+
162+
// 上传完成后调整文本框高度
163+
adjustTextareaHeight($('#fileLink')[0]);
164+
} catch (error) {
165+
console.error('上传文件时出现错误:', error);
166+
$('#fileLink').val('文件上传失败!');
167+
} finally {
168+
$('#uploadingText').hide();
169+
}
170+
}
171+
172+
// 处理上传响应
173+
async function handleUploadResponse(response) {
174+
if (response.ok) {
175+
const result = await response.json();
176+
return result.data;
177+
} else {
178+
return '文件上传失败!';
179+
}
180+
}
181+
182+
// 处理移除按钮点击事件
183+
function handleFileClear(event) {
184+
// 清空文本框的内容
185+
$('#fileLink').val('');
186+
187+
// 上传完成后调整文本框高度
188+
adjustTextareaHeight($('#fileLink')[0]);
189+
190+
// 隐藏按钮和文本框
191+
hideButtonsAndTextarea();
192+
}
193+
194+
// 根据接口选择器的值设置文件类型接受属性
195+
$('#interfaceSelector').change(function () {
196+
const selectedInterface = $(this).val();
197+
let acceptTypes = '';
198+
199+
switch (selectedInterface) {
200+
case 'tg':
201+
acceptTypes = 'image/gif,image/jpeg,image/png';
202+
break;
203+
}
204+
$('#fileInput').attr('accept', acceptTypes);
205+
}).trigger('change');
206+
207+
// 处理按钮点击事件
208+
$('#urlBtn, #bbcodeBtn, #markdownBtn').on('click', function () {
209+
const fileLink = originalImageURL.trim();
210+
if (fileLink !== '') {
211+
let formattedLink;
212+
switch ($(this).attr('id')) {
213+
case 'urlBtn':
214+
formattedLink = fileLink;
215+
break
216+
case 'urlBtn':
217+
formattedLink = fileLink;
218+
break;
219+
case 'bbcodeBtn':
220+
formattedLink = '[img]' + fileLink + '[/img]';
221+
break;
222+
case 'markdownBtn':
223+
formattedLink = '![image](' + fileLink + ')';
224+
break;
225+
default:
226+
formattedLink = fileLink;
227+
}
228+
$('#fileLink').val(formattedLink);
229+
adjustTextareaHeight($('#fileLink')[0]); // 调整文本框高度
230+
copyToClipboardWithToastr(formattedLink);
231+
}
232+
});
233+
234+
// 自动调整文本框高度函数
235+
function adjustTextareaHeight(textarea) {
236+
textarea.style.height = '1px'; // 先将高度设置为最小值
237+
textarea.style.height = (textarea.scrollHeight) + 'px'; // 根据内容重新设置高度
238+
}
239+
240+
// 复制文本到剪贴板,并显示 toastr 提示框
241+
function copyToClipboardWithToastr(text) {
242+
const input = document.createElement('input');
243+
input.setAttribute('value', text);
244+
document.body.appendChild(input);
245+
input.select();
246+
document.execCommand('copy');
247+
document.body.removeChild(input);
248+
249+
toastr.success('已复制到剪贴板', '', { timeOut: 300 });
250+
}
251+
252+
// 隐藏按钮和文本框
253+
function hideButtonsAndTextarea() {
254+
$('#urlBtn, #bbcodeBtn, #markdownBtn, #fileLink').parent('.form-group').hide();
255+
}
256+
257+
// 图片压缩函数
258+
function compressImage(file) {
259+
return new Promise((resolve) => {
260+
const quality = 0.6; // 设置压缩质量,你也可以根据需要修改
261+
const reader = new FileReader(); // 创建 FileReader
262+
reader.onload = ({ target: { result: src } }) => {
263+
const image = new Image(); // 创建 img 元素
264+
image.onload = async () => {
265+
const canvas = document.createElement('canvas'); // 创建 canvas 元素
266+
canvas.width = image.width; // 设置 canvas 宽度为图片宽度
267+
canvas.height = image.height; // 设置 canvas 高度为图片高度
268+
const ctx = canvas.getContext('2d'); // 获取 2D 上下文
269+
ctx.drawImage(image, 0, 0, image.width, image.height); // 在 canvas 上绘制图片
270+
271+
// 将图片压缩为 JPEG 格式
272+
const compressedDataURL = canvas.toDataURL('image/jpeg', quality);
273+
274+
// 将 base64 编码的图片数据转换为 Blob 对象
275+
const blob = await fetch(compressedDataURL).then(res => res.blob());
276+
277+
// 创建压缩后的 File 对象
278+
const compressedFile = new File([blob], file.name, { type: 'image/jpeg' });
279+
280+
resolve(compressedFile);
281+
};
282+
image.src = src; // 设置图片对象的 src 属性为文件的 base64 编码
283+
};
284+
reader.readAsDataURL(file); // 读取文件并将其作为 base64 编码传递给 onload 函数
285+
});
286+
}
287+
});
288+
</script>
289+
290+
</body>
291+
</html>
292+
`;
293+
294+
// 返回 HTML 内容,并设置响应头为 UTF-8 编码
295+
return new Response(html, { headers: { 'Content-Type': 'text/html;charset=UTF-8' } });
296+
}
297+
298+
// 接口配置对象,包含各平台的上传配置
299+
const interfaceConfigs = {
300+
tg: {
301+
uploadURL: 'https://telegra.ph/upload',
302+
// 准备表单数据的函数
303+
prepareFormData: async function(file, formData) {
304+
// 在这里设置上传请求头部信息
305+
const uploadHeaders = {
306+
// 在这里添加需要的请求头部信息,如果没有则留空
307+
};
308+
309+
return {
310+
url: this.uploadURL, // 使用对象属性的上传 URL
311+
headers: uploadHeaders,
312+
body: formData
313+
};
314+
}
315+
}
316+
};
317+
// 处理上传请求的函数
318+
async function handleUploadRequest(request) {
319+
try {
320+
// 从请求中提取表单数据
321+
const formData = await request.formData();
322+
const selectedInterface = formData.get('interface');
323+
const file = formData.get('file');
324+
325+
// 检查是否存在接口和文件
326+
if (!selectedInterface || !file) {
327+
throw new Error('Missing interface or file');
328+
}
329+
330+
// 获取接口配置
331+
const config = interfaceConfigs[selectedInterface];
332+
if (!config) {
333+
throw new Error('Interface configuration not found');
334+
}
335+
336+
// 准备上传表单数据
337+
const preparedFormData = await config.prepareFormData(file, formData);
338+
// 发送上传请求
339+
const response = await fetch(preparedFormData.url, {
340+
method: 'POST',
341+
headers: preparedFormData.headers,
342+
body: preparedFormData.body
343+
});
344+
345+
// 检查响应状态
346+
if (!response.ok) {
347+
throw new Error('Upload Failed');
348+
}
349+
350+
// 根据接口获取响应数据
351+
const responseData = await response.json();
352+
// 获取图片链接
353+
const imageURL = getImageURL(selectedInterface, responseData);
354+
355+
// 返回图片链接作为响应
356+
const jsonResponse = { data: imageURL };
357+
return new Response(JSON.stringify(jsonResponse), {
358+
status: 200,
359+
headers: { 'Content-Type': 'application/json' }
360+
});
361+
} catch (error) {
362+
console.error('Internal Server Error:', error);
363+
return new Response(JSON.stringify({ error: 'Internal Server Error' }), { status: 500, headers: { 'Content-Type': 'application/json' } });
364+
}
365+
}
366+
367+
// 获取图片链接的函数
368+
function getImageURL(selectedInterface, responseData) {
369+
let url;
370+
371+
// 根据不同接口解析图片链接
372+
switch (selectedInterface) {
373+
case 'tg':
374+
url = `https://${domain}${responseData[0].src}`;
375+
break;
376+
default:
377+
throw new Error('Unexpected response format');
378+
}
379+
380+
return url;
381+
}

0 commit comments

Comments
 (0)