Skip to content

Commit 3f490cd

Browse files
authored
ci: sync new issues to Feishu
Add a GitHub Actions workflow that sends newly opened GitHub issues to the configured Feishu bot webhook.
1 parent f72fc5e commit 3f490cd

1 file changed

Lines changed: 125 additions & 0 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
name: Sync GitHub issues to Feishu
2+
3+
on:
4+
issues:
5+
types: [opened]
6+
7+
permissions:
8+
contents: read
9+
issues: read
10+
11+
jobs:
12+
notify-feishu:
13+
name: Notify Feishu
14+
runs-on: ubuntu-latest
15+
timeout-minutes: 5
16+
if: ${{ !github.event.issue.pull_request }}
17+
env:
18+
FEISHU_BOT_WEBHOOK: ${{ secrets.FEISHU_BOT_WEBHOOK }}
19+
FEISHU_BOT_SECRET: ${{ secrets.FEISHU_BOT_SECRET }}
20+
steps:
21+
- name: Check Feishu webhook
22+
if: ${{ env.FEISHU_BOT_WEBHOOK == '' }}
23+
run: |
24+
echo "::error::Missing repository secret FEISHU_BOT_WEBHOOK"
25+
exit 1
26+
27+
- name: Build Feishu payload
28+
shell: bash
29+
run: |
30+
node <<'EOF'
31+
const crypto = require('crypto');
32+
const fs = require('fs');
33+
34+
const event = JSON.parse(
35+
fs.readFileSync(process.env.GITHUB_EVENT_PATH, 'utf8'),
36+
);
37+
38+
const issue = event.issue;
39+
const repository = event.repository;
40+
const repoName = repository.full_name;
41+
const labels = issue.labels.map((label) => label.name).join(', ') || 'none';
42+
const body = issue.body || '';
43+
const bodyPreview =
44+
body
45+
.replace(/\r\n/g, '\n')
46+
.replace(/\n{3,}/g, '\n\n')
47+
.slice(0, 1600) || '(empty)';
48+
49+
const payload = {
50+
msg_type: 'post',
51+
content: {
52+
post: {
53+
zh_cn: {
54+
title: `GitHub Issue #${issue.number}: ${issue.title}`,
55+
content: [
56+
[
57+
{
58+
tag: 'text',
59+
text: `Repository: ${repoName}\n`,
60+
},
61+
],
62+
[
63+
{
64+
tag: 'text',
65+
text: `Author: ${issue.user.login} | Labels: ${labels}\n`,
66+
},
67+
],
68+
[
69+
{
70+
tag: 'text',
71+
text: `Summary:\n${bodyPreview}\n`,
72+
},
73+
],
74+
[
75+
{
76+
tag: 'a',
77+
text: 'Open issue',
78+
href: issue.html_url,
79+
},
80+
],
81+
],
82+
},
83+
},
84+
},
85+
};
86+
87+
const secret = process.env.FEISHU_BOT_SECRET;
88+
if (secret) {
89+
const timestamp = Math.floor(Date.now() / 1000).toString();
90+
const stringToSign = `${timestamp}\n${secret}`;
91+
payload.timestamp = timestamp;
92+
payload.sign = crypto
93+
.createHmac('sha256', stringToSign)
94+
.update('')
95+
.digest('base64');
96+
}
97+
98+
fs.writeFileSync('feishu-payload.json', JSON.stringify(payload));
99+
EOF
100+
101+
- name: Send Feishu notification
102+
shell: bash
103+
run: |
104+
response="$(
105+
curl --fail-with-body --show-error --silent \
106+
--request POST \
107+
--header 'Content-Type: application/json' \
108+
--data-binary @feishu-payload.json \
109+
"$FEISHU_BOT_WEBHOOK"
110+
)"
111+
112+
RESPONSE="$response" node <<'EOF'
113+
const response = JSON.parse(process.env.RESPONSE);
114+
const success =
115+
response.code === 0 ||
116+
response.StatusCode === 0 ||
117+
response.StatusMessage === 'success';
118+
119+
if (!success) {
120+
console.error(JSON.stringify(response));
121+
process.exit(1);
122+
}
123+
124+
console.log('Feishu notification sent.');
125+
EOF

0 commit comments

Comments
 (0)