Browse Source

Initial commit.

master
Marrub 3 years ago
commit
e6dfea6392
7 changed files with 975 additions and 0 deletions
  1. +3
    -0
      .gitignore
  2. +22
    -0
      LICENSE
  3. +451
    -0
      build/bundle.js
  4. +44
    -0
      package.json
  5. +6
    -0
      rollup.config.js
  6. +400
    -0
      src/events.js
  7. +49
    -0
      src/main.js

+ 3
- 0
.gitignore View File

@@ -0,0 +1,3 @@
node_modules
*.swp


+ 22
- 0
LICENSE View File

@@ -0,0 +1,22 @@
The MIT License (MIT)

Copyright (c) 2016 Dylan Falconer and Project Golan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


+ 451
- 0
build/bundle.js View File

@@ -0,0 +1,451 @@
'use strict';

function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }

var bodyParser = _interopDefault(require('body-parser'));
var request = _interopDefault(require('request'));
var URI = _interopDefault(require('urijs'));
var merge = _interopDefault(require('merge'));
var express = _interopDefault(require('express'));
var crypto = _interopDefault(require('crypto-js'));
var bufeq = _interopDefault(require('buffer-equal-constant-time'));

const uri = process.env.WEBHOOK_URI;

let color = 0x77CBD1;

function esc(str)
{
return str.replace(/([*#/()[\]_`\\])/g, "\\$&");
}

function sendMessage(content, embeds)
{
request.post({ uri: uri, json: { content: content, embeds: embeds } },
function(error, response, body)
{
if(error || response.statusCode < 200 || response.statusCode >= 300)
{
if(body != undefined)
console.log("ERROR: \n", body);
else
console.log("<error>");
}
});
}

function struri(str)
{
return new URI(str).unicode().toString();
}

function uristr(name, uri)
{
return "[" + esc(name) + "](" + struri(uri) + ")";
}

function clipstr(str, amt)
{
if(str == null) return str;
if(str.length > amt)
str = str.substring(0, amt - 3) + "...";
return str;
}

function author(name, url, icon_url)
{
return {
color: color,
author: {
name: name,
url: url,
icon_url: icon_url
}
};
}

function embed(title, description, url)
{
return {
color: color,
title: title,
description: description,
url: url
};
}

function field(name, value)
{
return {
color: color,
fields: [{
name: name,
value: value,
inline: true
}]
};
}

function footer(text, icon_url)
{
return {
color: color,
footer: {
text: text,
icon_url: icon_url
}
};
}

function prsCommInfo(info)
{
return {
id: info.comment.commit_id.substring(0, 7),
idLong: info.comment.commit_id,
uri: info.comment.html_url,
body: esc(info.comment.body)
};
}

function prsTracInfo(info)
{
return {
title: info.title,
body: info.body,
uristr: uristr("#" + info.number, info.html_url),
uri: info.html_url
};
}

function prsIssuInfo(info)
{
return prsTracInfo(info.issue);
}

function prsPullInfo(info)
{
return prsTracInfo(info.pull_request);
}

function prsRepoInfo(info)
{
return {
shortname: info.repository.name,
name: info.repository.full_name,
avatar: info.repository.owner.avatar_url,
uri: info.repository.html_url,
uristr: uristr(info.repository.full_name, info.repository.html_url),
mai: "**" +
uristr(info.repository.full_name, info.repository.html_url) +
"**:"
};
}

function prsUserInfo(info)
{
return {
user: info.login,
userUri: info.html_url,
avatar: info.avatar_url,
uristr: uristr(info.login, info.html_url)
};
}

function prsSendInfo(info)
{
return prsUserInfo(info.sender);
}

function issueInfo(info, issu)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);

let outp = null;

switch(info.action)
{
case "opened":
const body = clipstr(issu.body, 128);
outp = merge(true,
author(send.user, send.uri, send.avatar),
field(esc(issu.title), body ? body : "<no description>")
);
break;
case "labeled": case "unlabeled":
const tmp = color;
const uri = struri(`${repo.uri}/labels/${info.label.name}`);
const tag = info.action[0].toUpperCase() + info.action.substring(1);

color = parseInt(info.label.color, 16);
outp = embed(`${tag} **${info.label.name}**`, null, uri);
color = tmp;
break;
case "assigned": case "unassigned":
let user = prsUserInfo(info.assignee);
outp =
author(`${user.user} was ${info.action}`, user.uri, user.avatar);
break;
}
return outp;
}

class Events
{
static commit_comment(info)
{
const comm = prsCommInfo(info);
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);

const comment = uristr("comment", comm.uri);
const commit = uristr(comm.id, `${repo.uri}/commit/${comm.idLong}`);

sendMessage(`${repo.mai} New ${comment} on commit **${commit}**`,
[merge(true,
author(send.user, send.uri, send.avatar),
footer(clipstr(comm.body, 80))
)]);
}

static create(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);

const type = info.ref_type;
const ref = info.ref;
const refmsg = (ref != null) ? (": **" + ref + "**") : "";

sendMessage(
`${send.uristr} created a new ${type} on ${repo.uristr}${refmsg}`);
}

