Logo
热心市民王先生

实施指南

技术研究 人工智能 Telegram

1. 在 Telegram 中搜索 2. 发送 命令 3. 按提示输入 Bot 名称(如 My Deploy Bot) 4. 输入 Bot 用户名(必须以 结尾,如 ) 5. 保存返回的 Bot Token(格式:)

前置条件

在开始实施之前,请确保已准备以下内容:

  1. Telegram 账号 - 用于创建和管理 Bot
  2. GitHub 账号 - 拥有目标仓库的管理权限
  3. Cloudflare 账号 - 免费注册,用于部署 Worker
  4. GitHub Personal Access Token - 具有 actions:write 权限

步骤 1:创建 Telegram Bot

1.1 通过 BotFather 创建 Bot

  1. 在 Telegram 中搜索 @BotFather
  2. 发送 /newbot 命令
  3. 按提示输入 Bot 名称(如 “My Deploy Bot”)
  4. 输入 Bot 用户名(必须以 bot 结尾,如 mydeploy_bot
  5. 保存返回的 Bot Token(格式:123456789:ABCdefGHIjklMNOpqrsTUVwxyz

1.2 配置 Slash Commands

发送 /setcommands 给 BotFather,然后输入:

deploy - 部署应用到指定环境
build - 触发构建工作流
status - 查看最近工作流状态
help - 显示帮助信息

这将启用 Telegram 的自动补全菜单,提升用户体验。

1.3 获取 Chat ID

需要获取允许使用命令的 Chat ID:

  1. 将 Bot 添加到目标群组或频道
  2. 群组中发送任意消息
  3. 访问:https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates
  4. 从 JSON 响应中找到 chat.id 字段

安全提示:记录 Chat ID 时,注意区分:

  • 个人聊天:正数(如 123456789
  • 群组:负数(如 -123456789
  • 超级群组/频道:以 -100 开头(如 -1001234567890

步骤 2:准备 GitHub 环境

2.1 创建 GitHub Token

  1. 访问 GitHub Settings → Developer settings → Personal access tokens → Fine-grained tokens
  2. 点击 “Generate new token”
  3. 设置 Token 名称(如 “Telegram Bot Integration”)
  4. 选择 “Only select repositories”,勾选目标仓库
  5. 权限设置:
    • Actions: Read and write
    • Contents: Read-only
  6. 点击生成并立即复制 Token(只显示一次)

2.2 创建 Actions 工作流

在目标仓库创建 .github/workflows/deploy.yml

name: Deploy via Telegram

on:
  workflow_dispatch:
    inputs:
      environment:
        description: '目标环境'
        required: true
        type: choice
        options:
          - staging
          - production
        default: 'staging'

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Setup environment
        run: |
          echo "Starting deployment to ${{ github.event.inputs.environment }}"
      
      - name: Run deployment
        run: |
          # 替换为你的部署脚本
          echo "Deploying..."
          sleep 5  # 模拟部署
          echo "Deployment complete!"
      
      - name: Notify Telegram
        if: always()
        uses: appleboy/telegram-action@master
        with:
          to: ${{ secrets.TELEGRAM_CHAT_ID }}
          token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
          format: markdown
          message: |
            📦 *Deployment ${{ job.status == 'success' && '✅ Succeeded' || '❌ Failed' }}*
            
            • Environment: `${{ github.event.inputs.environment }}`
            • Commit: `${{ github.sha }}`
            • Branch: `${{ github.ref_name }}`
            • Actor: ${{ github.actor }}
            
            [View Run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})

2.3 配置 GitHub Secrets

在仓库 Settings → Secrets and variables → Actions 中添加:

Secret NameValue
TELEGRAM_BOT_TOKEN步骤 1 获取的 Bot Token
TELEGRAM_CHAT_ID步骤 1 获取的 Chat ID

步骤 3:部署 Cloudflare Worker

3.1 安装 Wrangler CLI

npm install -g wrangler
wrangler login

3.2 创建 Worker 项目

mkdir telegram-github-webhook
cd telegram-github-webhook
wrangler init --yes

3.3 编写 Worker 代码

编辑 src/index.js

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    
    if (url.pathname === '/webhook' && request.method === 'POST') {
      return handleWebhook(request, env);
    }
    
    if (url.pathname === '/health') {
      return new Response('OK', { status: 200 });
    }
    
    return new Response('Not Found', { status: 404 });
  }
};

async function handleWebhook(request, env) {
  // 验证 Webhook Secret
  const secretToken = request.headers.get('X-Telegram-Bot-Api-Secret-Token');
  if (secretToken !== env.WEBHOOK_SECRET) {
    console.log('Invalid secret token');
    return new Response('Unauthorized', { status: 401 });
  }
  
  let update;
  try {
    update = await request.json();
  } catch (e) {
    return new Response('Invalid JSON', { status: 400 });
  }
  
  // 验证消息
  if (!update.message || !update.message.text) {
    return new Response('OK');
  }
  
  const text = update.message.text.trim();
  const chatId = update.message.chat.id;
  
  // 权限检查
  const allowedChats = env.ALLOWED_CHAT_IDS ? env.ALLOWED_CHAT_IDS.split(',') : [];
  if (allowedChats.length > 0 && !allowedChats.includes(chatId.toString())) {
    await sendMessage(env.BOT_TOKEN, chatId, '⛔ 未授权的用户');
    return new Response('OK');
  }
  
  // 解析命令
  if (!text.startsWith('/')) {
    return new Response('OK');
  }
  
  const parts = text.slice(1).split(' ');
  const command = parts[0];
  const args = parts.slice(1);
  
  // 路由命令
  switch (command) {
    case 'deploy':
      return handleDeploy(chatId, args, env);
    case 'build':
      return handleBuild(chatId, args, env);
    case 'status':
      return handleStatus(chatId, env);
    case 'help':
    case 'start':
      return handleHelp(chatId, env);
    default:
      await sendMessage(env.BOT_TOKEN, chatId, '❓ 未知命令,发送 /help 查看帮助');
      return new Response('OK');
  }
}

async function handleDeploy(chatId, args, env) {
  const environment = args[0] || 'staging';
  
  if (!['staging', 'production'].includes(environment)) {
    await sendMessage(env.BOT_TOKEN, chatId, '❌ 环境参数错误。可用选项:staging, production');
    return new Response('OK');
  }
  
  await sendMessage(env.BOT_TOKEN, chatId, `🚀 正在触发 ${environment} 环境部署...`);
  
  try {
    const response = await triggerGitHubWorkflow(env, 'deploy.yml', { environment });
    
    if (response.ok) {
      await sendMessage(env.BOT_TOKEN, chatId, `✅ 部署已触发!环境:*${environment}*\n稍后会收到完成通知。`);
    } else {
      const error = await response.text();
      await sendMessage(env.BOT_TOKEN, chatId, `❌ 触发失败:${error}`);
    }
  } catch (error) {
    console.error('Deploy error:', error);
    await sendMessage(env.BOT_TOKEN, chatId, `❌ 错误:${error.message}`);
  }
  
  return new Response('OK');
}

async function handleBuild(chatId, args, env) {
  await sendMessage(env.BOT_TOKEN, chatId, '🔨 正在触发构建...');
  
  try {
    const response = await triggerGitHubWorkflow(env, 'build.yml', {});
    
    if (response.ok) {
      await sendMessage(env.BOT_TOKEN, chatId, '✅ 构建已触发!');
    } else {
      const error = await response.text();
      await sendMessage(env.BOT_TOKEN, chatId, `❌ 触发失败:${error}`);
    }
  } catch (error) {
    console.error('Build error:', error);
    await sendMessage(env.BOT_TOKEN, chatId, `❌ 错误:${error.message}`);
  }
  
  return new Response('OK');
}

async function handleStatus(chatId, env) {
  await sendMessage(env.BOT_TOKEN, chatId, '请直接在 GitHub 查看工作流状态。');
  return new Response('OK');
}

async function handleHelp(chatId, env) {
  const helpText = `
🤖 *GitHub Actions Bot 帮助*

可用命令:
\/deploy [environment] - 部署应用
  示例:\/deploy staging
  示例:\/deploy production

\/build - 触发构建工作流

\/status - 查看状态(开发中)

\/help - 显示此帮助
  `;
  await sendMessage(env.BOT_TOKEN, chatId, helpText);
  return new Response('OK');
}

async function triggerGitHubWorkflow(env, workflowFile, inputs) {
  const url = `https://api.github.com/repos/${env.GITHUB_OWNER}/${env.GITHUB_REPO}/actions/workflows/${workflowFile}/dispatches`;
  
  return fetch(url, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${env.GITHUB_TOKEN}`,
      'Accept': 'application/vnd.github+json',
      'X-GitHub-Api-Version': '2022-11-28',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      ref: 'main',
      inputs: inputs
    })
  });
}

async function sendMessage(token, chatId, text) {
  try {
    await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        chat_id: chatId,
        text: text,
        parse_mode: 'Markdown'
      })
    });
  } catch (error) {
    console.error('Send message error:', error);
  }
}

3.4 配置环境变量

编辑 wrangler.toml

name = "telegram-github-webhook"
main = "src/index.js"
compatibility_date = "2024-01-01"

[vars]
GITHUB_OWNER = "your-github-username"
GITHUB_REPO = "your-repo-name"
ALLOWED_CHAT_IDS = "123456789,-123456789"  # 允许的 Chat ID,逗号分隔

# Secrets(通过 wrangler secret put 设置,不要写在文件中)
# BOT_TOKEN
# GITHUB_TOKEN
# WEBHOOK_SECRET

3.5 部署并设置 Secrets

# 部署 Worker
wrangler deploy

# 设置 Secrets(交互式输入,不会显示在命令历史中)
wrangler secret put BOT_TOKEN
# 输入:你的 Telegram Bot Token

wrangler secret put GITHUB_TOKEN
# 输入:你的 GitHub Fine-grained Token

wrangler secret put WEBHOOK_SECRET
# 输入:随机生成的密钥(至少 20 位字符)

3.6 记录 Worker URL

部署完成后,记录 Worker 的 URL:

https://telegram-github-webhook.your-subdomain.workers.dev

步骤 4:配置 Telegram Webhook

使用 curl 命令设置 Webhook:

curl -X POST "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/setWebhook" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://telegram-github-webhook.your-subdomain.workers.dev/webhook",
    "secret_token": "<YOUR_WEBHOOK_SECRET>",
    "max_connections": 40
  }'

验证 Webhook 状态:

curl "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getWebhookInfo"

步骤 5:测试验证

5.1 基础测试

  1. 在 Telegram 中向 Bot 发送 /help
  2. 应收到帮助信息

5.2 部署测试

  1. 发送 /deploy staging
  2. 应收到确认消息:“部署已触发”
  3. 前往 GitHub Actions 页面,确认工作流已启动
  4. 等待工作流完成,确认收到 Telegram 通知

5.3 错误场景测试

  • 发送未知命令:应提示帮助信息
  • 从非授权 Chat ID 发送:应提示未授权
  • 部署到不存在的环境:应提示参数错误

故障排查

问题 1:收不到消息

检查清单

  • Webhook URL 是否正确设置?
  • Worker URL 是否可访问?(访问 https://your-worker/health
  • Cloudflare Workers 日志是否有错误?(wrangler tail

问题 2:GitHub API 返回 404

可能原因

  • 仓库名称或所有者拼写错误
  • 工作流文件路径错误
  • GitHub Token 权限不足

问题 3:GitHub API 返回 422

可能原因

  • 工作流文件不存在于默认分支
  • inputs 参数与工作流定义不匹配

问题 4:Telegram 消息发送失败

检查点

  • Bot Token 是否正确
  • Chat ID 是否有效
  • 消息格式是否符合 Markdown 规范

安全加固建议

  1. 定期轮换 Token

    • GitHub Token:每 90 天更换一次
    • Telegram Bot Token:如泄露立即通过 BotFather 撤销
  2. 限制 IP 访问

    • 在 Worker 中添加 Telegram IP 白名单验证
    • Telegram Bot API 的 IP 范围:官方文档
  3. 审计日志

    • 启用 Cloudflare Workers Analytics
    • 记录所有命令执行日志

下一步扩展

  • 添加更多命令(如 /rollback/logs
  • 集成 Cloudflare KV 存储命令历史
  • 添加工作流状态轮询,主动推送结果
  • 支持多仓库管理

参考资料