Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration of issues addressed by jpklein #9

Open
wants to merge 99 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
746831e
Create settings template file in config directory
jpklein Sep 4, 2014
7cbbf27
Consolidate settings vars in config template file
jpklein Sep 4, 2014
42d8240
Add missing config-file require to memegenerator script
jpklein Sep 4, 2014
a6f9129
Simplify configuration of incoming webhook
jpklein Sep 4, 2014
b2899b0
Remove define-like variables from config file
jpklein Sep 4, 2014
42ecc51
Update README.md with installation instructions
Sep 4, 2014
3007dbd
Fix php tags, trailing whitespace, and terminal newlines
jpklein Sep 4, 2014
69b2a53
Convert indentation to tabs
jpklein Sep 4, 2014
6f49930
Standardize linespacing in/between function declarations
jpklein Sep 4, 2014
868fdc2
Reformat to modified PEAR coding standards
jpklein Sep 4, 2014
79252c1
Revert overzealous comment reformatting
jpklein Sep 4, 2014
13c1ec7
Parameterize userlink creation
jpklein Sep 4, 2014
88aa709
WIP! Create cron-job script file
Sep 4, 2014
c95dd79
Fix includes and define pseudoconstants in cron script
jpklein Sep 5, 2014
4596755
Define function to fetch latest conversation posts from Rally
jpklein Sep 5, 2014
a096dc4
Rearchitect function placement to hint at OO code structure
Sep 5, 2014
f48178b
Refactor function names for clarity
Sep 5, 2014
cdc95bf
Add Slack utility functions
Sep 5, 2014
de7aed9
Add Slack utility functions
Sep 5, 2014
705abb6
Add settings for rally cron job
Sep 5, 2014
874a855
Add settings for rally cron job
Sep 5, 2014
5edcee7
Post new-comment notifications to Slack
Sep 5, 2014
b732545
Add settings for rally cron job
jpklein Sep 6, 2014
cbd19ba
Add Slack utility functions
jpklein Sep 6, 2014
22fdf5e
Define rally cron job's main routine
jpklein Sep 6, 2014
a54b053
WIP! Fetch new and significantly-updated artifacts from Rally
jpklein Sep 6, 2014
7672173
Remove troubleshooting code from rally fetch
jpklein Sep 7, 2014
d160f9f
Post state-change notifications to Slack
jpklein Sep 7, 2014
a401027
Merge branch 'issue1'
jpklein Sep 7, 2014
1348f8a
Merge branch 'issue2'
jpklein Sep 7, 2014
aa788e8
Merge branch 'issue3'
jpklein Sep 7, 2014
a9ffe3e
Merge branch 'issue4'
jpklein Sep 7, 2014
71cdec5
Move rally cron script settings to centralized config file
jpklein Sep 7, 2014
9a3f830
Refactor global variables
Sep 8, 2014
1b6f5e3
Merge branch 'issue1'
Sep 8, 2014
55b2122
Update global variables in rally cron code
Sep 8, 2014
e1316d0
Separate rallyme script functions from library code
Sep 8, 2014
1e109fd
Remove unused library functions
Sep 8, 2014
ab23b9e
Merge branch 'issue10'
Sep 8, 2014
03fba94
Update library functions to use global variables
Sep 8, 2014
9c516bd
Refactor rally cron posting function
Sep 8, 2014
907eed9
Refactor rally cron posting function
Sep 8, 2014
ec428fa
Merge branch 'issue3'
Sep 8, 2014
3e0c9fc
Merge branch 'issue4'
Sep 8, 2014
4559f6a
Add an overview of rallybot notifications
Sep 22, 2014
202d9e5
Update README.md
Sep 22, 2014
553c952
Remove mention of unimplemented functionality from README.md
Sep 22, 2014
84b5cf0
Update README.md
Sep 22, 2014
c4effb9
Update README.md with note of new functionality
Sep 22, 2014
b42c20f
Merge remote-tracking branch 'jpklein/issue3'
Sep 22, 2014
5c3ed13
Merge remote-tracking branch 'jpklein/issue4'
Sep 22, 2014
31ce9c2
Update README.md
Sep 22, 2014
59d4f66
Fix bug introduced in ab23b9e (merge branch issue10)
Sep 22, 2014
5010c30
Fix bug introduced in a9ffe3e (merge branch issue4)
Sep 22, 2014
6461ba9
Update styling of state-change notifications (for mobile users)
Sep 22, 2014
12d12b6
Allow bot to link at-mentions in Rally comments to Slack users
Sep 22, 2014
83f6064
Merge branch 'issue3'
Sep 22, 2014
2e97576
Merge branch 'issue4'
Sep 22, 2014
4eba754
WIP! Rearchitect rallyme script to handle different output methods
Sep 29, 2014
69129a1
Return details of Rally defects as Slack-formatted JSON in response body
Sep 29, 2014
fe3994d
Fix public link to defect
Sep 29, 2014
1600e92
Move existing GetDefectPayload to rallyme include file
Sep 29, 2014
233f2b0
Re-engineer GetDefectPayload interface for Fetch routine
Sep 29, 2014
f185ecd
Remove unused & single-use variables in GetDefectPayload
Sep 29, 2014
6d2a966
Refactor defect link creation in GetDefectPayload
Sep 30, 2014
124b016
Rearrange field definitions & delete unused vars in GetDefectPayload
Sep 30, 2014
6386548
Rearrange code in GetDefectPayload for readability
Sep 30, 2014
ce2adc9
Remove unnecessary variable definitions in GetDefectPayload
Sep 30, 2014
8d3386a
Micro-optimize object declaration to piss off ircmaxell
Sep 30, 2014
39bb479
Update rallyme.inc.php
Sep 30, 2014
7263d8c
Move BuildUserLink to Slack library file
Sep 30, 2014
4fc5427
Generalize Slack's BuildUserLink
Sep 30, 2014
47fd3fd
Remove deprecated functions
Sep 30, 2014
c153a7f
Report user errors either via incoming webhook or response body
Sep 30, 2014
639b358
Exit on user error
Sep 30, 2014
837360f
Fix error in rallyme error-handler
Sep 30, 2014
e0aa70f
Enable posting two versions of defect info via incoming webhook
Oct 1, 2014
2837b9b
Remove deprecated functions
Oct 1, 2014
0ec67da
Remove deprecated functions
Oct 1, 2014
269a8d2
Add abstractions related to script execution for reuse/elaboration
Oct 1, 2014
d98c212
Remove short-form array declaration for back-compatibility
Oct 1, 2014
8b0c1d8
Remove short-form array declaration for back-compatibility
Oct 1, 2014
388e597
Move GetRequirementPayload to rallyme include file for re-engineering
Oct 1, 2014
99113c0
Refactor GetRequirementPayload to make it easier on my poor lizard brain
Oct 1, 2014
f8a5e59
Handle sending user story details via incoming webhook
Oct 1, 2014
56ff06f
Remove deprecated functions
Oct 1, 2014
0dda36d
Handle sending user story or task details via incoming webhook
Oct 1, 2014
8119d5c
Refactor settings variables and includes
jpklein Oct 1, 2014
b6ff156
Refactor rallyme includes
jpklein Oct 1, 2014
5448eae
Specify artifact type in query URL to reduce resultset from Rally
jpklein Oct 1, 2014
f539412
Refactor to abstract notion of artifact-payload header information
jpklein Oct 2, 2014
bdc71eb
Add troubleshooting message on webhook failure
jpklein Oct 2, 2014
c9dd514
Update display of stories
jpklein Oct 2, 2014
c1bf27f
Update display of tasks
jpklein Oct 2, 2014
b1b69d3
Filter info when field names added to command
jpklein Oct 2, 2014
95a14c4
Move Slack library functions out of rally file
jpklein Oct 2, 2014
6a1ee49
Allow truncated text to display read more link
jpklein Oct 2, 2014
1d25775
Update README.md with rallybot usage instructions
Oct 2, 2014
a97f0a4
Merge commit '1d257756a00aeef477e3d649163a1f288f357d47'
jpklein Oct 3, 2014
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
scripts/config/*
!scripts/config/default.config.php

59 changes: 44 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,61 @@
slack-integration
=================

A place where I put some integration scripts for the popular Slack messaging platform.
Some integration scripts for the popular Slack messaging platform.

## Features

#Rally Bot
### Rallybot Cron
Periodically pushes notifications from Rally into our team's Slack channel. Notifications are sent whenever:

We use this to query our Rally instance for defects, tasks and user stories. I have it configured to respond to a /rallyme slash command in Slack.
1. a comment is added to a ticket
2. a new defect, user story, or test case is created
3. a user story changes state

E.g.:
/rallyme DE12345
/rallyme US23456
/rallyme TA34567
The bot uses a combination of Rally's _state_ and _ready_ fields to track the progress of user stories. When a story's state is set to "In-Progress" and the ready field is checked, rallybot will announce that the story is ready for testing. When the QA team has completed testing, they may either: 1) uncheck the ready flag to have rallybot announce that the story needs work, or 2) set the story to "Completed" to notify the Product Owner that it is ready for acceptance.

Each of these returns a nicely formatted summary of the defect, story or task, including the owner/submitter, creation date, story title, project and description. For documents that have attachments, such as uploaded screenshots, the summary will include a link to the first available attachment.
> **Note**: Rally automatically sets a story's state to "Completed" when all of its tasks are completed, so be sure to leave at least one open task in order to correctly track stories with defects.

#Image Bot / Gif Bot
### Rallybot
We use this to query our Rally instance for defects, tasks and user stories. It can be configured to respond to a Slack [slash command](https://slack.zendesk.com/hc/en-us/articles/201259356-Slash-Commands) or to any messages that start with a Rally ticket's formatted id, e.g.:
```
/rallyme DE12345
/rallyme US23456 project
TA34567 owner description
```
Entering the id of an artifact without any arguments returns a nicely formatted summary of the defect, story or task, including the owner/submitter, creation date, story title, project and description. For documents that have attachments, such as uploaded screenshots, the summary will include a link to the first available attachment. Otherwise, adding one or more field names after the id will filter the summary to just display the requested fields.

### Image Bot / Gif Bot
This one is simple. It uses the google image search JSON API to query for images that match a keyword. It is configured to use the Safe Search feature, since we use this tool in the workplace. Being able to add some levity to our work chats with amusing images from the internet makes life more fun.

E.g.
```
/imageme kittens
/gifme roflcopter
```

### XKCD Bot
As geeks, we are well acquainted with the internet comic XKCD. This bot allows you to post an XKCD comic to the room, including the ALT text, which will be displayed beneath the image.

#XKCD Bot
E.g. `/xkcd 100` will return the "family circus" episode of XKCD.

As geeks, we are well acquainted with the internet comic XKCD. This bot allows you to post an XKCD comic to the room, including the ALT text, which will be displayed beneath the image.
## Installation

1. Clone this repository to your server

2. Create your config file from the default template:

```
cd scripts/config
cp default.config.php config.php
```

3. Create a new incoming webhook in Slack

**Note**: Leave the channel to post to set to "#general", our scripts use channel-overrides to respond wherever the user issues a slash command.

4. Edit your config file with the incoming webhook's unique webhook URL

5. Add a slash command to Slack for each feature (for example: "When a user enters /rallyme, POST to http://example.com/rallyme.php")

**Note**: The scripts will respond to requests over POST or GET.

E.g.
/xkcd 100 will return the "family circus" episode of XKCD.
6. If you are implementing Rally Bot, add your credentials to the config file
35 changes: 35 additions & 0 deletions scripts/config/default.config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

////////////////////
// Slack settings //
////////////////////

$SLACK_INCOMING_HOOK_URL = 'REPLACE ME'; //unique webhook URL including 'https://' and token value
$SLACK_OUTGOING_HOOK_TOKEN = 'REPLACE ME'; //used to validate requests coming from Slack

$SLACK_SUBDOMAIN = 'REPLACE ME'; //subdomain of slack.com used by your team, e.g.: 'cim'

////////////////////
// Rally settings //
////////////////////

$RALLY_USERNAME = 'REPLACE ME';
$RALLY_PASSWORD = 'REPLACE ME';

$RALLYBOT_NAME = 'rallybot';
$RALLYBOT_ICON = 'https://yt3.ggpht.com/-vkXOTHhRGck/AAAAAAAAAAI/AAAAAAAAAAA/IBjv0oYIm5Q/s100-c-k-no/photo.jpg';

////////////////////////
// Rallycron settings //
////////////////////////

$RALLYCRON_PROJECT_ID = REPLACE_ME; //number after '#/' in URL of the rally project to track
$RALLYCRON_CHANNEL = 'REPLACE ME'; //slack channel to post to; do not include hash symbol

$CRON_INTERVAL = 61; //seconds between cron runs; pad for script run time and latency

//////////////////////
// Rallyme settings //
//////////////////////

$RALLYME_DISPLAY_VERSION = 2; //displays different fields for fetched artifacts
19 changes: 8 additions & 11 deletions scripts/gifme.php
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
<?
<?php
require('include/curl.php');
require('include/slack.php');
require('include/slack.config.php');
require('config/config.php');

$command = BuildSlashCommand($_REQUEST);

$hook = $config['slack']['hook'];

//use one or the other of $emoji or $iconurl
$emoji = ":camera:";
$iconurl = null;

$enc = urlencode($command->Text);

$imageSearchJson = get_url_contents('http://ajax.googleapis.com/ajax/services/search/images?v=1.0&safe=active&as_filetype=gif&rsz=8&imgsz=medium&q=animated+'.$enc);
$imageSearchJson = get_url_contents('http://ajax.googleapis.com/ajax/services/search/images?v=1.0&safe=active&as_filetype=gif&rsz=8&imgsz=medium&q=animated+' . $enc);

$imageresponse = json_decode($imageSearchJson);

$userlink = "<https://cim.slack.com/team/{$command->UserName}|{$command->UserName}>";
$userlink = '<https://' . $SLACK_SUBDOMAIN . '.slack.com/team/' . $command->UserName . '|' . $command->UserName . '>';

if($imageresponse->responseData == null){
if ($imageresponse->responseData == null) {
//{"responseData": null, "responseDetails": "qps rate exceeded", "responseStatus": 503}
$details = $imageresponse->responseDetails;
$status = $imageresponse->responseStatus;
Expand All @@ -29,12 +27,11 @@
die;
}

$whichImage = rand(0,7);
$whichImage = rand(0, 7);
$returnedimageurl = $imageresponse->responseData->results[$whichImage]->url;

$payload = "@{$userlink} asked for '{$command->Text}'\n{$returnedimageurl}";

$ret = slack_incoming_hook_post($hook, "gifbot", $command->ChannelName, $iconurl, $emoji, $payload);
if($ret!="ok")
$ret = slack_incoming_hook_post($SLACK_INCOMING_HOOK_URL, "gifbot", $command->ChannelName, $iconurl, $emoji, $payload);
if ($ret != "ok")
print_r("@tdm, gifbot got this response when it tried to post to the incoming hook for /gifme.\n{$ret}");
?>
43 changes: 18 additions & 25 deletions scripts/imageme.php
Original file line number Diff line number Diff line change
@@ -1,60 +1,53 @@
<?
<?php
require('include/curl.php');
require('include/slack.php');
require('include/slack.config.php');
require('config/config.php');

$command = BuildSlashCommand($_REQUEST);

$hook = $config['slack']['hook'];

//use one or the other of $emoji or $iconurl
$emoji = ":camera:";
$iconurl = null;
$userlink = "<https://cim.slack.com/team/{$command->UserName}|{$command->UserName}>";
$userlink = '<https://' . $SLACK_SUBDOMAIN . '.slack.com/team/' . $command->UserName . '|' . $command->UserName . '>';
$maxtries = 2;
$tries = 0;


startover:

$imageresponse = RunImageSearch($command->Text);
$tries++;

if($imageresponse->responseData == null){
if ($imageresponse->responseData == null) {
//{"responseData": null, "responseDetails": "qps rate exceeded", "responseStatus": 503}
$details = $imageresponse->responseDetails;
$status = $imageresponse->responseStatus;

if($status == 503 && $tries < $maxtries)
{
sleep(1);
goto startover; //yeah, it's a goto. deal with it. http://xkcd.com/292/

if ($status == 503 && $tries < $maxtries) {
sleep(1);
goto startover; //yeah, it's a goto. deal with it. http://xkcd.com/292/
}

print_r("Sorry @{$userlink}, no image for you! [{$details}:{$status}]\n");
//print_r($imageresponse);
die;
}

$whichImage = rand(0,7);
$whichImage = rand(0, 7);
$returnedimageurl = $imageresponse->responseData->results[$whichImage]->url;

$payload = "@{$userlink} asked for '{$command->Text}'\n{$returnedimageurl}";

$ret = slack_incoming_hook_post($hook, "imagebot", $command->ChannelName, $iconurl, $emoji, $payload);
if($ret!="ok")
$ret = slack_incoming_hook_post($SLACK_INCOMING_HOOK_URL, "imagebot", $command->ChannelName, $iconurl, $emoji, $payload);
if ($ret != "ok")
print_r("@tdm, gifbot got this response when it tried to post to the incoming hook for /imageme.\n{$ret}");




function RunImageSearch($text)
{
$enc = urlencode($text);

$imageSearchJson = get_url_contents('http://ajax.googleapis.com/ajax/services/search/images?v=1.0&safe=active&rsz=8&imgsz=medium&q='.$enc);
$imageSearchJson = get_url_contents('http://ajax.googleapis.com/ajax/services/search/images?v=1.0&safe=active&rsz=8&imgsz=medium&q=' . $enc);

$imageresponse = json_decode($imageSearchJson);

$imageresponse = json_decode($imageSearchJson);

return $imageresponse;
return $imageresponse;
}
?>
43 changes: 23 additions & 20 deletions scripts/include/curl.php
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
<?
<?php
//HTTP Utility Methods

function get_url_contents($url) {
$crl = curl_init($url);
function get_url_contents($url)
{
$crl = curl_init($url);

curl_setopt($crl, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)');
curl_setopt($crl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($crl, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($crl, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)');
curl_setopt($crl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($crl, CURLOPT_CONNECTTIMEOUT, 5);

$response = curl_exec($crl);
curl_close($crl);
return $response;
$response = curl_exec($crl);
curl_close($crl);
return $response;
}

function get_url_contents_with_basicauth($url, $username, $password) {
$crl = curl_init();
function get_url_contents_with_basicauth($url, $username, $password)
{
$crl = curl_init();

curl_setopt($crl, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)');
curl_setopt($crl, CURLOPT_URL, $url);
curl_setopt($crl, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)');
curl_setopt($crl, CURLOPT_URL, $url);
curl_setopt($crl, CURLOPT_USERPWD, "{$username}:{$password}");
curl_setopt($crl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($crl, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($crl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($crl, CURLOPT_CONNECTTIMEOUT, 5);

$ret = curl_exec($crl);
curl_close($crl);
return $ret;
$ret = curl_exec($crl);
curl_close($crl);
return $ret;
}

function curl_post($uri, $data)
Expand All @@ -33,10 +35,11 @@ function curl_post($uri, $data)
curl_setopt($crl, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($crl, CURLOPT_POSTFIELDS, $data);
curl_setopt($crl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($crl, CURLOPT_HTTPHEADER, array('Content-Length: ' . strlen($data)));
curl_setopt($crl, CURLOPT_HTTPHEADER, array(
'Content-Length: ' . strlen($data)
));

$response = curl_exec($crl);
curl_close($crl);
return $response;
}
?>
39 changes: 18 additions & 21 deletions scripts/include/googleimage.php
Original file line number Diff line number Diff line change
@@ -1,39 +1,36 @@
<?
<?php
/*
built based on the docs here:
https://developers.google.com/image-search/v1/jsondevguide
*/