static delete(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);

const type = info.ref_type;
const ref = info.ref;
const refmsg = (ref != null) ? (": **" + ref + "**") : "";

sendMessage(
`${send.uristr} deleted a ${type} on ${repo.uristr}${refmsg}`);
}

static fork(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);

const fork = uristr(info.forkee.full_name, info.forkee.html_url);

sendMessage(`**${send.uristr}** forked ${repo.uristr} into **${fork}**`);
}

static issue_comment(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);
const user = prsUserInfo(info.comment.user);
const issu = prsIssuInfo(info);

let outp = null;
let msg = null;

switch(info.action)
{
case "created":
{
const comment = uristr("comment", info.comment.html_url);
msg = `New ${comment} on issue **${issu.uristr}**`;
outp = merge(true,
author(user.user, user.uri, user.avatar),
footer(clipstr(info.comment.body, 80))
);
break;
}
case "edited":
{
const comm = uristr("Comment", info.comment.html_url);
msg = `${comm} on issue **${issu.uristr}** edited by ${send.uristr}`;
break;
}
case "deleted":
msg = `Comment on issue **${issu.uristr}** deleted by ${send.uristr}`;
break;
}

sendMessage(`${repo.mai} ${msg}`, outp ? [outp] : null);
}

static issues(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);
const issu = prsIssuInfo(info);
const outp = issueInfo(info, issu);

sendMessage(
`${repo.mai} Issue **${issu.uristr}** ` +
`${info.action} by ${send.uristr}`,
outp ? [outp] : null);
}

static member(info)
{
const repo = prsRepoInfo(info);
const user = prsUserInfo(info.member);
sendMessage(`${user.uristr} was added to ${repo.uristr}`);
}

static milestone(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);

const mstone = uristr(info.milestone.title, info.milestone.html_url);
const action = info.action;

sendMessage(
`${repo.mai} Milestone ${mstone} ${action} by ${send.uristr}`);
}

static public(info)
{
sendMessage(`${prsRepoInfo(info).uristr} has been made public`);
}

static pull_request_review(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);
const pull = prsPullInfo(info);

let statename = info.review.state;
if(statename == "changes_requested") statename = "denied";

const state = uristr(statename, info.review.html_url);
const body = clipstr(info.review.body, 80);

sendMessage(
`${repo.mai} Pull request **${pull.uristr}** ${state}`,
[merge(true,
author(send.user, send.uri, send.avatar),
footer(clipstr(body ? body : "<no description>", 80))
)]);
}

static pull_request(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);
const pull = prsPullInfo(info);

let outp = null;
let msg =
`Pull request **${pull.uristr}** ${info.action} by ${send.uristr}`;

if(info.pull_request.merged && info.action == "closed")
msg = `Pull request **${pull.uristr}** merged by ${send.uristr}`;
else
outp = issueInfo(info, pull);

sendMessage(`${repo.mai} ${msg}`, outp ? [outp] : null);
}

static push(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);
const name = `${repo.shortname}/${info.ref.split("/")[2]}`;
const s = info.commits.length != 1 ? "s" : "";

