轻量级文件分享服务:基于 Telegram Bot 的轻图床应用

24 年 11 月 25 日 星期一
994 字
5 分钟

AI 摘要

奋力赶来...

分享文件使用网盘一直是一种标准解决方案,但实际使用时动不动就内容审核被删了、下载限速、登录后使用的体验还是不算友好。针对比较小的文件分享还是想要有一个比较持久的解决方案,考虑了 Cloudflare Workers + R2、GitHub Repositorie 等形式,最终决定使用 Telegram Bot 实现。

选择 Telegram Bot 主要是因为文件存储长久有效、上传过程就是文件发送,算是最简单快捷的处理方式了。但是劣势也很明显,文件大小受限、文件管理困难、受限于访问环境等。

架构与技术栈

  • 存储服务: Telegram
  • 运行环境:
    • Telegram Bot 作为文件上传入口接收分享链接返回信息。
    • Cloudflare Workers 处理 Bot 逻辑以及外链路由。
  • 编程语言: Javascript

采用 Worker Serverless 实现,业务逻辑分为上传、下载两部分:

  • 上传: 通过 Telegram Bot 发送文件,Cloudflare Workers 调用 Telegram API 生成外部使用的分享链接。
  • 下载: Cloudflare Workers 通过分享链接重定向到 Telegram API 文件下载链接实现文件下载。

部署步骤

  1. 在 Telegram 中的 BotFather 中创建 Bot,并获取 HTTP API Token。
  2. 在 Cloudflare Workers 中创建 Worker 并使用下方代码部署。
javascript
export default {
  async fetch(request, env) {
    const url = new URL(request.url)
    const origin = request.headers.get('origin') || `https://${url.hostname}`
    const MAX_FILE_SIZE = 0 //20 * 1024 * 1024; // 20MB

    // 初始化 Telegram Webhook
    if (url.pathname === '/setWebhook') {
      const webhookUrl = `${origin}/webhook`
      const telegramApiUrl = `https://api.telegram.org/bot${env.TELEGRAM_BOT_TOKEN}/setWebhook`

      const response = await fetch(telegramApiUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ url: webhookUrl }),
      })

      const result = await response.json()
      if (!result.ok) {
        return new Response(JSON.stringify({ error: 'Failed to set webhook', details: result }), {
          status: 500,
          headers: { 'Content-Type': 'application/json' },
        })
      }

      return new Response(JSON.stringify({ message: 'Webhook set successfully', webhookUrl }), {
        status: 200,
        headers: { 'Content-Type': 'application/json' },
      })
    }

    // 处理 Telegram Webhook 回调
    if (url.pathname === '/webhook' && request.method === 'POST') {
      const update = await request.json()

      const message = update.message
      if (message) {
        let fileId, fileType, fileSize

        if (message.document) {
          fileId = message.document.file_id
          fileType = 'document'
          fileSize = message.document.file_size
        } else if (message.photo) {
          const largestPhoto = message.photo[message.photo.length - 1]
          fileId = largestPhoto.file_id
          fileType = 'photo'
          fileSize = largestPhoto.file_size
        } else if (message.video) {
          fileId = message.video.file_id
          fileType = 'video'
          fileSize = message.video.file_size
        } else if (message.audio) {
          fileId = message.audio.file_id
          fileType = 'audio'
          fileSize = message.audio.file_size
        } else if (message.voice) {
          fileId = message.voice.file_id
          fileType = 'voice'
          fileSize = message.voice.file_size
        } else if (message.animation) {
          fileId = message.animation.file_id
          fileType = 'animation'
          fileSize = message.animation.file_size
        } else {
          return new Response('Unsupported file type', { status: 200 })
        }

        // 检查文件大小限制
        if (MAX_FILE_SIZE > 0 && fileSize > MAX_FILE_SIZE) {
          const telegramApiUrl = `https://api.telegram.org/bot${env.TELEGRAM_BOT_TOKEN}/sendMessage`
          const payload = {
            chat_id: message.chat.id,
            text: `The uploaded ${fileType} exceeds the ${MAX_FILE_SIZE / 1024 / 1024}MB limit. Please upload a smaller file.`,
          }

          await fetch(telegramApiUrl, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload),
          })

          return new Response('File too large', { status: 200 })
        }

        // 生成访问链接
        const accessLink = `${origin}/file?id=${encodeURIComponent(fileId)}`

        // 回复
        const telegramApiUrl = `https://api.telegram.org/bot${env.TELEGRAM_BOT_TOKEN}/sendMessage`
        const payload = {
          chat_id: message.chat.id,
          text: `Your ${fileType} has been uploaded successfully: ${accessLink}`,
        }

        await fetch(telegramApiUrl, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(payload),
        })

        return new Response('Webhook processed', { status: 200 })
      }

      return new Response('No file found in the update', { status: 200 })
    }

    // 文件访问处理
    if (url.pathname === '/file') {
      const fileId = url.searchParams.get('id')
      if (!fileId) {
        return new Response('Missing file ID', { status: 400 })
      }

      const fileInfoApi = `https://api.telegram.org/bot${env.TELEGRAM_BOT_TOKEN}/getFile?file_id=${fileId}`
      const fileInfoResponse = await fetch(fileInfoApi)
      const fileInfoResult = await fileInfoResponse.json()

      if (!fileInfoResult.ok) {
        return new Response('Failed to retrieve file info', { status: 500 })
      }

      const filePath = fileInfoResult.result.file_path
      const fileUrl = `https://api.telegram.org/file/bot${env.TELEGRAM_BOT_TOKEN}/${filePath}`
      return Response.redirect(fileUrl, 302)
    }

    // 其他未匹配的路由
    return new Response('Not Found', { status: 404 })
  },
}
  1. Cloudflare Workers 设置中绑定自定义域名,并将第一步得到的 Token 存储到 TELEGRAM_BOT_TOKEN 变量中。
Worker设置
  1. 访问 https://example.com/setWebhook 完成初始化,注意将域名换为第三步中绑定的自定义域名。
Set Telegram Webhook

至此,就可以尝试在 Bot 中发送文件获取外部访问链接。

Telegram Bot

实际体验

“理想很丰满,现实很骨感”

本想实现一个不受文件大小、数量限制的方案,但实际上,外部访问链接只支持 5 MB 之内的图片文件、20 MB 之内的其他文件。大大限制了可应用的范围,但是作为网站的图床或者分享其他小媒体资源分享应该是足够了。

文章标题:轻量级文件分享服务:基于 Telegram Bot 的轻图床应用

文章作者:Cedar

文章链接:https://some.fylsen.com/posts/telegram-bot-lightweight-image-hosting-service  [复制]

最后修改时间:


商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用CC BY-NC-SA 4.0进行许可。