feat: Add modules

This commit is contained in:
xuexb 2017-10-14 23:08:42 +08:00
parent d86e3a81f8
commit f6cc9f164a
7 changed files with 280 additions and 0 deletions

48
src/app.js Executable file
View File

@ -0,0 +1,48 @@
/**
* @file github-bot 入口文件
* @author xuexb <fe.xiaowu@gmail.com>
*/
require('dotenv').config();
const EventEmitter = require('events');
const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const requireDir = require('require-dir');
const {verifySignature, getRepo} = require('./utils');
const issueActions = requireDir('./modules/issues');
const app = new Koa();
const githubEvent = new EventEmitter();
app.use(bodyParser());
app.use(ctx => {
let eventName = ctx.request.headers['x-github-event'];
if (eventName && verifySignature(ctx.request)) {
const payload = ctx.request.body;
const action = payload.action || payload.ref_type;
eventName += `_${action}`;
console.log(`receive event: ${eventName}`);
if (payload.sender.login !== process.env.GITHUB_BOT_NAME) {
githubEvent.emit(eventName, {
repo: getRepo(payload.repository.full_name),
payload
});
}
ctx.body = 'Ok.';
}
else {
ctx.body = 'Go away.';
}
});
Object.keys(issueActions).forEach((key) => {
issueActions[key](githubEvent.on.bind(githubEvent));
});
const port = 8000;
app.listen(port);
console.log(`Listening on http://0.0.0.0:${port}`);

69
src/github.js Executable file
View File

@ -0,0 +1,69 @@
/**
* @file github 操作库
* @author xuexb <fe.xiaowu@gmail.com>
*/
const GitHub = require('github');
const github = new GitHub({
debug: process.env.NODE_ENV === 'development'
});
github.authenticate({
type: 'token',
token: process.env.GITHUB_TOKEN
});
module.exports = {
commentIssue(payload, body) {
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const number = payload.issue.number;
github.issues.createComment({
owner,
repo,
number,
body
});
},
closeIssue(payload) {
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const number = payload.issue.number;
github.issues.edit({
owner,
repo,
number,
state: 'closed'
});
},
addAssigneesToIssue(payload, assign) {
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const number = payload.issue.number;
github.issues.edit({
owner,
repo,
number,
assignees: Array.isArray(assign) ? assign : [assign]
});
},
addLabelsToIssue(payload, labels) {
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const number = payload.issue.number;
github.issues.addLabels({
owner,
repo,
number,
labels: Array.isArray(labels) ? labels : [labels]
});
}
};

View File

@ -0,0 +1,25 @@
/**
* @file issue 自动 `assign` 给指定人员
* @author xuexb <fe.xiaowu@gmail.com>
*/
const {addAssigneesToIssue} = require('../../github');
const assignMap = {
bug: 'xuexb',
enhancement: 'xuexb',
question: 'xuexb'
};
function autoAssign(on) {
on('issues_labeled', ({payload, repo}) => {
if (assignMap[payload.label.name]) {
addAssigneesToIssue(
payload,
assignMap[payload.label.name]
);
}
});
}
module.exports = autoAssign;

View File

@ -0,0 +1,18 @@
/**
* @file 根据 tag 自动 release
* @author xuexb <fe.xiaowu@gmail.com>
*/
const {getReleaseByTag} = require('../../github');
const {cloneRepo, getTags} = require('../../utils');
function autoReleaseNote(on) {
on('create_tag', ({payload, repo}) => {
const repoDir = cloneRepo(payload.repository.clone_url, repo);
const tags = getTags(repoDir);
console.log(repoDir, tags);
});
}
module.exports = autoReleaseNote;

View File

@ -0,0 +1,41 @@
/**
* @file 不规范issue则自动关闭
* @author xuexb <fe.xiaowu@gmail.com>
*/
const format = require('string-template');
const {
commentIssue,
closeIssue,
addLabelsToIssue
} = require('../../github');
const comment = [
'hi @{user},非常感谢您的反馈,',
'但是由于您没有使用 [规范的issue](https://github.com/xuexb/github-bot) 格式, 将直接被关闭, 谢谢!'
].join('');
const match = str => {
return /node version:\s*(\d\.?)+/.test(str) && /url:\s*(https?:)?\/\/(\w{3,})/.test(str);
};
function replyInvalid(on) {
on('issues_opened', ({payload}) => {
const issue = payload.issue;
const opener = issue.user.login;
if (!match(issue.body)) {
commentIssue(
payload,
format(comment, {
user: opener
})
);
closeIssue(payload);
addLabelsToIssue(payload, 'invalid');
}
});
}
module.exports = replyInvalid;

View File

@ -0,0 +1,25 @@
/**
* @file 不规范issue则自动关闭
* @author xuexb <fe.xiaowu@gmail.com>
*/
const format = require('string-template');
const {commentIssue} = require('../../github');
const comment = 'hi @{user},请提供一个可预览的链接,如: <https://codepen.io/pen?template=KgPZrE&editors=0010>';
function replyNeedDemo(on) {
on('issues_labeled', ({payload, repo}) => {
if (payload.label.name === 'need demo') {
commentIssue(
payload,
format(comment, {
user: payload.issue.user.login
})
);
}
});
}
module.exports = replyNeedDemo;

54
src/utils.js Executable file
View File

@ -0,0 +1,54 @@
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const {fixedTimeComparison} = require('cryptiles');
const {execSync, exec} = require('child_process');
const utils = {
mentioned(body) {
return body.includes(`@${process.env.GITHUB_BOT_NAME}`);
},
verifySignature(request) {
let signature = crypto.createHmac('sha1', process.env.GITHUB_SECRET_TOKEN)
.update(request.rawBody)
.digest('hex');
signature = `sha1=${signature}`;
return fixedTimeComparison(signature, request.headers['x-hub-signature']);
},
getRepo(url) {
return url.split('/')[1];
},
isDirectory(file) {
try {
return fs.statSync(file).isDirectory();
}
catch (e) {
if (e.code !== 'ENOENT') {
throw e;
}
return false;
}
},
cloneRepo(url, repo) {
const repoDir = path.resolve(__dirname, '../github/', repo);
if (!utils.isDirectory(repoDir)) {
throw new Error(`${repoDir} 不是github目录!`);
}
execSync(`cd ${repoDir} && git pull`);
return repoDir;
},
getTags(dir) {
return execSync(`cd ${dir} && git tag -l`).toString().split(/\n+/).filter(tag => !!tag).reverse();
}
};
module.exports = utils;