let commits = "";
for (let commit of info.commits) {
if(commits.length > 1000) break;
const shorthash = commit.id.substring(0, 7);
const uri = struri(`${repo.uri}/commit/${commit.id}`);
const msg = esc(clipstr(commit.message.split(/\r?\n/)[0], 64));
const name = esc(commit.author.name);
commits += `${uristr(`\`${shorthash}\``, uri)} ${msg} - *${name}*\n`;
}

const uri = info.compare;

sendMessage(null, [merge(true,
author(send.user, send.uri, send.avatar),
embed(`[${name}] ${info.commits.length} new commit${s}`, commits, uri)
)]);
}

static release(info)
{
const repo = prsRepoInfo(info);
const tag = uristr(info.release.tag_name, info.release.html_url);
sendMessage(`${repo.mai} ${tag} released`);
}

static watch(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);

sendMessage(`${repo.uristr} was starred by ${send.uristr}`);
}

static ping(info) {}
static gollum(info) {}
static status(info) {}
static team_add(info) {}
static repository(info) {}
static deployment(info) {}
static membership(info) {}
static page_build(info) {}
static deployment_status(info) {}
static pull_request_review_comment(info) {}
}

const env = process.env;
const vhost = env.VHOST;

function verifyRequest(req)
{
const hmac = crypto.HmacSHA1(req.body, env.GITHUB_SECRET);

const hash = new Buffer("sha1=" + crypto.enc.Hex.stringify(hmac));
const reqhash = new Buffer(req.get("X-Hub-Signature"));

return bufeq(hash, reqhash);
}

function handleRequest(req, res)
{
if(req.body == null || (vhost && req.hostname != vhost)) return;

if(env.GITHUB_SECRET && !verifyRequest(req))
res.sendStatus(500);
else
{
const event = req.get("X-GitHub-Event");

if(event)
{
Events[event](JSON.parse(req.body));
res.sendStatus(200);
}
else
res.sendStaus(400);
}
}

function main()
{
express()
.use(bodyParser.text({type: "application/json"}))
.post("/", handleRequest)
.listen(2237);
}

main();

+ 44
- 0
package.json View File

@@ -0,0 +1,44 @@
{
"name": "UNATCORelay",
"version": "1.0.0",
"description": "A Discord webhook generator for GitHub.",
"main": "build/bundle.js",
"scripts": {
"test": "echo \"No tests\"",
"build": "./node_modules/.bin/rollup -c",
"start": "npm run build && node build/bundle.js"
},
"engines": {
"node": "6.5.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Project-Golan/UNATCORelay.git"
},
"keywords": [
"Discord",
"GitHub"
],
"contributors": [
"Dylan Falconer <me@falconerd.com> (http://falconerd.com)",
"Project Golan (http://greyserv.net)"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/Project-Golan/UNATCORelay/issues"
},
"homepage": "https://github.com/Project-Golan/UNATCORelay#readme",
"dependencies": {
"buffer-equal-constant-time": "^1.0.1",
"body-parser": "^1.9.2",
"crypto-js": "^3.1.8",
"express": "^4.14.0",
"request": "^2.74.0",
"rollup": "^0.35.11",
"merge": "^1.2.0",
"urijs": "^1.18.2"
},
"devDependencies": {
"rollup": "^0.35.11"
}
}

+ 6
- 0
rollup.config.js View File

@@ -0,0 +1,6 @@
export default
{
entry: "src/main.js",
dest: "build/bundle.js",
format: "cjs"
}

+ 400
- 0
src/events.js View File

@@ -0,0 +1,400 @@
import request from "request";
import URI from "urijs";
import merge from "merge";

const uri = process.env.WEBHOOK_URI;

let color = 0x77CBD1;

function esc(str)
{
return str.replace(/([*#/()[\]_`\\])/g, "\\$&");
}

function sendMessage(content, embeds)
{
request.post({ uri: uri, json: { content: content, embeds: embeds } },
function(error, response, body)
{
if(error || response.statusCode < 200 || response.statusCode >= 300)
{
if(body != undefined)
console.log("ERROR: \n", body);
else
console.log("<error>");
}
});
}

function struri(str)
{
return new URI(str).unicode().toString();
}

function uristr(name, uri)
{
return "[" + esc(name) + "](" + struri(uri) + ")";
}

function clipstr(str, amt)
{
if(str == null) return str;
if(str.length > amt)
str = str.substring(0, amt - 3) + "...";
return str;
}

function author(name, url, icon_url)
{
return {
color: color,
author: {
name: name,
url: url,
icon_url: icon_url
}
};
}

