GitHub
We can do 2 types of integrations with GitHub:
  • Receive alerts from GitHub (Incoming WebHook)
  • Send commands to GitHub and optionally receive a response (Outgoing WebHook)

Receive alerts

  1. 1.
    Create a new Incoming WebHook.
  2. 2.
    Select the channel where you will receive the alerts. You may wish to create a dedicated channel for your notifications.
  3. 3.
    Select an account from which the alerts will be posted. You may wish to create a dedicated account just for notifications.
  4. 4.
    Set the "Enable Scripts" option to True.
  5. 5.
    Copy-paste one of the example scripts below.
  6. 6.
    Save the integration. This will generate a webhook URL and secret for you.
  7. 7.
    Go to your repository Settings -> WebHooks & services -> Add WebHook.
  8. 8.
    Paste your WebHook URL from Rocket.Chat into Payload URL.
  9. 9.
    Keep Content type as application/json.
  10. 10.
    Leave Secret empty and save.

Example script 1:

This script will generate notifications for the following repository events:
  • Issue events (create, edit, close, reopen, assign, label, etc)
  • Issue comment events
  • Push events (singular and multiple commits)
Screenshot of messages generated by GitHub integration script 1
1
/* exported Script */
2
3
String.prototype.capitalizeFirstLetter = function() {
4
return this.charAt(0).toUpperCase() + this.slice(1);
5
}
6
7
const getLabelsField = (labels) => {
8
let labelsArray = [];
9
labels.forEach(function(label) {
10
labelsArray.push(label.name);
11
});
12
labelsArray = labelsArray.join(', ');
13
return {
14
title: 'Labels',
15
value: labelsArray,
16
short: labelsArray.length <= 40
17
};
18
};
19
20
const githubEvents = {
21
ping(request) {
22
return {
23
content: {
24
text: '_' + request.content.hook.id + '_\n' + ':thumbsup: ' + request.content.zen
25
}
26
};
27
},
28
29
/* NEW OR MODIFY ISSUE */
30
issues(request) {
31
const user = request.content.sender;
32
33
if (request.content.action == "opened" || request.content.action == "reopened" || request.content.action == "edited") {
34
var body = request.content.issue.body;
35
} else if (request.content.action == "labeled") {
36
var body = "Current labels: " + getLabelsField(request.content.issue.labels).value;
37
} else if (request.content.action == "assigned" || request.content.action == "unassigned") {
38
// Note that the issues API only gives you one assignee.
39
var body = "Current assignee: " + request.content.issue.assignee.login;
40
} else if (request.content.action == "closed") {
41
if (request.content.issue.closed_by) {
42
var body = "Closed by: " + request.content.issue.closed_by.login;
43
} else {
44
var body = "Closed.";
45
}
46
} else {
47
return {
48
error: {
49
success: false,
50
message: 'Unsupported issue action'
51
}
52
};
53
}
54
55
const action = request.content.action.capitalizeFirstLetter();
56
57
const text = '_' + request.content.repository.full_name + '_\n' +
58
'**[' + action + ' issue ​#' + request.content.issue.number +
59
' - ' + request.content.issue.title + '](' +
60
request.content.issue.html_url + ')**\n\n' +
61
body;
62
63
return {
64
content: {
65
attachments: [
66
{
67
thumb_url: user.avatar_url,
68
text: text,
69
fields: []
70
}
71
]
72
}
73
};
74
},
75
76
/* COMMENT ON EXISTING ISSUE */
77
issue_comment(request) {
78
const user = request.content.comment.user;
79
80
if (request.content.action == "edited") {
81
var action = "Edited comment ";
82
} else {
83
var action = "Comment "
84
}
85
86
const text = '_' + request.content.repository.full_name + '_\n' +
87
'**[' + action + ' on issue ​#' + request.content.issue.number +
88
' - ' + request.content.issue.title + '](' +
89
request.content.comment.html_url + ')**\n\n' +
90
request.content.comment.body;
91
92
return {
93
content: {
94
attachments: [
95
{
96
thumb_url: user.avatar_url,
97
text: text,
98
fields: []
99
}
100
]
101
}
102
};
103
},
104
105
/* COMMENT ON COMMIT */
106
commit_comment(request) {
107
const user = request.content.comment.user;
108
109
if (request.content.action == "edited") {
110
var action = "Edited comment ";
111
} else {
112
var action = "Comment "
113
}
114
115
const text = '_' + request.content.repository.full_name + '_\n' +
116
'**[' + action + ' on commit id ' + request.content.comment.commit_id +
117
' - ' + + '](' +
118
request.content.comment.html_url + ')**\n\n' +
119
request.content.comment.body;
120
121
return {
122
content: {
123
attachments: [
124
{
125
thumb_url: user.avatar_url,
126
text: text,
127
fields: []
128
}
129
]
130
}
131
};
132
},
133
/* END OF COMMENT ON COMMIT */
134
135
/* PUSH TO REPO */
136
push(request) {
137
var commits = request.content.commits;
138
var multi_commit = ""
139
var is_short = true;
140
var changeset = 'Changeset';
141
if ( commits.length > 1 ) {
142
var multi_commit = " [Multiple Commits]";
143
var is_short = false;
144
var changeset = changeset + 's';
145
var output = [];
146
}
147
const user = request.content.sender;
148
149
var text = '**Pushed to ' + "["+request.content.repository.full_name+"]("+request.content.repository.url+"):"
150
+ request.content.ref.split('/').pop() + "**\n\n";
151
152
for (var i = 0; i < commits.length; i++) {
153
var commit = commits[i];
154
var shortID = commit.id.substring(0,7);
155
var a = '[' + shortID + '](' + commit.url + ') - ' + commit.message;
156
if ( commits.length > 1 ) {
157
output.push( a );
158
} else {
159
var output = a;
160
}
161
}
162
163
if (commits.length > 1) {
164
text += output.reverse().join('\n');
165
} else {
166
text += output;
167
}
168
169
return {
170
content: {
171
attachments: [
172
{
173
thumb_url: user.avatar_url,
174
text: text,
175
fields: []
176
}
177
]
178
}
179
};
180
}, // End GitHub Push
181
182
/* NEW PULL REQUEST */
183
pull_request(request) {
184
const user = request.content.sender;
185
186
if (request.content.action == "opened" || request.content.action == "reopened" || request.content.action == "edited" || request.content.action == "synchronize") {
187
var body = request.content.pull_request.body;
188
} else if (request.content.action == "labeled") {
189
var body = "Current labels: " + getLabelsField(request.content.pull_request.labels).value;
190
} else if (request.content.action == "assigned" || request.content.action == "unassigned") {
191
// Note that the issues API only gives you one assignee.
192
var body = "Current assignee: " + request.content.pull_request.assignee.login;
193
} else if (request.content.action == "closed") {
194
if (request.content.pull_request.merged) {
195
var body = "Merged by: " + request.content.pull_request.merged_by.login;
196
} else {
197
var body = "Closed.";
198
}
199
} else {
200
return {
201
error: {
202
success: false,
203
message: 'Unsupported pull request action'
204
}
205
};
206
}
207
208
const action = request.content.action.capitalizeFirstLetter();
209
210
const text = '_' + request.content.repository.full_name + '_\n' +
211
'**[' + action + ' pull request ​#' + request.content.pull_request.number +
212
' - ' + request.content.pull_request.title + '](' +
213
request.content.pull_request.html_url + ')**\n\n' +
214
body;
215
216
return {
217
content: {
218
attachments: [
219
{
220
thumb_url: user.avatar_url,
221
text: text,
222
fields: []
223
}
224
]
225
}
226
};
227
},
228
};
229
230
class Script {
231
process_incoming_request({ request }) {
232
const header = request.headers['x-github-event'];
233
if (githubEvents[header]) {
234
return githubEvents[header](request);
235
}
236
237
return {
238
error: {
239
success: false,
240
message: 'Unsupported method'
241
}
242
};
243
}
244
}
Copied!

