summaryrefslogtreecommitdiff
path: root/src/api/service/github.ts
blob: 1c78267c0f76f24fb60bca60aa4be03059253a16 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import * as EventEmitter from 'events';
import * as express from 'express';
import * as request from 'request';
const crypto = require('crypto');
import User from '../models/user';
import config from '../../conf';

module.exports = async (app: express.Application) => {
	if (config.github_bot == null) return;

	const bot = await User.findOne({
		username_lower: config.github_bot.username.toLowerCase()
	});

	if (bot == null) {
		console.warn(`GitHub hook bot specified, but not found: @${config.github_bot.username}`);
		return;
	}

	const post = text => require('../endpoints/posts/create')({ text }, bot);

	const handler = new EventEmitter();

	app.post('/hooks/github', (req, res, next) => {
		// req.headers['x-hub-signature'] および
		// req.headers['x-github-event'] は常に string ですが、型定義の都合上
		// string | string[] になっているので string を明示しています
		if ((new Buffer(req.headers['x-hub-signature'] as string)).equals(new Buffer(`sha1=${crypto.createHmac('sha1', config.github_bot.hook_secret).update(JSON.stringify(req.body)).digest('hex')}`))) {
			handler.emit(req.headers['x-github-event'] as string, req.body);
			res.sendStatus(200);
		} else {
			res.sendStatus(400);
		}
	});

	handler.on('status', event => {
		const state = event.state;
		switch (state) {
			case 'error':
			case 'failure':
				const commit = event.commit;
				const parent = commit.parents[0];

				// Fetch parent status
				request({
					url: `${parent.url}/statuses`,
					headers: {
						'User-Agent': 'misskey'
					}
				}, (err, res, body) => {
					if (err) {
						console.error(err);
						return;
					}
					const parentStatuses = JSON.parse(body);
					const parentState = parentStatuses[0].state;
					const stillFailed = parentState == 'failure' || parentState == 'error';
					if (stillFailed) {
						post(`**⚠️BUILD STILL FAILED⚠️**: ?[${commit.commit.message}](${commit.html_url})`);
					} else {
						post(`**🚨BUILD FAILED🚨**: →→→?[${commit.commit.message}](${commit.html_url})←←←`);
					}
				});
				break;
		}
	});

	handler.on('push', event => {
		const ref = event.ref;
		switch (ref) {
			case 'refs/heads/master':
				const pusher = event.pusher;
				const compare = event.compare;
				const commits = event.commits;
				post([
					`Pushed by **${pusher.name}** with ?[${commits.length} commit${commits.length > 1 ? 's' : ''}](${compare}):`,
					commits.reverse().map(commit => `・[?[${commit.id.substr(0, 7)}](${commit.url})] ${commit.message.split('\n')[0]}`).join('\n'),
				].join('\n'));
				break;
			case 'refs/heads/release':
				const commit = event.commits[0];
				post(`RELEASED: ${commit.message}`);
				break;
		}
	});

	handler.on('issues', event => {
		const issue = event.issue;
		const action = event.action;
		let title: string;
		switch (action) {
			case 'opened': title = 'Issue opened'; break;
			case 'closed': title = 'Issue closed'; break;
			case 'reopened': title = 'Issue reopened'; break;
			default: return;
		}
		post(`${title}: <${issue.number}>「${issue.title}」\n${issue.html_url}`);
	});

	handler.on('issue_comment', event => {
		const issue = event.issue;
		const comment = event.comment;
		const action = event.action;
		let text: string;
		switch (action) {
			case 'created': text = `Commented to「${issue.title}」:${comment.user.login}${comment.body}」\n${comment.html_url}`; break;
			default: return;
		}
		post(text);
	});

	handler.on('watch', event => {
		const sender = event.sender;
		post(`⭐️ Starred by **${sender.login}** ⭐️`);
	});

	handler.on('fork', event => {
		const repo = event.forkee;
		post(`🍴 Forked:\n${repo.html_url} 🍴`);
	});

	handler.on('pull_request', event => {
		const pr = event.pull_request;
		const action = event.action;
		let text: string;
		switch (action) {
			case 'opened': text = `New Pull Request:「${pr.title}」\n${pr.html_url}`; break;
			case 'reopened': text = `Pull Request Reopened:「${pr.title}」\n${pr.html_url}`; break;
			case 'closed':
				text = pr.merged
					? `Pull Request Merged!:「${pr.title}」\n${pr.html_url}`
					: `Pull Request Closed:「${pr.title}」\n${pr.html_url}`;
				break;
			default: return;
		}
		post(text);
	});
};