function embed(title, description, url)
{
return {
color: color,
title: title,
description: description,
url: url
};
}

function field(name, value)
{
return {
color: color,
fields: [{
name: name,
value: value,
inline: true
}]
};
}

function footer(text, icon_url)
{
return {
color: color,
footer: {
text: text,
icon_url: icon_url
}
};
}

function prsCommInfo(info)
{
return {
id: info.comment.commit_id.substring(0, 7),
idLong: info.comment.commit_id,
uri: info.comment.html_url,
body: esc(info.comment.body)
};
}

function prsTracInfo(info)
{
return {
title: info.title,
body: info.body,
uristr: uristr("#" + info.number, info.html_url),
uri: info.html_url
};
}

function prsIssuInfo(info)
{
return prsTracInfo(info.issue);
}

function prsPullInfo(info)
{
return prsTracInfo(info.pull_request);
}

function prsRepoInfo(info)
{
return {
shortname: info.repository.name,
name: info.repository.full_name,
avatar: info.repository.owner.avatar_url,
uri: info.repository.html_url,
uristr: uristr(info.repository.full_name, info.repository.html_url),
mai: "**" +
uristr(info.repository.full_name, info.repository.html_url) +
"**:"
};
}

function prsUserInfo(info)
{
return {
user: info.login,
userUri: info.html_url,
avatar: info.avatar_url,
uristr: uristr(info.login, info.html_url)
};
}

function prsSendInfo(info)
{
return prsUserInfo(info.sender);
}

function issueInfo(info, issu)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);

let outp = null;

switch(info.action)
{
case "opened":
const body = clipstr(issu.body, 128);
outp = merge(true,
author(send.user, send.uri, send.avatar),
field(esc(issu.title), body ? body : "<no description>")
);
break;
case "labeled": case "unlabeled":
const tmp = color;
const uri = struri(`${repo.uri}/labels/${info.label.name}`);
const tag = info.action[0].toUpperCase() + info.action.substring(1);

color = parseInt(info.label.color, 16);
outp = embed(`${tag} **${info.label.name}**`, null, uri);
color = tmp;
break;
case "assigned": case "unassigned":
let user = prsUserInfo(info.assignee);
outp =
author(`${user.user} was ${info.action}`, user.uri, user.avatar);
break;
}
return outp;
}

export default class Events
{
static commit_comment(info)
{
const comm = prsCommInfo(info);
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);

const comment = uristr("comment", comm.uri);
const commit = uristr(comm.id, `${repo.uri}/commit/${comm.idLong}`);

sendMessage(`${repo.mai} New ${comment} on commit **${commit}**`,
[merge(true,
author(send.user, send.uri, send.avatar),
footer(clipstr(comm.body, 80))
)]);
}

static create(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);

const type = info.ref_type;
const ref = info.ref;
const refmsg = (ref != null) ? (": **" + ref + "**") : "";

sendMessage(
`${send.uristr} created a new ${type} on ${repo.uristr}${refmsg}`);
}

static delete(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);

const type = info.ref_type;
const ref = info.ref;
const refmsg = (ref != null) ? (": **" + ref + "**") : "";

sendMessage(
`${send.uristr} deleted a ${type} on ${repo.uristr}${refmsg}`);
}

static fork(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);

const fork = uristr(info.forkee.full_name, info.forkee.html_url);

sendMessage(`**${send.uristr}** forked ${repo.uristr} into **${fork}**`);
}

static issue_comment(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);
const user = prsUserInfo(info.comment.user);
const issu = prsIssuInfo(info);

let outp = null;
let msg = null;

switch(info.action)
{
case "created":
{
const comment = uristr("comment", info.comment.html_url);
msg = `New ${comment} on issue **${issu.uristr}**`;
outp = merge(true,
author(user.user, user.uri, user.avatar),
footer(clipstr(info.comment.body, 80))
);
break;
}
case "edited":
{
const comm = uristr("Comment", info.comment.html_url);
msg = `${comm} on issue **${issu.uristr}** edited by ${send.uristr}`;
break;
}
case "deleted":
msg = `Comment on issue **${issu.uristr}** deleted by ${send.uristr}`;
break;
}

sendMessage(`${repo.mai} ${msg}`, outp ? [outp] : null);
}

