Skip to content

[Feature]: 需要cli 版本 #36

[Feature]: 需要cli 版本

[Feature]: 需要cli 版本 #36

name: Sync GitHub issues to Feishu
on:
issues:
types: [opened]
permissions:
contents: read
issues: read
jobs:
notify-feishu:
name: Notify Feishu
runs-on: ubuntu-latest
timeout-minutes: 5
if: ${{ !github.event.issue.pull_request }}
env:
FEISHU_BOT_WEBHOOK: ${{ secrets.FEISHU_BOT_WEBHOOK }}
FEISHU_BOT_SECRET: ${{ secrets.FEISHU_BOT_SECRET }}
steps:
- name: Check Feishu webhook
if: ${{ env.FEISHU_BOT_WEBHOOK == '' }}
run: |
echo "::error::Missing repository secret FEISHU_BOT_WEBHOOK"
exit 1
- name: Build Feishu payload
shell: bash
run: |
node <<'EOF'
const crypto = require('crypto');
const fs = require('fs');
const event = JSON.parse(
fs.readFileSync(process.env.GITHUB_EVENT_PATH, 'utf8'),
);
const issue = event.issue;
const repository = event.repository;
const repoName = repository.full_name;
const normalizeText = (value) =>
String(value || '')
.replace(/\r\n/g, '\n')
.replace(/\n{3,}/g, '\n\n')
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, '')
.trim();
const truncate = (value, maxLength) =>
value.length > maxLength
? `${value.slice(0, maxLength - 1)}...`
: value;
const escapeLarkMarkdown = (value) =>
normalizeText(value)
.replace(/\\/g, '\\\\')
.replace(/`/g, '\\`')
.replace(/\*/g, '\\*')
.replace(/_/g, '\\_')
.replace(/\[/g, '\\[')
.replace(/\]/g, '\\]')
.replace(/\(/g, '\\(')
.replace(/\)/g, '\\)')
.replace(/~/g, '\\~')
.replace(/</g, '‹')
.replace(/>/g, '›');
const labelNames = issue.labels.map((label) => label.name);
const getIssueKind = (labels) => {
if (labels.includes('bug')) {
return 'bug';
}
if (labels.includes('enhancement')) {
return 'enhancement';
}
if (labels.includes('question')) {
return 'question';
}
return 'issue';
};
const issueKind = getIssueKind(labelNames);
const primaryLabel = labelNames[0] || issueKind;
const headerTemplateByKind = {
bug: 'red',
enhancement: 'green',
question: 'blue',
issue: 'blue',
};
const getLabelColor = (label) => ({
bug: 'red',
enhancement: 'green',
question: 'blue',
})[label] || 'neutral';
const headerTemplate = headerTemplateByKind[issueKind];
const labelTags =
labelNames
.map((label) => {
const color = getLabelColor(label);
return `<text_tag color='${color}'>${escapeLarkMarkdown(label)}</text_tag>`;
})
.join(' ') || "<font color='grey'>none</font>";
const body = issue.body || '';
const bodyPreview = truncate(escapeLarkMarkdown(body) || '(empty)', 1000);
const title = truncate(normalizeText(issue.title).replace(/\n/g, ' '), 100);
const createdAt = new Intl.DateTimeFormat('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false,
timeZone: 'Asia/Shanghai',
})
.format(new Date(issue.created_at))
.replace(/\//g, '-');
const payload = {
msg_type: 'interactive',
card: {
config: {
enable_forward: true,
update_multi: true,
},
card_link: {
url: issue.html_url,
},
header: {
template: headerTemplate,
title: {
tag: 'plain_text',
content: `GitHub Issue #${issue.number}: ${title}`,
},
},
elements: [
{
tag: 'markdown',
content: [
`**Repository:** [${repoName}](${repository.html_url})`,
`**Type:** ${escapeLarkMarkdown(primaryLabel)}`,
`**Author:** ${escapeLarkMarkdown(issue.user.login)}`,
`**Created:** ${createdAt} UTC+8`,
`**Labels:** ${labelTags}`,
].join('\n'),
},
{
tag: 'hr',
},
{
tag: 'markdown',
content: `**Summary**\n${bodyPreview}`,
},
{
tag: 'action',
actions: [
{
tag: 'button',
text: {
tag: 'plain_text',
content: 'Open issue',
},
type: 'primary',
url: issue.html_url,
},
],
},
],
},
};
const secret = process.env.FEISHU_BOT_SECRET;
if (secret) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const stringToSign = `${timestamp}\n${secret}`;
payload.timestamp = timestamp;
payload.sign = crypto
.createHmac('sha256', stringToSign)
.update('')
.digest('base64');
}
fs.writeFileSync('feishu-payload.json', JSON.stringify(payload));
EOF
- name: Send Feishu notification
shell: bash
run: |
response="$(
curl --fail-with-body --show-error --silent \
--request POST \
--header 'Content-Type: application/json' \
--data-binary @feishu-payload.json \
"$FEISHU_BOT_WEBHOOK"
)"
RESPONSE="$response" node <<'EOF'
const response = JSON.parse(process.env.RESPONSE);
const success =
response.code === 0 ||
response.StatusCode === 0 ||
response.StatusMessage === 'success';
if (!success) {
console.error(JSON.stringify(response));
process.exit(1);
}
console.log('Feishu notification sent.');
EOF