Feat:telegram渠道支持设置代理;优化上传设置页面使用体验

This commit is contained in:
MarSeventh
2026-01-07 17:44:33 +08:00
parent 03afed89f7
commit bdc94f80a2
82 changed files with 91 additions and 69 deletions

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

BIN
css/239.d71f401a.css.gz Normal file

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

BIN
css/417.8b7df435.css.gz Normal file

Binary file not shown.

Binary file not shown.

1
css/855.0de4ad0d.css Normal file

File diff suppressed because one or more lines are too long

BIN
css/855.0de4ad0d.css.gz Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

1
css/app.be4a11ae.css Normal file

File diff suppressed because one or more lines are too long

BIN
css/app.be4a11ae.css.gz Normal file

Binary file not shown.

View File

@@ -3,12 +3,12 @@ import { getDatabase } from '../../../utils/databaseAdapter.js';
export async function onRequest(context) { export async function onRequest(context) {
// 上传设置相关GET方法读取设置POST方法保存设置 // 上传设置相关GET方法读取设置POST方法保存设置
const { const {
request, // same as existing Worker API request, // same as existing Worker API
env, // same as existing Worker API env, // same as existing Worker API
params, // if filename includes [id] or [[path]] params, // if filename includes [id] or [[path]]
waitUntil, // same as ctx.waitUntil in existing Worker API waitUntil, // same as ctx.waitUntil in existing Worker API
next, // used for middleware or to fetch assets next, // used for middleware or to fetch assets
data, // arbitrary space for passing data between middlewares data, // arbitrary space for passing data between middlewares
} = context; } = context;
const db = getDatabase(env); const db = getDatabase(env);
@@ -60,6 +60,7 @@ export async function getUploadConfig(db, env) {
savePath: 'environment variable', savePath: 'environment variable',
botToken: env.TG_BOT_TOKEN, botToken: env.TG_BOT_TOKEN,
chatId: env.TG_CHAT_ID, chatId: env.TG_CHAT_ID,
proxyUrl: env.TG_PROXY_URL || '', // 可选的代理 URL
enabled: true, enabled: true,
fixed: true, fixed: true,
}) })
@@ -70,6 +71,7 @@ export async function getUploadConfig(db, env) {
// 如果环境变量未删除,进行覆盖操作 // 如果环境变量未删除,进行覆盖操作
if (telegramChannels[0]) { if (telegramChannels[0]) {
telegramChannels[0].enabled = tg.enabled telegramChannels[0].enabled = tg.enabled
telegramChannels[0].proxyUrl = tg.proxyUrl
} }
continue continue
@@ -85,7 +87,7 @@ export async function getUploadConfig(db, env) {
channels: [], channels: [],
} }
telegram.loadBalance = tgLoadBalance telegram.loadBalance = tgLoadBalance
// =====================读取r2渠道配置===================== // =====================读取r2渠道配置=====================
@@ -156,7 +158,7 @@ export async function getUploadConfig(db, env) {
s3Channels[0].enabled = s.enabled s3Channels[0].enabled = s.enabled
s3Channels[0].quota = s.quota // 保留容量限制配置 s3Channels[0].quota = s.quota // 保留容量限制配置
} }
continue continue
} }
// id自增 // id自增
@@ -176,7 +178,7 @@ export async function getUploadConfig(db, env) {
const discord = {} const discord = {}
const discordChannels = [] const discordChannels = []
discord.channels = discordChannels discord.channels = discordChannels
// 从环境变量读取 Discord 配置 // 从环境变量读取 Discord 配置
if (env.DISCORD_BOT_TOKEN) { if (env.DISCORD_BOT_TOKEN) {
discordChannels.push({ discordChannels.push({
@@ -192,7 +194,7 @@ export async function getUploadConfig(db, env) {
fixed: true, fixed: true,
}) })
} }
for (const dc of settingsKV.discord?.channels || []) { for (const dc of settingsKV.discord?.channels || []) {
// 如果 savePath 是 environment variable修改可变参数 // 如果 savePath 是 environment variable修改可变参数
if (dc.savePath === 'environment variable') { if (dc.savePath === 'environment variable') {
@@ -221,7 +223,7 @@ export async function getUploadConfig(db, env) {
const huggingface = {} const huggingface = {}
const huggingfaceChannels = [] const huggingfaceChannels = []
huggingface.channels = huggingfaceChannels huggingface.channels = huggingfaceChannels
// 从环境变量读取 HuggingFace 配置 // 从环境变量读取 HuggingFace 配置
if (env.HF_TOKEN) { if (env.HF_TOKEN) {
huggingfaceChannels.push({ huggingfaceChannels.push({
@@ -236,7 +238,7 @@ export async function getUploadConfig(db, env) {
fixed: true, fixed: true,
}) })
} }
for (const hf of settingsKV.huggingface?.channels || []) { for (const hf of settingsKV.huggingface?.channels || []) {
// 如果 savePath 是 environment variable修改可变参数 // 如果 savePath 是 environment variable修改可变参数
if (hf.savePath === 'environment variable') { if (hf.savePath === 'environment variable') {

View File

@@ -119,14 +119,17 @@ export async function onRequest(context) { // Contents of context object
} }
} }
// 获取TG图片真实地址 // 获取TG图片真实地址(支持代理域名)
const TgBotToken = imgRecord.metadata?.TgBotToken || env.TG_BOT_TOKEN; const TgBotToken = imgRecord.metadata?.TgBotToken || env.TG_BOT_TOKEN;
const tgApi = new TelegramAPI(TgBotToken); const TgProxyUrl = imgRecord.metadata?.TgProxyUrl || '';
const tgApi = new TelegramAPI(TgBotToken, TgProxyUrl);
const filePath = await tgApi.getFilePath(TgFileID); const filePath = await tgApi.getFilePath(TgFileID);
if (filePath === null) { if (filePath === null) {
return new Response('Error: Failed to fetch image path', { status: 500 }); return new Response('Error: Failed to fetch image path', { status: 500 });
} }
targetUrl = `https://api.telegram.org/file/bot${TgBotToken}/${filePath}`; // 使用代理域名或官方域名
const fileDomain = TgProxyUrl ? `https://${TgProxyUrl}` : 'https://api.telegram.org';
targetUrl = `${fileDomain}/file/bot${TgBotToken}/${filePath}`;
} else { } else {
targetUrl = 'https://telegra.ph/' + url.pathname + url.search; targetUrl = 'https://telegra.ph/' + url.pathname + url.search;
} }
@@ -162,6 +165,7 @@ async function handleTelegramChunkedFile(context, imgRecord, encodedFileName, fi
const metadata = imgRecord.metadata; const metadata = imgRecord.metadata;
const TgBotToken = metadata.TgBotToken || env.TG_BOT_TOKEN; const TgBotToken = metadata.TgBotToken || env.TG_BOT_TOKEN;
const TgProxyUrl = metadata.TgProxyUrl || '';
// 从KV的value中读取分片信息 // 从KV的value中读取分片信息
let chunks = []; let chunks = [];
@@ -258,8 +262,8 @@ async function handleTelegramChunkedFile(context, imgRecord, encodedFileName, fi
break; break;
} }
// 获取分片数据 // 获取分片数据(支持代理域名)
const chunkData = await fetchTelegramChunkWithRetry(TgBotToken, chunk, 3); const chunkData = await fetchTelegramChunkWithRetry(TgBotToken, chunk, TgProxyUrl, 3);
if (!chunkData) { if (!chunkData) {
throw new Error(`Failed to fetch chunk ${chunk.index} after retries`); throw new Error(`Failed to fetch chunk ${chunk.index} after retries`);
} }
@@ -308,11 +312,11 @@ async function handleTelegramChunkedFile(context, imgRecord, encodedFileName, fi
} }
} }
// 带重试机制的Telegram分片获取函数 // 带重试机制的Telegram分片获取函数(支持代理域名)
async function fetchTelegramChunkWithRetry(botToken, chunk, maxRetries = 3) { async function fetchTelegramChunkWithRetry(botToken, chunk, proxyUrl = '', maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) { for (let attempt = 0; attempt < maxRetries; attempt++) {
try { try {
const tgApi = new TelegramAPI(botToken); const tgApi = new TelegramAPI(botToken, proxyUrl);
const response = await tgApi.getFileContent(chunk.fileId); const response = await tgApi.getFileContent(chunk.fileId);

View File

@@ -432,6 +432,10 @@ async function mergeTelegramChunksInfo(context, uploadId, completedChunks, metad
metadata.ChannelName = tgChannel.name; metadata.ChannelName = tgChannel.name;
metadata.TgChatId = tgChatId; metadata.TgChatId = tgChatId;
metadata.TgBotToken = tgBotToken; metadata.TgBotToken = tgBotToken;
// 保存代理域名配置(如果有)
if (tgChannel.proxyUrl) {
metadata.TgProxyUrl = tgChannel.proxyUrl;
}
metadata.IsChunked = true; metadata.IsChunked = true;
metadata.TotalChunks = completedChunks.length; metadata.TotalChunks = completedChunks.length;
metadata.FileSize = (totalSize / 1024 / 1024).toFixed(2); metadata.FileSize = (totalSize / 1024 / 1024).toFixed(2);

View File

@@ -553,15 +553,17 @@ async function uploadSingleChunkToTelegram(context, chunkData, chunkIndex, total
const tgBotToken = tgChannel.botToken; const tgBotToken = tgChannel.botToken;
const tgChatId = tgChannel.chatId; const tgChatId = tgChannel.chatId;
const tgProxyUrl = tgChannel.proxyUrl || '';
// 创建分块文件名 // 创建分块文件名
const chunkFileName = `${originalFileName}.part${chunkIndex.toString().padStart(3, '0')}`; const chunkFileName = `${originalFileName}.part${chunkIndex.toString().padStart(3, '0')}`;
const chunkBlob = new Blob([chunkData], { type: 'application/octet-stream' }); const chunkBlob = new Blob([chunkData], { type: 'application/octet-stream' });
// 上传分块到Telegram // 上传分块到Telegram(支持代理域名)
const chunkInfo = await uploadChunkToTelegramWithRetry( const chunkInfo = await uploadChunkToTelegramWithRetry(
tgBotToken, tgBotToken,
tgChatId, tgChatId,
tgProxyUrl,
chunkBlob, chunkBlob,
chunkFileName, chunkFileName,
chunkIndex, chunkIndex,
@@ -1218,11 +1220,11 @@ export async function uploadLargeFileToTelegram(context, file, fullId, metadata,
} }
} }
// 将每个分块上传至Telegram支持失败重试 // 将每个分块上传至Telegram支持失败重试(支持代理域名)
async function uploadChunkToTelegramWithRetry(tgBotToken, tgChatId, chunkBlob, chunkFileName, chunkIndex, totalChunks, maxRetries = 2) { async function uploadChunkToTelegramWithRetry(tgBotToken, tgChatId, tgProxyUrl, chunkBlob, chunkFileName, chunkIndex, totalChunks, maxRetries = 2) {
for (let attempt = 0; attempt < maxRetries; attempt++) { for (let attempt = 0; attempt < maxRetries; attempt++) {
try { try {
const tgAPI = new TelegramAPI(tgBotToken); const tgAPI = new TelegramAPI(tgBotToken, tgProxyUrl);
const caption = `Part ${chunkIndex + 1}/${totalChunks}`; const caption = `Part ${chunkIndex + 1}/${totalChunks}`;

View File

@@ -412,10 +412,11 @@ async function uploadFileToTelegram(context, fullId, metadata, fileExt, fileName
const tgBotToken = tgChannel.botToken; const tgBotToken = tgChannel.botToken;
const tgChatId = tgChannel.chatId; const tgChatId = tgChannel.chatId;
const tgProxyUrl = tgChannel.proxyUrl || '';
const file = formdata.get('file'); const file = formdata.get('file');
const fileSize = file.size; const fileSize = file.size;
const telegramAPI = new TelegramAPI(tgBotToken); const telegramAPI = new TelegramAPI(tgBotToken, tgProxyUrl);
// 16MB 分片阈值 (TG Bot getFile download limit: 20MB, leave 4MB safety margin) // 16MB 分片阈值 (TG Bot getFile download limit: 20MB, leave 4MB safety margin)
const CHUNK_SIZE = 16 * 1024 * 1024; // 16MB const CHUNK_SIZE = 16 * 1024 * 1024; // 16MB
@@ -484,8 +485,9 @@ async function uploadFileToTelegram(context, fullId, metadata, fileExt, fileName
); );
// 图像审查 // 图像审查(使用代理域名或官方域名)
const moderateUrl = `https://api.telegram.org/file/bot${tgBotToken}/${filePath}`; const moderateDomain = tgProxyUrl ? `https://${tgProxyUrl}` : 'https://api.telegram.org';
const moderateUrl = `${moderateDomain}/file/bot${tgBotToken}/${filePath}`;
metadata.Label = await moderateContent(env, moderateUrl); metadata.Label = await moderateContent(env, moderateUrl);
// 更新metadata写入KV数据库 // 更新metadata写入KV数据库
@@ -496,6 +498,10 @@ async function uploadFileToTelegram(context, fullId, metadata, fileExt, fileName
metadata.TgFileId = id; metadata.TgFileId = id;
metadata.TgChatId = tgChatId; metadata.TgChatId = tgChatId;
metadata.TgBotToken = tgBotToken; metadata.TgBotToken = tgBotToken;
// 保存代理域名配置
if (tgProxyUrl) {
metadata.TgProxyUrl = tgProxyUrl;
}
await db.put(fullId, "", { await db.put(fullId, "", {
metadata: metadata, metadata: metadata,
}); });

View File

@@ -2,9 +2,13 @@
* Telegram API 封装类 * Telegram API 封装类
*/ */
export class TelegramAPI { export class TelegramAPI {
constructor(botToken) { constructor(botToken, proxyUrl = '') {
this.botToken = botToken; this.botToken = botToken;
this.baseURL = `https://api.telegram.org/bot${this.botToken}`; this.proxyUrl = proxyUrl;
// 如果设置了代理域名,使用代理域名,否则使用官方 API
const apiDomain = proxyUrl ? `https://${proxyUrl}` : 'https://api.telegram.org';
this.baseURL = `${apiDomain}/bot${this.botToken}`;
this.fileDomain = proxyUrl ? `https://${proxyUrl}` : 'https://api.telegram.org';
this.defaultHeaders = { this.defaultHeaders = {
"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" "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"
}; };
@@ -36,8 +40,8 @@ export class TelegramAPI {
headers: this.defaultHeaders, headers: this.defaultHeaders,
body: formData body: formData
}); });
console.log('Telegram API response:', response.status, response.statusText); console.log('Telegram API response:', response.status, response.statusText);
if (!response.ok) { if (!response.ok) {
throw new Error(`Telegram API error: ${response.statusText}`); throw new Error(`Telegram API error: ${response.statusText}`);
} }
@@ -54,10 +58,10 @@ export class TelegramAPI {
*/ */
getFileInfo(responseData) { getFileInfo(responseData) {
const getFileDetails = (file) => ({ const getFileDetails = (file) => ({
file_id: file.file_id, file_id: file.file_id,
file_name: file.file_name || file.file_unique_id, file_name: file.file_name || file.file_unique_id,
file_size: file.file_size, file_size: file.file_size,
}); });
try { try {
if (!responseData.ok) { if (!responseData.ok) {
@@ -127,7 +131,7 @@ export class TelegramAPI {
throw new Error(`File path not found for fileId: ${fileId}`); throw new Error(`File path not found for fileId: ${fileId}`);
} }
const fullURL = `https://api.telegram.org/file/bot${this.botToken}/${filePath}`; const fullURL = `${this.fileDomain}/file/bot${this.botToken}/${filePath}`;
const response = await fetch(fullURL, { const response = await fetch(fullURL, {
headers: this.defaultHeaders headers: this.defaultHeaders
}); });

View File

@@ -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"><link rel="apple-touch-icon" href="/logo.png"><link rel="mask-icon" href="/logo.png" color="#f4b400"><meta name="description" content="Sanyue ImgHub - A modern file hosting platform"><meta name="keywords" content="Sanyue, ImgHub, file hosting, image hosting, cloud storage"><meta name="author" content="SanyueQi"><title>Sanyue ImgHub</title><script defer="defer" src="/js/chunk-vendors.780b6559.js"></script><script defer="defer" src="/js/app.41abafbc.js"></script><link href="/css/chunk-vendors.4363ed49.css" rel="stylesheet"><link href="/css/app.14879ca1.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"><link rel="apple-touch-icon" href="/logo.png"><link rel="mask-icon" href="/logo.png" color="#f4b400"><meta name="description" content="Sanyue ImgHub - A modern file hosting platform"><meta name="keywords" content="Sanyue, ImgHub, file hosting, image hosting, cloud storage"><meta name="author" content="SanyueQi"><title>Sanyue ImgHub</title><script defer="defer" src="/js/chunk-vendors.780b6559.js"></script><script defer="defer" src="/js/app.633fc8d5.js"></script><link href="/css/chunk-vendors.4363ed49.css" rel="stylesheet"><link href="/css/app.be4a11ae.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.

2
js/163.477f8202.js Normal file

File diff suppressed because one or more lines are too long

BIN
js/163.477f8202.js.gz Normal file

Binary file not shown.

1
js/163.477f8202.js.map Normal file

File diff suppressed because one or more lines are too long

BIN
js/163.477f8202.js.map.gz Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

2
js/171.ce5e9b2d.js Normal file

File diff suppressed because one or more lines are too long

BIN
js/171.ce5e9b2d.js.gz Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
js/171.ce5e9b2d.js.map.gz Normal file

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.

2
js/226.725c32ec.js Normal file

File diff suppressed because one or more lines are too long

BIN
js/226.725c32ec.js.gz Normal file

Binary file not shown.

1
js/226.725c32ec.js.map Normal file

File diff suppressed because one or more lines are too long

BIN
js/226.725c32ec.js.map.gz Normal file

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

2
js/417.1472ac96.js Normal file

File diff suppressed because one or more lines are too long

BIN
js/417.1472ac96.js.gz Normal file

Binary file not shown.

1
js/417.1472ac96.js.map Normal file

File diff suppressed because one or more lines are too long

BIN
js/417.1472ac96.js.map.gz Normal file

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.

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.

2
js/845.e45a075a.js Normal file

File diff suppressed because one or more lines are too long

BIN
js/845.e45a075a.js.gz Normal file

Binary file not shown.

1
js/845.e45a075a.js.map Normal file

File diff suppressed because one or more lines are too long

BIN
js/845.e45a075a.js.map.gz Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
js/855.8e40903c.js.gz Normal file

Binary file not shown.

1
js/855.8e40903c.js.map Normal file

File diff suppressed because one or more lines are too long

BIN
js/855.8e40903c.js.map.gz Normal file

Binary file not shown.

2
js/917.49df25ec.js Normal file

File diff suppressed because one or more lines are too long

BIN
js/917.49df25ec.js.gz Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

BIN
js/917.49df25ec.js.map.gz Normal file

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

File diff suppressed because one or more lines are too long

BIN
js/app.633fc8d5.js.map.gz Normal file

Binary file not shown.