static issues(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);
const issu = prsIssuInfo(info);
const outp = issueInfo(info, issu);

sendMessage(
`${repo.mai} Issue **${issu.uristr}** ` +
`${info.action} by ${send.uristr}`,
outp ? [outp] : null);
}

static member(info)
{
const repo = prsRepoInfo(info);
const user = prsUserInfo(info.member);
sendMessage(`${user.uristr} was added to ${repo.uristr}`);
}

static milestone(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);

const mstone = uristr(info.milestone.title, info.milestone.html_url);
const action = info.action;

sendMessage(
`${repo.mai} Milestone ${mstone} ${action} by ${send.uristr}`);
}

static public(info)
{
sendMessage(`${prsRepoInfo(info).uristr} has been made public`);
}

static pull_request_review(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);
const pull = prsPullInfo(info);

let statename = info.review.state;
if(statename == "changes_requested") statename = "denied";

const state = uristr(statename, info.review.html_url);
const body = clipstr(info.review.body, 80);

sendMessage(
`${repo.mai} Pull request **${pull.uristr}** ${state}`,
[merge(true,
author(send.user, send.uri, send.avatar),
footer(clipstr(body ? body : "<no description>", 80))
)]);
}

static pull_request(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);
const pull = prsPullInfo(info);

let outp = null;
let msg =
`Pull request **${pull.uristr}** ${info.action} by ${send.uristr}`;

if(info.pull_request.merged && info.action == "closed")
msg = `Pull request **${pull.uristr}** merged by ${send.uristr}`;
else
outp = issueInfo(info, pull);

sendMessage(`${repo.mai} ${msg}`, outp ? [outp] : null);
}

static push(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);
const name = `${repo.shortname}/${info.ref.split("/")[2]}`;
const s = info.commits.length != 1 ? "s" : "";

let commits = "";
for (let commit of info.commits) {
if(commits.length > 1000) break;
const shorthash = commit.id.substring(0, 7);
const uri = struri(`${repo.uri}/commit/${commit.id}`);
const msg = esc(clipstr(commit.message.split(/\r?\n/)[0], 64));
const name = esc(commit.author.name);
commits += `${uristr(`\`${shorthash}\``, uri)} ${msg} - *${name}*\n`;
}

const uri = info.compare;

sendMessage(null, [merge(true,
author(send.user, send.uri, send.avatar),
embed(`[${name}] ${info.commits.length} new commit${s}`, commits, uri)
)]);
}

static release(info)
{
const repo = prsRepoInfo(info);
const tag = uristr(info.release.tag_name, info.release.html_url);
sendMessage(`${repo.mai} ${tag} released`);
}

static watch(info)
{
const repo = prsRepoInfo(info);
const send = prsSendInfo(info);

sendMessage(`${repo.uristr} was starred by ${send.uristr}`);
}

static ping(info) {}
static gollum(info) {}
static status(info) {}
static team_add(info) {}
static repository(info) {}
static deployment(info) {}
static membership(info) {}
static page_build(info) {}
static deployment_status(info) {}
static pull_request_review_comment(info) {}
}

+ 49
- 0
src/main.js View File

@@ -0,0 +1,49 @@
import bodyParser from "body-parser";
import Events from "./events";
import express from "express";
import crypto from "crypto-js";
import bufeq from "buffer-equal-constant-time";

const env = process.env;
const vhost = env.VHOST;
const port = parseInt(env.WEBHOOK_PORT);

function verifyRequest(req)
{
const hmac = crypto.HmacSHA1(req.body, env.GITHUB_SECRET);

const hash = new Buffer("sha1=" + crypto.enc.Hex.stringify(hmac));
const reqhash = new Buffer(req.get("X-Hub-Signature"));

return bufeq(hash, reqhash);
}

function handleRequest(req, res)
{
if(req.body == null || (vhost && req.hostname != vhost)) return;

if(env.GITHUB_SECRET && !verifyRequest(req))
res.sendStatus(500);
else
{
const event = req.get("X-GitHub-Event");

if(event)
{
Events[event](JSON.parse(req.body));
res.sendStatus(200);
}
else
res.sendStaus(400);
}
}

function main()
{
express()
.use(bodyParser.text({type: "application/json"}))
.post("/", handleRequest)
.listen(port);
}

main();

Loading…
Cancel
Save