guanghulab/github-bridge.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

210 lines
8.0 KiB
JavaScript
Raw Permalink 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.

// GitHub桥接模块·github-bridge.js·v1.0
// HoloLake·M-DINGTALK Phase8
// DEV-004 之之×秋秋
//
// 功能钉钉命令→GitHubAPI→结果回传钉钉
// 支持查PR·查Issue·查仓库状态·触发部署
//
var axios = require('axios');
var GITHUB_TOKEN = process.env.GITHUB_TOKEN || '';
var GITHUB_OWNER = process.env.GITHUB_OWNER || 'qinfendebingshuo';
var GITHUB_REPO = process.env.GITHUB_REPO || 'guanghulab';
var GITHUB_API = 'https://api.github.com';
// ===== GitHub API 请求封装 =====
async function githubRequest(endpoint, method, data) {
method = method || 'GET';
var url = GITHUB_API + endpoint;
try {
var config = {
method: method,
url: url,
headers: {
'Authorization': GITHUB_TOKEN ? ('token ' + GITHUB_TOKEN) : undefined,
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'HoloLake-DingTalk-Bot'
},
timeout: 15000
};
if (data) config.data = data;
var res = await axios(config);
return { ok: true, data: res.data, status: res.status };
} catch (err) {
var status = err.response ? err.response.status : 0;
var msg = err.response ? (err.response.data.message || err.message) : err.message;
console.error('[GitHub] API错误: ' + status + ' ' + msg);
return { ok: false, error: msg, status: status };
}
}
// ====== /github status · 仓库状态 =====
async function getRepoStatus() {
var res = await githubRequest('/repos/' + GITHUB_OWNER + '/' + GITHUB_REPO);
if (!res.ok) return '❌ 获取仓库状态失败: ' + res.error;
var repo = res.data;
// 获取最近提交
var commitsRes = await githubRequest('/repos/' + GITHUB_OWNER + '/' + GITHUB_REPO + '/commits?per_page=3');
var commitsText = '';
if (commitsRes.ok && commitsRes.data.length > 0) {
commitsText = '\n\n**最近提交:**\n';
commitsRes.data.forEach(function(c, i) {
var date = new Date(c.commit.author.date).toLocaleString('zh-CN');
var msg = (c.commit.message || '').split('\n')[0].substring(0, 60);
commitsText += (i + 1) + '. `' + msg + '` · ' + date + '\n';
});
}
return '### 📊 仓库状态\n\n'
+ '- **仓库**: ' + repo.full_name + '\n'
+ '- **默认分支**: ' + repo.default_branch + '\n'
+ '- **Star**: ' + repo.stargazers_count + ' · **Fork**: ' + repo.forks_count + '\n'
+ '- **最后更新**: ' + new Date(repo.updated_at).toLocaleString('zh-CN') + '\n'
+ '- **大小**: ' + Math.round(repo.size / 1024) + ' MB'
+ commitsText;
}
// ====== /github pr · 查看PR ======
async function listPRs(state) {
state = state || 'open';
var res = await githubRequest('/repos/' + GITHUB_OWNER + '/' + GITHUB_REPO + '/pulls?state=' + state + '&per_page=10');
if (!res.ok) return '❌ 获取PR列表失败: ' + res.error;
var prs = res.data;
if (prs.length === 0) return '### 📋 Pull Requests\n\n当前没有' + (state === 'open' ? '打开的' : '') + 'PR。';
var text = '### 📋 Pull Requests' + state + '\n\n';
prs.forEach(function(pr) {
var date = new Date(pr.created_at).toLocaleString('zh-CN');
text += '- **#' + pr.number + '** ' + pr.title + '\n';
text += ' 👤 ' + pr.user.login + ' · 📅 ' + date + '\n';
});
return text;
}
// ====== /github issue · 查看Issue ======
async function listIssues(state) {
state = state || 'open';
var res = await githubRequest('/repos/' + GITHUB_OWNER + '/' + GITHUB_REPO + '/issues?state=' + state + '&per_page=10');
if (!res.ok) return '❌ 获取Issue列表失败: ' + res.error;
var issues = res.data.filter(function(i) { return !i.pull_request; }); // 排除PR
if (issues.length === 0) return '### 📋 Issues\n\n当前没有' + (state === 'open' ? '打开的' : '') + 'Issue。';
var text = '### 📋 Issues' + state + '\n\n';
issues.forEach(function(issue) {
var labels = issue.labels.map(function(l) { return l.name; }).join(', ');
text += '- **#' + issue.number + '** ' + issue.title;
if (labels) text += ' `' + labels + '`';
text += '\n';
});
return text;
}
// ====== /github actions · 查看最近的Actions运行 ======
async function listActions() {
var res = await githubRequest('/repos/' + GITHUB_OWNER + '/' + GITHUB_REPO + '/actions/runs?per_page=5');
if (!res.ok) return '❌ 获取Actions状态失败: ' + res.error;
var runs = res.data.workflow_runs || [];
if (runs.length === 0) return '### GitHub Actions\n\n暂无运行记录。';
var text = '### GitHub Actions最近5次\n\n';
runs.forEach(function(run) {
var status = run.conclusion || run.status;
var icon = status === 'success' ? '✅' : status === 'failure' ? '❌' : '⏳';
var date = new Date(run.created_at).toLocaleString('zh-CN');
text += '- ' + icon + ' **' + run.name + '** ' + status + ' · ' + date + '\n';
});
return text;
}
// ====== /github deploy · 触发部署 (dispatch事件) =====
async function triggerDeploy(environment) {
environment = environment || 'production';
var res = await githubRequest(
'/repos/' + GITHUB_OWNER + '/' + GITHUB_REPO + '/dispatches',
'POST',
{
event_type: 'deploy',
client_payload: {
environment: environment,
triggered_by: 'dingtalk-bot',
timestamp: new Date().toISOString()
}
}
);
if (res.status === 204 || res.ok) {
return '### 🚀 部署触发成功\n\n'
+ '- **环境**: ' + environment + '\n'
+ '- **触发时间**: ' + new Date().toLocaleString('zh-CN') + '\n'
+ '- **来源**: 钉钉机器人\n\n'
+ '> 请到 GitHub Actions 页面查看部署进度。';
}
return '❌ 触发部署失败: ' + (res.error || '未知错误') + '\n> 请确认仓库已配置 repository_dispatch 工作流。';
}
// ====== 命令路由 =====
async function handleGitHubCommand(args) {
var subCommand = (args[0] || 'help').toLowerCase();
switch (subCommand) {
case 'status':
case '状态':
return await getRepoStatus();
case 'pr':
case 'prs':
return await listPRs(args[1] || 'open');
case 'issue':
case 'issues':
return await listIssues(args[1] || 'open');
case 'actions':
case 'ci':
return await listActions();
case 'deploy':
case '部署':
return await triggerDeploy(args[1] || 'production');
case 'help':
case '帮助':
default:
return '### GitHub 桥接命令\n\n'
+ '| 命令 | 说明 |\n'
+ '|------|------|\n'
+ '| `/github status` | 查看仓库状态+最近提交 |\n'
+ '| `/github pr` | 查看打开的PR |\n'
+ '| `/github pr closed` | 查看已关闭的PR |\n'
+ '| `/github issue` | 查看打开的Issue |\n'
+ '| `/github actions` | 查看最近CI/CD运行 |\n'
+ '| `/github deploy` | 触发生产部署 |\n'
+ '| `/github help` | 显示本帮助 |\n\n'
+ '> HoloLake · M-DINGTALK Phase 8 · 钉钉+GitHub桥接';
}
}
// ===== 健康检查 =====
async function healthCheck() {
var res = await githubRequest('/repos/' + GITHUB_OWNER + '/' + GITHUB_REPO);
return {
status: res.ok ? 'ok' : 'error',
repo: GITHUB_OWNER + '/' + GITHUB_REPO,
hasToken: !!GITHUB_TOKEN,
apiReachable: res.ok,
error: res.ok ? null : res.error
};
}
module.exports = {
handleGitHubCommand: handleGitHubCommand,
getRepoStatus: getRepoStatus,
listPRs: listPRs,
listIssues: listIssues,
listActions: listActions,
triggerDeploy: triggerDeploy,
healthCheck: healthCheck
};