Skip to content

Commit

Permalink
FInally figured out the slack attachments thing
Browse files Browse the repository at this point in the history
Ok, so fields are for tiny bits of extra data.. not the message text.

That works much better.
  • Loading branch information
clonemeagain committed Oct 14, 2017
1 parent bf4a3fc commit 5ff589a
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 49 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ This plugin uses CURL and tested on osTicket-1.10.1
- Select "Authorize"
- Scroll down and copy the Webhook URL entirely, paste this into the Plugin config.

If you want to add the Department as a field in each slack notice, tick the Checkbox in the Plugin config.

The channel you select will receive an event notice, like:
```
Aaron [10:56 AM] added an integration to this channel: osTicket Notification
Expand All @@ -37,10 +39,10 @@ Create a ticket!

You should see something like the following appear in your Slack channel:

![slack-new-ticket](https://user-images.githubusercontent.com/5077391/31572028-5b8f69ce-b0e8-11e7-86f0-d5a4cef2b98e.png)
![slack-new-ticket](https://user-images.githubusercontent.com/5077391/31572647-923e07b0-b0f6-11e7-9515-98205d6f800f.png)

When a user replies, you'll get something like:

![slack-reply](https://user-images.githubusercontent.com/5077391/31572029-5d1144e8-b0e8-11e7-9fad-cc5204b0ca64.png)
![slack-reply](https://user-images.githubusercontent.com/5077391/31572648-9279eb18-b0f6-11e7-97da-9a9c63a200d4.png)

Notes, Replies from Agents and System messages shouldn't appear.
4 changes: 4 additions & 0 deletions config.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ function getOptions() {
'length' => 200
),
)),
'display-dept' => new BooleanField([
'label' => $__('Add field for Department in Slack notice'),
'default' => FALSE,
])
);
}

Expand Down
187 changes: 140 additions & 47 deletions slack.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,30 @@
require_once(INCLUDE_DIR . 'class.ticket.php');
require_once(INCLUDE_DIR . 'class.osticket.php');
require_once(INCLUDE_DIR . 'class.config.php');
require_once(INCLUDE_DIR . 'class.format.php');
require_once('config.php');

class SlackPlugin extends Plugin {

var $config_class = "SlackPluginConfig";

/**
* The entrypoint of the plugin, keep short, always runs.
*/
function bootstrap() {
// Listen for osTicket to tell us it's made a new ticket or updated
// an existing ticket:
Signal::connect('ticket.created', array($this, 'onTicketCreated'));
Signal::connect('threadentry.created', array($this, 'onTicketUpdated'));
}

/**
* What to do with a new Ticket?
*
* @global OsticketConfig $cfg
* @param Ticket $ticket
* @return type
*/
function onTicketCreated(Ticket $ticket) {
global $cfg;
if (!$cfg instanceof OsticketConfig) {
Expand All @@ -26,6 +39,7 @@ function onTicketCreated(Ticket $ticket) {
// Convert any HTML in the message into text
$plaintext = Format::html2text($ticket->getMessages()[0]->getBody()->getClean());

// Format the messages we'll send.
$heading = sprintf('%s CONTROLSTART%sscp/tickets.php?id=%d|#%s - %sCONTROLEND %s'
, __("New Ticket")
, $cfg->getBaseUrl()
Expand All @@ -38,9 +52,16 @@ function onTicketCreated(Ticket $ticket) {
, $ticket->getName()
, $ticket->getEmail()
, "\n\n" . $plaintext);
$this->sendToSlack($ticket, $heading, $body);
$this->sendToSlack($ticket, $heading, $plaintext);
}

/**
* What to do with an Updated Ticket?
*
* @global OsticketConfig $cfg
* @param ThreadEntry $entry
* @return type
*/
function onTicketUpdated(ThreadEntry $entry) {
global $cfg;
if (!$cfg instanceof OsticketConfig) {
Expand All @@ -51,16 +72,20 @@ function onTicketUpdated(ThreadEntry $entry) {
// this was a reply or a system entry.. not a message from a user
return;
}
$ticket = $this->getTicket($entry);

// Need to fetch the ticket from the ThreadEntry
$ticket = $this->getTicket($entry);

// Check to make sure this entry isn't the first (ie: a New ticket)
$first_entry = $ticket->getMessages()[0];
if ($entry->getId() == $first_entry->getId()) {
// don't post the same thing twice.. let onCreated handle it.
return;
}

// Convert any HTML in the message into text
$plaintext = Format::html2text($entry->getBody()->getClean());

// Format the messages we'll send
$heading = sprintf('%s CONTROLSTART%sscp/tickets.php?id=%d|#%s %sCONTROLEND %s'
, __("Ticket")
, $cfg->getBaseUrl()
Expand All @@ -75,47 +100,90 @@ function onTicketUpdated(ThreadEntry $entry) {
, __('in')
, $ticket->getDeptName()
, "\n\n" . $plaintext);
$this->sendToSlack($ticket, $heading, $body, 'warning');
$this->sendToSlack($ticket, $heading, $plaintext, 'warning');
}

/**
* A helper function that sends messages to slack endpoints.
*
* @global osTicket $ost
* @global OsticketConfig $cfg
* @param Ticket $ticket
* @param string $heading
* @param string $body
* @param string $colour
* @throws \Exception
*/
function sendToSlack(Ticket $ticket, $heading, $body, $colour = 'good') {
global $ost, $cfg;
if (!$ost instanceof osTicket || !$cfg instanceof OsticketConfig) {
error_log("Slack plugin called too early.");
return;
}
$url = $this->getConfig()->get('slack-webhook-url');
if (!$url) {
$ost->logError('Slack Plugin not configured', 'You need to read the Readme and configure a webhook URL before using this.');
}

// Obey message formatting rules:https://api.slack.com/docs/message-formatting
$formatter = ['<' => '&lt;', '>' => '&gt;', '&' => '&amp;'];
$heading = str_replace(array_keys($formatter), array_values($formatter), $heading);
$body = str_replace(array_keys($formatter), array_values($formatter), $body);
// put the <>'s control characters back in
$moreformatter = ['CONTROLSTART' => '<', 'CONTROLEND' => '>'];
$heading = str_replace(array_keys($moreformatter), array_values($moreformatter), $heading);
$body = str_replace(array_keys($moreformatter), array_values($moreformatter), $body);
$heading = $this->format_text($heading);
$body = $this->format_text($body);

try {
$payload['attachments'][] = [
'pretext' => $heading,
'fallback' => $heading,
'color' => $colour,
"author" => $ticket->getName(),
"author_link" => $cfg->getBaseUrl() . 'scp/users.php?id=' . $ticket->getOwner()->getId(),
'ts' => Misc::gmtime(),
'footer' => __('Department') . ': ' . $ticket->getDeptName() . ' -> ' . $ticket->getTopic(),
"fields" => [
[
"title" => $ticket->getSubject(),
"title_link" => $cfg->getBaseUrl() . 'scp/tickets.php?id=' . $ticket->getId(),
"value" => $body,
"short" => false,
]
]
// Build the payload with the formatted data:
$payload['attachments'][0] = [
'pretext' => $heading,
'fallback' => $heading,
'color' => $colour,
'title' => $ticket->getSubject(),
'title_link' => $cfg->getBaseUrl() . 'scp/tickets.php?id=' . $ticket->getId(),
'ts' => Misc::gmtime(),
'footer' => 'via osTicket Slack Plugin',
'footer_icon' => 'https://platform.slack-edge.com/img/default_application_icon.png',
'text' => $body,
'fields' => [
[
'title' => __('User'),
'value' => '<' . $cfg->getBaseUrl() . 'scp/users.php?id=' . $ticket->getOwnerId() . '|' . $ticket->getName() . '> (' . $ticket->getEmail() . ')',
'short' => TRUE,
],
[
'title' => __('Priority'),
'value' => $ticket->getPriority(),
'short' => TRUE,
],
[
'title' => __('Topic'),
'value' => str_replace('/', '-', $ticket->getTopic()),
'short' => TRUE,
],
]
];
// Add a field for tasks if there are open ones
if ($ticket->getNumOpenTasks()) {
$payload['attachments'][0]['fields'][] = [
'title' => __('Open Tasks'),
'value' => $ticket->getNumOpenTasks(),
'short' => TRUE,
];
}
// Change the colour to Fuschia if ticket is overdue
if ($ticket->isOverdue()) {
$payload['attachments'][0]['colour'] = '#ff00ff';
}

$data_string = utf8_encode(json_encode($payload));
$url = $this->getConfig()->get('slack-webhook-url');
if ($this->getConfig()->get('display-dept')) {
$payload['attachments'][0]['fields'][] = [
'title' => __('Department'),
'value' => $ticket->getDeptName(),
'short' => TRUE,
];
}


// Format the payload:
$data_string = utf8_encode(json_encode($payload));

try {
// Setup curl
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
Expand All @@ -125,18 +193,23 @@ function sendToSlack(Ticket $ticket, $heading, $body, $colour = 'good') {
'Content-Length: ' . strlen($data_string))
);

// Actually send the payload to slack:
if (curl_exec($ch) === false) {
throw new \Exception($url . ' - ' . curl_error($ch));
} else {
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($statusCode != '200') {
throw new \Exception($url . ' Http code: ' . $statusCode);
throw new \Exception(
'Error sending to: ' . $url
. ' Http code: ' . $statusCode
. ' curl-error: ' . curl_errno($ch));
}
}
curl_close($ch);
} catch (\Exception $e) {
$ost->logError('Slack posting issue!', $e->getMessage(), true);
error_log('Error posting to Slack. ' . $e->getMessage());
} finally {
curl_close($ch);
}
}

Expand All @@ -146,20 +219,40 @@ function sendToSlack(Ticket $ticket, $heading, $body, $colour = 'good') {
* @param ThreadEntry $entry
* @return Ticket
*/
private static function getTicket(ThreadEntry $entry) {
static $ticket;
if (!$ticket) {
// aquire ticket from $entry.. I suspect there is a more efficient way.
$ticket_id = Thread::objects()->filter([
'id' => $entry->getThreadId()
])->values_flat('object_id')->first() [0];

// Force lookup rather than use cached data..
$ticket = Ticket::lookup(array(
'ticket_id' => $ticket_id
));
}
return $ticket;
function getTicket(ThreadEntry $entry) {
$ticket_id = Thread::objects()->filter([
'id' => $entry->getThreadId()
])->values_flat('object_id')->first() [0];

// Force lookup rather than use cached data..
// This ensures we get the full ticket, with all
// thread entries etc..
return Ticket::lookup(array(
'ticket_id' => $ticket_id
));
}

/**
* Formats text according to the
* formatting rules:https://api.slack.com/docs/message-formatting
*
* @param string $text
* @return string
*/
function format_text($text) {
$formatter = [
'<' => '&lt;',
'>' => '&gt;',
'&' => '&amp;'
];
$formatted_text = str_replace(array_keys($formatter), array_values($formatter), $text);
// put the <>'s control characters back in
$moreformatter = [
'CONTROLSTART' => '<',
'CONTROLEND' => '>'
];
// Replace the CONTROL characters, and limit text length to 500 characters.
return substr(str_replace(array_keys($moreformatter), array_values($moreformatter), $formatted_text), 0, 500);
}

}

0 comments on commit 5ff589a

Please sign in to comment.