Skip to content

Instantly share code, notes, and snippets.

@pstephenson02
Last active February 18, 2016 04:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pstephenson02/05a6dd1d35fa40999a44 to your computer and use it in GitHub Desktop.
Save pstephenson02/05a6dd1d35fa40999a44 to your computer and use it in GitHub Desktop.
Phil Stephenson's Code Samples!
import HipchatterBase from 'hipchatter';
import trebek from './trebek';
import winston from 'winston';
import fetch from 'node-fetch';
import moment from 'moment';
import App from './models/App';
import { HIPCHAT_CALLBACK_URL } from './config';
export default class Hipchatter extends HipchatterBase {
get_session(callback) {
this.request('get', 'oauth/token/'+this.token, callback);
}
}
export default class HipchatBot {
constructor(express) {
this.express = express;
this.buildCapabilitiesDescriptor = this.buildCapabilitiesDescriptor.bind(this);
this.install = this.install.bind(this);
this.uninstall = this.uninstall.bind(this);
this.onMessage = this.onMessage.bind(this);
this.onRoomEnter = this.onRoomEnter.bind(this);
this.onError = this.onError.bind(this);
this.say = this.say.bind(this);
this.start();
}
async start() {
this.express.get('/capabilities', this.buildCapabilitiesDescriptor);
this.express.post('/install', this.install);
this.express.delete('/install/*', this.uninstall);
this.express.post('/room_message', this.onMessage);
this.express.post('/room_enter', this.onRoomEnter);
this.app = await App.get();
if (!this.app.hipchat.oauthId || !this.app.hipchat.oauthSecret) {
throw new Error('Before you can run Jeopardy Bot on Hipchat, you must first install it as an add-on.');
}
await this.validateToken(this.app.hipchat);
this.hipchatter = new Hipchatter(this.app.hipchat.accessToken.token);
const roomId = await this.getInstalledRoomId();
this.registerWebhook(roomId, 'room_message');
this.registerWebhook(roomId, 'room_enter');
}
getInstalledRoomId() {
return new Promise(resolve => {
this.hipchatter.get_session((err, session) => {
resolve(session.client.room.id);
});
});
}
async validateToken(hipchat) {
if (!hipchat.accessToken.token || moment().isAfter(hipchat.accessToken.expires_in)) {
const { access_token: token, expires_in: expires } = await this.getAccessToken(hipchat.oauthId, hipchat.oauthSecret);
this.app.hipchat.accessToken.token = token;
this.app.hipchat.accessToken.expires = moment().add(expires, 'seconds');
await this.app.save();
}
}
buildCapabilitiesDescriptor(req, res) {
const descriptor = {
"name": "Jeopardy Bot",
"description": "This is Jeopardy!",
"key": "com.jeopardy.bot",
"links": {
"homepage": "https://github.com/kesne/jeopardy-bot",
"self": HIPCHAT_CALLBACK_URL + "/capabilities",
},
"capabilities": {
"hipchatApiConsumer": {
"scopes": [
"admin_room",
"send_notification",
]
},
"installable": {
"callbackUrl": HIPCHAT_CALLBACK_URL + "/install",
"allowGlobal": false,
"allowRoom": true,
},
},
}
res.send(descriptor);
}
async install(req, res) {
const { capabilitiesUrl, oauthId, roomId, oauthSecret } = req.body;
const tokenUrl = await this.getTokenUrl(capabilitiesUrl);
const { access_token: token, expires_in: expires } = await this.getAccessToken(oauthId, oauthSecret, tokenUrl);
this.app.hipchat.oauthId = oauthId;
this.app.hipchat.oauthSecret = oauthSecret;
this.app.hipchat.accessToken.token = token;
this.app.hipchat.accessToken.expires = moment().add(expires, 'seconds');
await this.app.save();
winston.info(`JeopardyBot successfully installed in room ${roomId}.`);
res.sendStatus(200);
this.start();
}
uninstall(req, res) {
// Hipchat will cleanup any webhooks created with this token, so no need to manually do that
this.app.collection.updateOne({ _id: this.app._id }, {
$unset: {
'hipchat.oauthId': this.app.hipchat.oauthId,
'hipchat.oauthSecret': this.app.hipchat.oauthSecret,
'hipchat.accessToken.token': this.app.hipchat.accessToken.token,
},
$pullAll: {
webhooks: this.app.hipchat.webhooks,
},
}, async (err, result) => {
if (err === null) {
await this.app.save();
res.sendStatus(200);
} else {
this.onError(err);
}
});
}
getTokenUrl(tokenUrl) {
return fetch(tokenUrl)
.then(res => res.json())
.then(json => json.capabilities.oauth2Provider.tokenUrl);
}
getAccessToken(oauthId, oauthSecret, tokenUrl = 'https://api.hipchat.com/v2/oauth/token') {
const auth = new Buffer(`${oauthId}:${oauthSecret}`).toString('base64');
return fetch(tokenUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic '+auth,
},
body: 'grant_type=client_credentials&scope[]=admin_room&scope[]=send_notification'
}).then(res => res.json());
}
onMessage(req) {
const {
event: subtype,
item: {
room: { name: channel_name, },
message: {
message: text,
from: {
id: user_id,
mention_name: user_name,
},
},
},
} = req.body;
this.channelName = req.body.item.room.name;
const channel_id = (req.body.item.room.id).toString();
const timestamp = Date.parse(req.body.item.message.date) / 1000;
trebek(text, {
subtype,
channel_name,
channel_id,
timestamp,
user_id,
user_name,
}, this.say);
}
onRoomEnter(req) {
const {
event: subtype,
item: {
room: { name: channel_name, },
sender: {
id: user_id,
mention_name: user_name,
},
},
} = req.body;
this.channelName = req.body.item.room.name;
const channel_id = (req.body.item.room.id).toString();
// The room_enter payload does not include a timestamp
const timestamp = moment();
// Just to be consistent with the slack api
// https://api.slack.com/events/message/channel_join
const text = `@${user_name} has joined the channel`;
trebek(text, {
subtype,
channel_name,
channel_id,
timestamp,
user_id,
user_name,
}, this.say);
}
say(message, url = '') {
if (url) {
message += `<br /><img src="${url}" width="420" height="259" /><br />`;
}
this.validateToken(this.app.hipchat);
this.hipchatter.notify(this.channelName, { message: message, token: this.app.hipchat.accessToken.token }, this.onError);
}
async registerWebhook(roomId, event) {
for (let webhookId of this.app.hipchat.webhooks) {
let wh;
try {
wh = await this.validateWebhook(roomId, webhookId, event);
} catch (err) {
this.onError(err);
}
if (wh) {
return;
}
}
// If we get here we don't yet have a valid webhook, so let's create one
winston.error('No valid webhooks found. Creating a new one...');
this.hipchatter.create_webhook(roomId, {
url: `${HIPCHAT_CALLBACK_URL}/${event}`,
event: event,
}, (err, webhook) => {
if (err === null) {
this.app.hipchat.webhooks.push(webhook.id);
winston.info(`Successfully created ${event} webhook with id: ${webhook.id}.`);
this.app.save();
} else {
this.onError(err);
}
});
}
validateWebhook(roomId, webhookId, event) {
return new Promise((resolve, reject) => {
this.hipchatter.get_webhook(roomId, webhookId, (err, webhook) => {
if (err === null && webhook.event === event && webhook.url === `${HIPCHAT_CALLBACK_URL}/${event}`) {
winston.info(`Existing ${event} webhook ${webhook.id} found and valid.`);
resolve(webhook);
} else {
reject(`${webhookId} is not a valid ${event} webhook for room ${roomId}`);
}
});
});
}
onError(err) {
if (err) winston.error(err);
}
}
<?php
namespace ICC\VoteBundle\Controller;
use FOS\RestBundle\Controller\Annotations\RouteResource;
use FOS\RestBundle\Controller\Annotations\QueryParam;
use FOS\RestBundle\Controller\Annotations\View;
use FOS\RestBundle\Util\Codes;
use FOS\RestBundle\View\View as FOSView;
use ICC\UtilityBundle\Controller\AbstractController;
use ICC\VoteBundle\Entity\ManualBallotOption;
use Symfony\Component\HttpFoundation\Request;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use ICC\VoteBundle\Model\ManualVoteInterface;
use FOS\RestBundle\Request\ParamFetcherInterface;
use ICC\AvectraBundle\Entity\User;
use FOS\RestBundle\Controller\Annotations\Get;
/**
* Class ManualPCHVoteController
* @RouteResource("ManualPCHVote")
*/
class ManualPCHVoteController extends AbstractController
{
/**
* Get a ManualPCHVote entity
*
* @param int $id
* @View(serializerEnableMaxDepthChecks=true)
*
* @ApiDoc(
* resource = true,
* description = "Gets a ManualPCHVote for a given id",
* output = "ICC\VoteBundle\Entity\ManualPCHVote",
* statusCodes = {
* 200 = "Returned when successful",
* 404 = "Returned when the vote is not found"
* }
* )
*
* @return ManualVoteInterface
*
* @throws NotFoundHttpException
*/
public function getAction($id)
{
if (!($vote = $this->container->get('vote.manual_vote_manager')->get($id))) {
throw new NotFoundHttpException(sprintf('The resource \'%s\' was not found.', $id));
}
return $vote;
}
/**
* @param int $mbo_id
*
* @Get("/manualpchvote/{mbo_id}")
*
* @return mixed
*/
public function getVoteByMboAction($mbo_id)
{
$user_id = $this->container->get('security.context')->getToken()->getUser()->getId();
return $this->container->get('vote.manual_vote_manager')->getVoteByManualBallotOption($mbo_id, $user_id);
}
/**
* List all votes.
*
* @ApiDoc(
* resource = true,
* statusCodes = {
* 200 = "Returned when successful"
* }
* )
*
* @QueryParam(name="offset", requirements="\d+", nullable=true, description="Offset from which to start listing votes.")
* @QueryParam(name="limit", requirements="\d+", default="5", description="How many votes to return.")
*
* @param ParamFetcherInterface $paramFetcher param fetcher service
*
* @return array
*/
public function getVotesAction(ParamFetcherInterface $paramFetcher)
{
$offset = $paramFetcher->get('offset');
$offset = null == $offset ? 0 : $offset;
$limit = $paramFetcher->get('limit');
return $this->container->get('vote.manual_vote_manager')->all($limit, $offset);
}
/**
* Create ManualPCHVote(s).
*
* @View(statusCode=201, serializerEnableMaxDepthChecks=true)
*
* @ApiDoc(
* resource = true,
* description = "Creates new ManualPCHVote(s) given ManualBallotOption id, User id, and vote (ManualPCHVote::SUPPORT/OPPOSE).",
* requirements = {
* {"name"="user", "dataType"="integer", "required"=true, "description"="Avectra User id"},
* {"name"="manualBallotOption", "dataType"="integer", "required"=true, "description"="ManualBallotOption id"},
* {"name"="vote", "dataType"="integer", "required"=true, "description"="1 (support) or 2 (oppose)"}
* },
* statusCodes = {
* 200 = "Returned when successful",
* 400 = "Returned when the form has errors"
* }
* )
*
* @param Request $request
*
* @return View
*/
public function postAction(Request $request)
{
$votePermissions = $this->container->get('vote.permissions');
$user = $votePermissions->getUser();
$votes = $errorData = array();
if (!$votePermissions->canUserVote()) {
return $this->createJsonResponse(false, 'vote.permission.not_allowed', Codes::HTTP_FORBIDDEN);
}
if (is_array($request->request->all())) {
foreach ($request->request->all() as $vote) {
$pin = isset($vote['user']) ? $vote['user'] : false;
if (!$votePermissions->canSubmitManualVote($pin, $errorData)) {
// $vote['user'] actually contains the pin
return $this->createJsonResponse(
false,
$errorData['message'],
200,
isset($errorData['data']) ? $errorData['data'] : array()
);
}
$vote['user'] = $user->getId();
$votes[] = $this->container->get('vote.manual_vote_manager')->post($vote);
}
} else {
throw new HttpException(400);
}
$response = array(
'message_class' => 'ajax-success',
'data' => array(
'pin_invalid' => false,
)
);
return $this->createJmsResponse($response, true, 'vote.success');
}
}
<?php
namespace ICC\VoteBundle\Tests\Controller;
use ICC\AvectraBundle\Service\MockUsers;
use ICC\SettingBundle\Entity\Setting;
use ICC\TestBundle\Lib\Test\WebDoctrineTestCase;
use ICC\VoteBundle\Entity\ManualPCHVote;
class ManualPCHVoteControllerTest extends WebDoctrineTestCase
{
protected $avectraUserProvider;
public function setUp()
{
parent::setUp();
static::$client->followRedirects();
$this->getContainer()->get('setting.manager')->set(Setting::VOTING_ENABLED, true);
$this->avectraUserProvider = $this->getContainer()->get('avectra_user_provider');
}
public function testJsonGetManualPCHVoteAction()
{
$votes = static::$entityManager->getRepository('VoteBundle:ManualPCHVote')->findAll();
$manualPCHVote = $votes[rand(0, count($votes)-1)]; // Get a random vote
$route = $this->getUrl('api_get_manualpchvote', array('id' => $manualPCHVote->getId(), '_format' => 'json'));
static::$client->request('GET', $route, array('ACCEPT' => 'application/json'));
$response = static::$client->getResponse();
$this->assertJsonResponse($response, 200);
$content = $response->getContent();
$decoded = json_decode($content, true);
$this->assertTrue(isset($decoded['id']));
}
public function testJsonPostManualPCHVoteAction()
{
$this->logIn(MockUsers::PCH_USER);
$voter = static::$entityManager->getRepository('AvectraBundle:User')->findOneBy(array('firstName' => 'Long Beach Clicker'));
$manualBallotOptions = static::$entityManager->getRepository('VoteBundle:ManualBallotOption')->findAll();
$manualBallotOption = $manualBallotOptions[rand(0, count($manualBallotOptions)-1)];
$json_data = array(
'manualBallotOption' => $manualBallotOption->getId(),
'user' => $this->avectraUserProvider->getPin($voter),
'vote' => ManualPCHVote::SUPPORT
);
static::$client->request(
'POST',
'/api/manualpchvotes.json',
array(),
array(),
array('CONTENT_TYPE' => 'application/json'),
json_encode(array($json_data))
);
$this->assertJsonResponse(static::$client->getResponse(), 200, false);
}
public function testJsonBulkPostVotesAction()
{
$this->logIn(MockUsers::PCH_USER);
$voter = static::$entityManager->getRepository('AvectraBundle:User')->findOneBy(array('firstName' => 'Long Beach Clicker'));
$manualBallotOptions = static::$entityManager->getRepository('VoteBundle:ManualBallotOption')->findAll();
$json_data = array();
foreach ($manualBallotOptions as $manualBallotOption) {
$json_data[] = array(
'manualBallotOption' => $manualBallotOption->getId(),
'user' => $this->avectraUserProvider->getPin($voter),
'vote' => rand(ManualPCHVote::SUPPORT, ManualPCHVote::OPPOSE)
);
}
static::$client->request(
'POST',
'/api/manualpchvotes.json',
array(),
array(),
array('CONTENT_TYPE' => 'application/json'),
json_encode($json_data)
);
$this->assertJsonResponse(static::$client->getResponse(), 200, false);
}
public function testJsonPostManualPCHVoteActionShouldReturn400()
{
$this->logIn(MockUsers::PCH_USER);
$invalid_json_data = array(
'manualBallotOptions' => 2,
'users' => 1,
'vote' => ManualPCHVote::SUPPORT
);
static::$client->request(
'POST',
'/api/manualpchvotes.json',
array(),
array(),
array('CONTENT_TYPE' => 'application/json'),
$invalid_json_data
);
$this->assertJsonResponse(static::$client->getResponse(), 400, false);
}
protected function assertJsonResponse(
$response,
$statusCode = 200,
$checkValidJson = true,
$contentType = 'application/json'
) {
$this->assertEquals(
$statusCode, $response->getStatusCode(),
$response->getContent()
);
$this->assertTrue(
$response->headers->contains('Content-Type', $contentType),
$response->headers
);
if ($checkValidJson) {
$decode = json_decode($response->getContent());
$this->assertTrue(($decode != null && $decode != false),
'is response valid json: [' . $response->getContent() . ']'
);
}
}
public function tearDown()
{
static::$client->followRedirects(false);
$this->getContainer()->get('setting.manager')->set(Setting::VOTING_ENABLED, false);
parent::tearDown();
}
}
<?php
namespace ICC\VoteBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use ICC\AvectraBundle\Entity\User;
use ICC\VoteBundle\Model\ManualVoteInterface;
use JMS\Serializer\Annotation as JMS;
/**
* ManualPCHVote
*
* @ORM\Entity
* @ORM\HasLifecycleCallbacks
* @ORM\Table(name="cdp_manual_pch_vote")
* @ORM\Entity(repositoryClass="ICC\VoteBundle\Entity\ManualPCHVoteRepository")
* @JMS\ExclusionPolicy("all")
*/
class ManualPCHVote implements ManualVoteInterface
{
const SUPPORT = 1;
const OPPOSE = 2;
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*
* @JMS\Expose
* @JMS\Type("integer");
*/
protected $id;
/**
* @var ManualBallotOption
*
* @ORM\ManyToOne(targetEntity="ManualBallotOption")
* @ORM\JoinColumn(name="manual_ballot_option_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $manualBallotOption;
/**
* @var User
*
* @ORM\ManyToOne(targetEntity="ICC\AvectraBundle\Entity\User")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $user;
/**
* @var \DateTime
*
* @ORM\Column(name="created_at", type="datetime")
*/
protected $createdAt;
/**
* @var \DateTime
*
* @ORM\Column(name="edited_at", type="datetime")
*
* @JMS\Expose
* @JMS\Type("DateTime")
* @JMS\Groups({"mycdpaccess"})
*/
protected $editedAt;
/**
* @var int
* @ORM\Column(name="vote", type="integer")
* SUPPORT(1) or OPPOSE(2)
* @JMS\Expose
* @JMS\Type("integer");
*/
protected $vote;
/**
* @param ManualBallotOption $manualBallotOption
* @param User $user
* @param int $vote
*/
public function __construct(ManualBallotOption $manualBallotOption, User $user, $vote)
{
$this->manualBallotOption = $manualBallotOption;
$this->user = $user;
$this->vote = $vote;
}
/**
* @ORM\PrePersist
*/
public function setCreatedAtValue()
{
$this->setCreatedAt(new \DateTime());
$this->setEditedAt(new \DateTime());
}
/**
* @ORM\PreUpdate
*/
public function setPreUpdateValues()
{
$this->setEditedAt(new \DateTime());
}
/**
* {@inheritdoc}
*/
public function getId()
{
return $this->id;
}
/**
* {@inheritdoc}
*/
public function getManualBallotOption()
{
return $this->manualBallotOption;
}
/**
* {@inheritdoc}
*/
public function getUser()
{
return $this->user;
}
/**
* {@inheritdoc}
*/
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
/**
* {@inheritdoc}
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* {@inheritdoc}
*/
public function setEditedAt($editedAt)
{
$this->editedAt = $editedAt;
return $this;
}
/**
* {@inheritdoc}
*/
public function getEditedAt()
{
return $this->editedAt;
}
/**
* {@inheritdoc}
*/
public function getVote()
{
return $this->vote;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment