Files
CloudFlare-ImgBed/functions/upload.js
2024-09-13 13:17:07 +08:00

254 lines
9.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { errorHandling, telemetryData } from "./utils/middleware";
function UnauthorizedException(reason) {
return new Response(reason, {
status: 401,
statusText: "Unauthorized",
headers: {
"Content-Type": "text/plain;charset=UTF-8",
// Disables caching by default.
"Cache-Control": "no-store",
// Returns the "Content-Length" header for HTTP HEAD requests.
"Content-Length": reason.length,
},
});
}
function isValidAuthCode(envAuthCode, authCode) {
return authCode === envAuthCode;
}
function isAuthCodeDefined(authCode) {
return authCode !== undefined && authCode !== null && authCode.trim() !== '';
}
function getCookieValue(cookies, name) {
const match = cookies.match(new RegExp('(^| )' + 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();
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'};
}
// 优先从请求 URL 获取 authCode
let authCode = url.searchParams.get('authCode');
// 如果 URL 中没有 authCode从 Referer 中获取
if (!authCode) {
const referer = request.headers.get('Referer');
if (referer) {
try {
const refererUrl = new URL(referer);
authCode = new URLSearchParams(refererUrl.search).get('authCode');
} catch (e) {
console.error('Invalid referer URL:', e);
}
}
}
// 如果 Referer 中没有 authCode从请求头中获取
if (!authCode) {
authCode = request.headers.get('authCode');
}
// 如果请求头中没有 authCode从 Cookie 中获取
if (!authCode) {
const cookies = request.headers.get('Cookie');
if (cookies) {
authCode = getCookieValue(cookies, 'authCode');
}
}
if (isAuthCodeDefined(env.AUTH_CODE) && !isValidAuthCode(env.AUTH_CODE, authCode)) {
return new UnauthorizedException("error");
}
// 构建目标 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!', { 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;
const encodedFullId = encodeURIComponent(fullId);
// 若上传成功,将响应返回给客户端
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(encodedFullId, "", {
metadata: { FileName: fileName, FileType: fileType, ListType: "None", Label: "None", TimeStamp: time, Channel: "TelegramNew", TgFilePath: filePath, TgFileId: id },
});
} 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(encodedFullId, "", {
metadata: { FileName: fileName, FileType: fileType, ListType: "None", Label: moderate_data.rating_label, TimeStamp: time, Channel: "TelegramNew", TgFilePath: filePath, TgFileId: id },
});
} catch (error) {
console.error('Moderate Error:', error);
} finally {
console.log('Moderate Done');
}
}
} catch (error) {
console.error('Error:', error);
} finally {
return res;
}
}
function getFile(response) {
try {
if (!response.ok) {
return null;
}
const getFileDetails = (file) => ({
file_id: file.file_id,
file_name: file.file_name || file.file_unique_id
});
if (response.result.photo) {
const largestPhoto = response.result.photo.reduce((prev, current) =>
(prev.file_size > current.file_size) ? prev : current
);
return getFileDetails(largestPhoto);
}
if (response.result.video) {
return getFileDetails(response.result.video);
}
if (response.result.document) {
return getFileDetails(response.result.document);
}
return null;
} catch (error) {
console.error('Error getting file id:', error.message);
return null;
}
}
async function getFilePath(env, file_id) {
try {
const url = `https://api.telegram.org/bot${env.TG_BOT_TOKEN}/getFile?file_id=${file_id}`;
const res = await fetch(url, {
method: 'GET',
headers: {
"User-Agent": " Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome"
},
})
let responseData = await res.json();
if (responseData.ok) {
const file_path = responseData.result.file_path
return file_path
} else {
return null;
}
} catch (error) {
return null;
}
}
function isExtValid(fileExt) {
return ['jpeg', 'jpg', 'png', 'gif', 'webp',
'mp4', 'mp3', 'ogg',
'mp3', 'wav', 'flac', 'aac', 'opus',
'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'pdf',
'txt', 'md', 'json', 'xml', 'html', 'css', 'js', 'ts', 'go', 'java', 'php', 'py', 'rb', 'sh', 'bat', 'cmd', 'ps1', 'psm1', 'psd', 'ai', 'sketch', 'fig', 'svg', 'eps', 'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', 'apk', 'exe', 'msi', 'dmg', 'iso', 'torrent', 'webp', 'ico', 'svg', 'ttf', 'otf', 'woff', 'woff2', 'eot', 'apk', 'crx', 'xpi', 'deb', 'rpm', 'jar', 'war', 'ear', 'img', 'iso', 'vdi', 'ova', 'ovf', 'qcow2', 'vmdk', 'vhd', 'vhdx', 'pvm', 'dsk', 'hdd', 'bin', 'cue', 'mds', 'mdf', 'nrg', 'ccd', 'cif', 'c2d', 'daa', 'b6t', 'b5t', 'bwt', 'isz', 'isz', 'cdi', 'flp', 'uif', 'xdi', 'sdi'
].includes(fileExt);
}