Example script 2:

This script will generate notifications for the following repository events:
  • New and closed issue events
  • Comment events (issues only)
  • Push events (singular and multiple commits)
Screenshot of messages generated by GitHub integration script 2
1
/* exported Script */
2
3
// Begin embedded images
4
const gh_cmit_svg = '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="octicon octicon-git-commit" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M10.86 7c-.45-1.72-2-3-3.86-3-1.86 0-3.41 1.28-3.86 3H0v2h3.14c.45 1.72 2 3 3.86 3 1.86 0 3.41-1.28 3.86-3H14V7h-3.14zM7 10.2c-1.22 0-2.2-.98-2.2-2.2 0-1.22.98-2.2 2.2-2.2 1.22 0 2.2.98 2.2 2.2 0 1.22-.98 2.2-2.2 2.2z"></path></svg>';
5
const gh_pr_svg = '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="octicon octicon-git-pull-request" height="16" version="1.1" viewBox="0 0 12 16" width="12"><path d="M11 11.28V5c-.03-.78-.34-1.47-.94-2.06C9.46 2.35 8.78 2.03 8 2H7V0L4 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 10 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zM4 3c0-1.11-.89-2-2-2a1.993 1.993 0 0 0-1 3.72v6.56A1.993 1.993 0 0 0 2 15a1.993 1.993 0 0 0 1-3.72V4.72c.59-.34 1-.98 1-1.72zm-.8 10c0 .66-.55 1.2-1.2 1.2-.65 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"></path></svg>';
6
const gh_iss_svg = '<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" class="octicon octicon-issue-opened" height="16" version="1.1" viewBox="0 0 14 16" width="14"><path d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg>';
7
const svg_inline_prefix = 'data:image/svg+xml;ascii,';
8
// End embedded images
9
10
const getLabelsField = (labels) => {
11
let labelsArray = [];
12
labels.forEach(function(label) {
13
labelsArray.push(label.name);
14
});
15
labelsArray = labelsArray.join(', ');
16
return {
17
title: 'Labels',
18
value: labelsArray,
19
short: labelsArray.length <= 40
20
};
21
};
22
23
const githubEvents = {
24
ping(request) {
25
return {
26
content: {
27
text: ':thumbsup: ' + request.content.zen
28
}
29
};
30
},
31
32
issues(request) {
33
const user = request.content.sender;
34
const attachment = {
35
author_icon: svg_inline_prefix + gh_iss_svg,
36
author_name: '#' + request.content.issue.number + ' - ' + request.content.issue.title,
37
author_link: request.content.issue.html_url,
38
fields: []
39
};
40
41
if (request.content.issue.labels) {
42
attachment.fields.push(getLabelsField(request.content.issue.labels));
43
}
44
45
if (request.content.issue.assignee) {
46
attachment.fields.push({
47
title: 'Assignee',
48
value: request.content.issue.assignee.login,
49
short: true
50
});
51
}
52
53
const actions = {
54
'assigned': ':inbox_tray:',
55
'unassigned': ':outbox_tray:',
56
'opened': ':triangular_flag_on_post:',
57
'closed': ':white_check_mark:',
58
'reopened': ':triangular_flag_on_post:',
59
'labeled': ':label:',
60
'unlabeled': ':label:',
61
'edited': ':pencil:'
62
};
63
64
const text = actions[request.content.action] + ' issue';
65
66
return {
67
content: {
68
icon_url: user.avatar_url,
69
alias: user.login,
70
text: text,
71
attachments: [attachment]
72
}
73
};
74
},
75
76
issue_comment(request) {
77
const user = request.content.comment.user;
78
var attachment = {
79
author_icon: svg_inline_prefix + gh_iss_svg,
80
author_name: '#' + request.content.issue.number + ' - ' + request.content.issue.title,
81
author_link: request.content.comment.html_url,
82
fields: []
83
};
84
85
if (request.content.issue.labels) {
86
attachment.fields.push(getLabelsField(request.content.issue.labels));
87
}
88
89
if (request.content.issue.assignee) {
90
attachment.fields.push({
91
title: 'Assignee',
92
value: request.content.issue.assignee.login,
93
short: true
94
});
95
}
96
97
const text = ':speech_balloon: ' + request.content.comment.body;
98
99
return {
100
content: {
101
icon_url: user.avatar_url,
102
alias: user.login,
103
text: text,
104
attachments: [attachment]
105
}
106
};
107
},
108
109
pull_request(request) {
110
const user = request.content.sender;
111
const attachment = {
112
author_icon: svg_inline_prefix + gh_pr_svg,
113
author_name: '#' + request.content.pull_request.number + ' - ' + request.content.pull_request.title,
114
author_link: request.content.pull_request.html_url
115
};
116
117
let text = 'Pull request';
118
switch (request.content.action) {
119
case 'assigned':
120
text += ' assigned to: ' + request.content.assignee.login;
121
break;
122
case 'unassigned':
123
text += ' unassigned of ' + request.content.assignee.login;
124
break;
125
case 'opened':
126
text += ' opened';
127
break;
128
case 'closed':
129
if (request.content.pull_request.merged) {
130
text += ' merged';
131
} else {
132
text += ' closed';
133
}
134
break;
135
case 'reopened':
136
text += ' reopened';
137
break;
138
case 'labeled':
139
text += ' added label: "' + request.content.label.name + '" ';
140
break;
141
case 'unlabeled':
142
text += ' removed label: "' + request.content.label.name + '" ';
143
break;
144
case 'synchronize':
145
text += ' synchronized';
146
}
147
148
return {
149
content: {
150
icon_url: user.avatar_url,
151
alias: user.login,
152
text: text,
153
attachments: [attachment]
154
}
155
};
156
},
157
158
//// GitHub push event
159
push(request) {
160
var commits = request.content.commits;
161
var multi_commit = ""
162
var is_short = true;
163
var changeset = 'Changeset';
164
if ( commits.length > 1 ) {
165
var multi_commit = " [Multiple Commits]";
166
var is_short = false;
167
var changeset = changeset + 's';
168
var output = [];
169
}
170
const user = request.content.sender;
171
const attachment = {
172
author_icon: svg_inline_prefix + gh_cmit_svg,
173
author_name: "Message: " + request.content.head_commit.message + multi_commit,
174
author_link: request.content.compare,
175
fields: []
176
};
177
178
if (request.content.repository.full_name) {
179
attachment.fields.push({
180
title: 'Repo',
181
value: "["+request.content.repository.full_name+"]("+request.content.repository.url+")",
182
short: is_short
183
});
184
}
185
186
for (var i = 0; i < commits.length; i++) {
187
var commit = commits[i];
188
var shortID = commit.id.substring(0,7);
189
if ( commits.length > 1 ) {
190
output = '[' + shortID + '](' + commit.url + ') - ' + commit.message
191
if (i == 0){
192
attachment.fields.push({
193
title: changeset,
194
value: output,
195
short: is_short
196
});
197
} else{
198
attachment.fields.push({
199
title: changeset,
200
value: output,
201
short: is_short
202
});
203
}
204
} else {
205
output = "[" + shortID + "](" + commit.url + ")"
206
attachment.fields.push({
207
title: changeset,
208
value: output,
209
short: is_short
210
});
211
}
212
}
213
214
const text = ':ballot_box_with_check: Pushed to ' + "["+request.content.ref.split('/').pop()+"]";
215
216
return {
217
content: {
218
icon_url: user.avatar_url,
219
alias: user.login,
220
text: text,
221
attachments: [attachment]
222
}
223
};
224
}, // End GitHub Push
225
};
226
227
class Script {
228
process_incoming_request({ request }) {
229
const header = request.headers['x-github-event'];
230
if (githubEvents[header]) {
231
return githubEvents[header](request);
232
}
233
234
return {
235
error: {
236
success: false,
237
message: 'Unsupported method'
238
}
239
};
240
}
241
}
Copied!

