3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2010, StatusNet, Inc.
6 * Send and receive notices using an IRC network
10 * This program is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU Affero General Public License as published by
12 * the Free Software Foundation, either version 3 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Affero General Public License for more details.
20 * You should have received a copy of the GNU Affero General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 * @author Luke Fitzgerald <lw.fitzgerald@googlemail.com>
26 * @copyright 2010 StatusNet, Inc.
27 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
28 * @link http://status.net/
31 if (!defined('STATUSNET')) {
32 // This check helps protect against security problems;
33 // your code file can't be executed directly from the web.
37 // We bundle the Phergie library...
38 set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib/phergie');
45 * @author Luke Fitzgerald <lw.fitzgerald@googlemail.com>
46 * @copyright 2010 StatusNet, Inc.
47 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
48 * @link http://status.net/
50 class IrcPlugin extends ImPlugin {
53 public $username = null;
54 public $realname = null;
56 public $password = null;
57 public $nickservidentifyregexp = null;
58 public $nickservpassword = null;
59 public $channels = null;
60 public $transporttype = null;
61 public $encoding = null;
62 public $pinginterval = null;
64 public $regcheck = null;
65 public $unregregexp = null;
66 public $regregexp = null;
68 public $transport = 'irc';
73 * Get the internationalized/translated display name of this IM service
75 * @return string Name of service
77 public function getDisplayName() {
78 // TRANS: Service name for IRC.
83 * Normalize a screenname for comparison
85 * @param string $screenname Screenname to normalize
86 * @return string An equivalent screenname in normalized form
88 public function normalize($screenname) {
89 $screenname = str_replace(" ","", $screenname);
90 return strtolower($screenname);
94 * Get the screenname of the daemon that sends and receives messages
96 * @return string Screenname
98 public function daemonScreenname() {
103 * Validate (ensure the validity of) a screenname
105 * @param string $screenname Screenname to validate
106 * @return boolean true if screenname is valid
108 public function validate($screenname) {
109 if (preg_match('/\A[a-z0-9\-_]{1,1000}\z/i', $screenname)) {
117 * Load related modules when needed
119 * @param string $cls Name of the class to be loaded
120 * @return boolean hook value; true means continue processing, false means stop.
122 public function onAutoload($cls) {
123 $dir = dirname(__FILE__);
127 include_once $dir . '/'.strtolower($cls).'.php';
130 case 'Irc_waiting_message':
131 case 'ChannelResponseChannel':
132 include_once $dir . '/'. $cls .'.php';
135 if (substr($cls, 0, 7) == 'Phergie') {
136 include_once str_replace('_', DIRECTORY_SEPARATOR, $cls) . '.php';
144 * Start manager on daemon start
146 * @param array &$versions Array to insert manager into
149 public function onStartImDaemonIoManagers(&$classes) {
150 parent::onStartImDaemonIoManagers(&$classes);
151 $classes[] = new IrcManager($this); // handles sending/receiving
156 * Ensure the database table is present
159 public function onCheckSchema() {
160 $schema = Schema::get();
162 // For storing messages while sessions become ready
163 $schema->ensureTable('irc_waiting_message',
164 array(new ColumnDef('id', 'integer', null,
165 false, 'PRI', null, null, true),
166 new ColumnDef('data', 'blob', null, false),
167 new ColumnDef('prioritise', 'tinyint', 1, false),
168 new ColumnDef('attempts', 'integer', null, false),
169 new ColumnDef('created', 'datetime', null, false),
170 new ColumnDef('claimed', 'datetime')));
176 * Get a microid URI for the given screenname
178 * @param string $screenname Screenname
179 * @return string microid URI
181 public function microiduri($screenname) {
182 return 'irc:' . $screenname;
186 * Send a message to a given screenname
188 * @param string $screenname Screenname to send to
189 * @param string $body Text to send
190 * @return boolean true on success
192 public function sendMessage($screenname, $body) {
193 $lines = explode("\n", $body);
194 foreach ($lines as $line) {
195 $this->fake_irc->doPrivmsg($screenname, $line);
196 $this->enqueueOutgoingRaw(array('type' => 'message', 'prioritise' => 0, 'data' => $this->fake_irc->would_be_sent));
202 * Accept a queued input message.
204 * @return boolean true if processing completed, false if message should be reprocessed
206 public function receiveRawMessage($data) {
207 if (strpos($data['source'], '#') === 0) {
208 $message = $data['message'];
209 $parts = explode(' ', $message, 2);
210 $command = $parts[0];
211 if (in_array($command, $this->whiteList)) {
212 $this->handle_channel_incoming($data['sender'], $data['source'], $message);
214 $this->handleIncoming($data['sender'], $message);
217 $this->handleIncoming($data['sender'], $data['message']);
223 * Helper for handling incoming messages from a channel requiring response
224 * to the channel instead of via PM
226 * @param string $nick Screenname the message was sent from
227 * @param string $channel Channel the message originated from
228 * @param string $message Message text
229 * @param boolean true on success
231 protected function handle_channel_incoming($nick, $channel, $notice_text) {
232 $user = $this->getUser($nick);
233 // For common_current_user to work
238 $this->sendFromSite($nick, 'Unknown user; go to ' .
239 common_local_url('imsettings') .
240 ' to add your address to your account');
241 common_log(LOG_WARNING, 'Message from unknown user ' . $nick);
244 if ($this->handle_channel_command($user, $channel, $notice_text)) {
245 common_log(LOG_INFO, "Command message by $nick handled.");
247 } else if ($this->isAutoreply($notice_text)) {
248 common_log(LOG_INFO, 'Ignoring auto reply from ' . $nick);
250 } else if ($this->isOtr($notice_text)) {
251 common_log(LOG_INFO, 'Ignoring OTR from ' . $nick);
254 common_log(LOG_INFO, 'Posting a notice from ' . $user->nickname);
255 $this->addNotice($nick, $user, $notice_text);
265 * Attempt to handle a message from a channel as a command
267 * @param User $user User the message is from
268 * @param string $channel Channel the message originated from
269 * @param string $body Message text
270 * @return boolean true if the message was a command and was executed, false if it was not a command
272 protected function handle_channel_command($user, $channel, $body) {
273 $inter = new CommandInterpreter();
274 $cmd = $inter->handle_command($user, $body);
276 $chan = new ChannelResponseChannel($this, $channel);
277 $cmd->execute($chan);
285 * Send a confirmation code to a user
287 * @param string $screenname screenname sending to
288 * @param string $code the confirmation code
289 * @param User $user user sending to
290 * @return boolean success value
292 public function sendConfirmationCode($screenname, $code, $user, $checked = false) {
293 // TRANS: Body text for e-mail confirmation message for IRC.
294 // TRANS: %1$s is a user nickname, %2$s is the StatusNet sitename,
295 // TRANS: %3$s is the plugin display name ("IRC"), %4$s is the confirm address URL.
296 $body = sprintf(_m('User "%1$s" on %2$s has said that your %3$s screenname belongs to them. ' .
297 'If that\'s true, you can confirm by clicking on this URL: ' .
299 ' . (If you cannot click it, copy-and-paste it into the ' .
300 'address bar of your browser). If that user is not you, ' .
301 'or if you did not request this confirmation, just ignore this message.'),
302 $user->nickname, common_config('site', 'name'), $this->getDisplayName(), common_local_url('confirmaddress', array('code' => $code)));
304 if ($this->regcheck && !$checked) {
305 return $this->checked_sendConfirmationCode($screenname, $code, $user);
307 return $this->sendMessage($screenname, $body);
312 * Only sends the confirmation message if the nick is
315 * @param string $screenname Screenname sending to
316 * @param string $code The confirmation code
317 * @param User $user User sending to
318 * @return boolean true on succes
320 public function checked_sendConfirmationCode($screenname, $code, $user) {
321 $this->fake_irc->doPrivmsg('NickServ', 'INFO '.$screenname);
322 $this->enqueueOutgoingRaw(
324 'type' => 'nickcheck',
326 'data' => $this->fake_irc->would_be_sent,
329 'screenname' => $screenname,
343 public function initialize() {
344 if (!isset($this->host)) {
345 // TRANS: Exception thrown when initialising the IRC plugin fails because of an incorrect configuration.
346 throw new Exception(_m('You must specify a host.'));
348 if (!isset($this->username)) {
349 // TRANS: Exception thrown when initialising the IRC plugin fails because of an incorrect configuration.
350 throw new Exception(_m('You must specify a username.'));
352 if (!isset($this->realname)) {
353 // TRANS: Exception thrown when initialising the IRC plugin fails because of an incorrect configuration.
354 throw new Exception(_m('You must specify a "real name".'));
356 if (!isset($this->nick)) {
357 // TRANS: Exception thrown when initialising the IRC plugin fails because of an incorrect configuration.
358 throw new Exception(_m('You must specify a nickname.'));
361 if (!isset($this->port)) {
364 if (!isset($this->transporttype)) {
365 $this->transporttype = 'tcp';
367 if (!isset($this->encoding)) {
368 $this->encoding = 'UTF-8';
370 if (!isset($this->pinginterval)) {
371 $this->pinginterval = 120;
374 if (!isset($this->regcheck)) {
375 $this->regcheck = true;
378 $this->fake_irc = new Fake_Irc;
381 * Commands allowed to return output to a channel
383 $this->whiteList = array('stats', 'last', 'get');
389 * Get plugin information
391 * @param array $versions Array to insert information into
394 public function onPluginVersion(&$versions) {
395 $versions[] = array('name' => 'IRC',
396 'version' => STATUSNET_VERSION,
397 'author' => 'Luke Fitzgerald',
398 'homepage' => 'http://status.net/wiki/Plugin:IRC',
400 // TRANS: Plugin description.
401 _m('The IRC plugin allows users to send and receive notices over an IRC network.'));