function BuildSearchCommand($query, $size, $maxresults, $safe, $filetype)
{
$cmd = new stdClass();
$cmd->Size = $size;
$cmd->Query = $query;
$cmd->Count = $maxresults;
$cmd->Safe = $safe;
$cmd->FileType = $filetype;
return $cmd;

$cmd = new stdClass();
$cmd->Size = $size;
$cmd->Query = $query;
$cmd->Count = $maxresults;
$cmd->Safe = $safe;
$cmd->FileType = $filetype;

return $cmd;
}

function GetImageSearchResponse($cmd)
{

$googleImageSearch = "http://ajax.googleapis.com/ajax/services/search/images?v=1.0&safe={$cmd->Safe}&as_filetype={$cmd->FileType}&rsz={$cmd->Count}&imgsz={$cmd->Size}&q={$cmd->Query}";

$result = CallAPI($googleImageSearch);
$googleImageSearch = "http://ajax.googleapis.com/ajax/services/search/images?v=1.0&safe={$cmd->Safe}&as_filetype={$cmd->FileType}&rsz={$cmd->Count}&imgsz={$cmd->Size}&q={$cmd->Query}";

return $result;
$result = CallAPI($googleImageSearch);

return $result;
}

function GetRandomResultFromResponse($n, $result)
{
$resultArray = $result->responseData->results;
$count = count($resultArray);
$item = rand(0,$count-1);
return $resultArray[$item];
$resultArray = $result->responseData->results;
$count = count($resultArray);
$item = rand(0, $count - 1);
return $resultArray[$item];
}

?>
16 changes: 8 additions & 8 deletions scripts/include/log.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<?
<?php

function mylog($logfile, $payload)
{
$fh = fopen($logfile,"ra+");
fwrite($fh,time()."\n");
fwrite($fh,$payload);
fwrite($fh,"\n____________________\n");
fclose($fh);
$fh = fopen($logfile, "ra+");
fwrite($fh, time() . "\n");
fwrite($fh, $payload);
fwrite($fh, "\n____________________\n");

fclose($fh);
}
?>
Loading