Customizing your integration scripts

The purpose of the integration script is to transform data in one format (the format provided by your incoming service, such as GitHub) into another format (the format expected by Rocket.Chat). Therefore, should you wish to customize either of the scripts presented above, you will need two resources:
Note that data comes into your script from GitHub as the request.content object.

Send commands to GitHub

This script only works for public repositories
  • Create a new Outgoing WebHook
  • Select the channel where you will use the commands and receive the responses
  • Set URLs as https://api.github.com/repos/User-Or-Org-Name/Repo-Name like https://api.github.com/repos/RocketChat/Rocket.Chat
  • Enable Scripts
  • Use this Script to listen for commands pr ls, pr list and help
1
/* exported Script */
2
/* globals Store */
3
4
class Script {
5
prepare_outgoing_request({ request }) {
6
let match;
7
8
console.log('lastCmd', Store.get('lastCmd'));
9
10
match = request.data.text.match(/^pr last$/);
11
if (match && Store.get('lastCmd')) {
12
request.data.text = Store.get('lastCmd');
13
}
14
15
match = request.data.text.match(/^pr\s(ls|list)\s*(open|closed|all)?$/);
16
if (match) {
17
Store.set('lastCmd', request.data.text);
18
let u = request.url + '/pulls';
19
if (match[2]) {
20
u += '?state='+match[2];
21
}
22
return {
23
url: u,
24
headers: request.headers,
25
method: 'GET'
26
};
27
}
28
29
match = request.data.text.match(/^help$/);
30
if (match) {
31
Store.set('lastCmd', request.data.text);
32
return {
33
message: {
34
text: [
35
'**GitHub commands**',
36
'```',
37
' pr ls|list [open|closed|all] List Pull Requests',
38
'```'
39
].join('\n')
40
}
41
};
42
}
43
}
44
45
process_outgoing_response({ request, response }) {
46
var text = [];
47
response.content.forEach(function(pr) {
48
text.push('> '+pr.state+' [#'+pr.number+']('+pr.html_url+') - '+pr.title);
49
});
50
51
return {
52
content: {
53
text: text.join('\n'),
54
parseUrls: false
55
}
56
};
57
}
58
}
Copied!
  • Save your integration
Last modified 8mo ago