Page MenuHomec4science

PhabricatorIRCObjectNameHandler.php
No OneTemporary

File Metadata

Created
Sat, Aug 17, 20:04

PhabricatorIRCObjectNameHandler.php

<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Looks for Dxxxx, Txxxx and links to them.
*
* @group irc
*/
final class PhabricatorIRCObjectNameHandler extends PhabricatorIRCHandler {
/**
* Map of PHIDs to the last mention of them (as an epoch timestamp); prevents
* us from spamming chat when a single object is discussed.
*/
private $recentlyMentioned = array();
public function receiveMessage(PhabricatorIRCMessage $message) {
switch ($message->getCommand()) {
case 'PRIVMSG':
$reply_to = $message->getReplyTo();
if (!$reply_to) {
break;
}
$this->handleSymbols($message);
$message = $message->getMessageText();
$matches = null;
$pattern =
'@'.
'(?<!/)(?:^|\b)'. // Negative lookbehind prevent matching "/D123".
'(D|T|P|V|F)(\d+)'.
'(?:\b|$)'.
'@';
$pattern_override = '/(^[^\s]+)[,:] [DTPVF]\d+/';
$revision_ids = array();
$task_ids = array();
$paste_ids = array();
$commit_names = array();
$vote_ids = array();
$file_ids = array();
$matches_override = array();
if (preg_match_all($pattern, $message, $matches, PREG_SET_ORDER)) {
if (preg_match($pattern_override, $message, $matches_override)) {
$reply_to = $matches_override[1];
}
foreach ($matches as $match) {
switch ($match[1]) {
case 'D':
$revision_ids[] = $match[2];
break;
case 'T':
$task_ids[] = $match[2];
break;
case 'P':
$paste_ids[] = $match[2];
break;
case 'V':
$vote_ids[] = $match[2];
break;
case 'F':
$file_ids[] = $match[2];
break;
}
}
}
$pattern =
'@'.
'(?<!/)(?:^|\b)'.
'(r[A-Z]+[0-9a-z]{1,40})'.
'(?:\b|$)'.
'@';
if (preg_match_all($pattern, $message, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) {
$commit_names[] = $match[1];
}
}
$output = array();
if ($revision_ids) {
$revisions = $this->getConduit()->callMethodSynchronous(
'differential.query',
array(
'query' => 'revision-ids',
'ids' => $revision_ids,
));
$revisions = array_select_keys(
ipull($revisions, null, 'id'),
$revision_ids
);
foreach ($revisions as $revision) {
$output[$revision['phid']] =
'D'.$revision['id'].' '.$revision['title'].' - '.
$revision['uri'];
}
}
if ($task_ids) {
foreach ($task_ids as $task_id) {
if ($task_id == 1000) {
$output[1000] = 'T1000: A nanomorph mimetic poly-alloy'
.'(liquid metal) assassin controlled by Skynet: '
.'http://en.wikipedia.org/wiki/T-1000';
continue;
}
$task = $this->getConduit()->callMethodSynchronous(
'maniphest.info',
array(
'task_id' => $task_id,
));
$output[$task['phid']] = 'T'.$task['id'].': '.$task['title'].
' (Priority: '.$task['priority'].') - '.$task['uri'];
}
}
if ($vote_ids) {
foreach ($vote_ids as $vote_id) {
$vote = $this->getConduit()->callMethodSynchronous(
'slowvote.info',
array(
'poll_id' => $vote_id,
));
$output[$vote['phid']] = 'V'.$vote['id'].': '.$vote['question'].
' Come Vote '.$vote['uri'];
}
}
if ($file_ids) {
foreach ($file_ids as $file_id) {
$file = $this->getConduit()->callMethodSynchronous(
'file.info',
array(
'id' => $file_id,
));
$output[$file['phid']] = $file['objectName'].": ".$file['uri']." - ".
$file['name'];
}
}
if ($paste_ids) {
foreach ($paste_ids as $paste_id) {
$paste = $this->getConduit()->callMethodSynchronous(
'paste.info',
array(
'paste_id' => $paste_id,
));
// Eventually I'd like to show the username of the paster as well,
// however that will need something like a user.username_from_phid
// since we (ideally) want to keep the bot to Conduit calls...and
// not call to Phabricator-specific stuff (like actually loading
// the User object and fetching his/her username.)
$output[$paste['phid']] = 'P'.$paste['id'].': '.$paste['uri'].' - '.
$paste['title'];
if ($paste['language']) {
$output[$paste['phid']] .= ' ('.$paste['language'].')';
}
}
}
if ($commit_names) {
$commits = $this->getConduit()->callMethodSynchronous(
'diffusion.getcommits',
array(
'commits' => $commit_names,
));
foreach ($commits as $commit) {
if (isset($commit['error'])) {
continue;
}
$output[$commit['commitPHID']] = $commit['uri'];
}
}
foreach ($output as $phid => $description) {
// Don't mention the same object more than once every 10 minutes
// in public channels, so we avoid spamming the chat over and over
// again for discsussions of a specific revision, for example. In
// direct-to-bot chat, respond to every object reference.
if ($this->isChannelName($reply_to)) {
if (empty($this->recentlyMentioned[$reply_to])) {
$this->recentlyMentioned[$reply_to] = array();
}
$quiet_until = idx(
$this->recentlyMentioned[$reply_to],
$phid,
0) + (60 * 10);
if (time() < $quiet_until) {
// Remain quiet on this channel.
continue;
}
$this->recentlyMentioned[$reply_to][$phid] = time();
}
$this->write('PRIVMSG', "{$reply_to} :{$description}");
}
break;
}
}
private function handleSymbols(PhabricatorIRCMessage $message) {
$reply_to = $message->getReplyTo();
$text = $message->getMessageText();
$matches = null;
if (!preg_match('/where(?: in the world) is (\S+?)\?/i', $text, $matches)) {
return;
}
$symbol = $matches[1];
$results = $this->getConduit()->callMethodSynchronous(
'diffusion.findsymbols',
array(
'name' => $symbol,
));
$default_uri = $this->getURI('/diffusion/symbol/'.$symbol.'/');
if (count($results) > 1) {
$response = "Multiple symbols named '{$symbol}': {$default_uri}";
} else if (count($results) == 1) {
$result = head($results);
$response =
$result['type'].' '.
$result['name'].' '.
'('.$result['language'].'): '.
nonempty($result['uri'], $default_uri);
} else {
$response = "No symbol '{$symbol}' found anywhere.";
}
$this->write('PRIVMSG', "{$reply_to} :{$response}");
}
}

Event Timeline