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/
51 class IrcPlugin extends ImPlugin {
54 public $username = null;
55 public $realname = null;
57 public $password = null;
58 public $nickservidentifyregexp = null;
59 public $nickservpassword = null;
60 public $channels = null;
61 public $transporttype = null;
62 public $encoding = 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() {
82 * Normalize a screenname for comparison
84 * @param string $screenname Screenname to normalize
85 * @return string An equivalent screenname in normalized form
87 public function normalize($screenname) {
88 $screenname = str_replace(" ","", $screenname);
89 return strtolower($screenname);
93 * Get the screenname of the daemon that sends and receives messages
95 * @return string Screenname
97 public function daemon_screenname() {
102 * Validate (ensure the validity of) a screenname
104 * @param string $screenname Screenname to validate
105 * @return boolean true if screenname is valid
107 public function validate($screenname) {
108 if (preg_match('/\A[a-z0-9\-_]{1,1000}\z/i', $screenname)) {
116 * Load related modules when needed
118 * @param string $cls Name of the class to be loaded
119 * @return boolean hook value; true means continue processing, false means stop.
121 public function onAutoload($cls) {
122 $dir = dirname(__FILE__);
126 include_once $dir . '/'.strtolower($cls).'.php';
129 case 'Irc_waiting_message':
130 case 'ChannelResponseChannel':
131 include_once $dir . '/'. $cls .'.php';
134 if (substr($cls, 0, 7) == 'Phergie') {
135 include_once str_replace('_', DIRECTORY_SEPARATOR, $cls) . '.php';
143 * Start manager on daemon start
145 * @param array &$versions Array to insert manager into
148 public function onStartImDaemonIoManagers(&$classes) {
149 parent::onStartImDaemonIoManagers(&$classes);
150 $classes[] = new IrcManager($this); // handles sending/receiving
155 * Ensure the database table is present
158 public function onCheckSchema() {
159 $schema = Schema::get();
161 // For storing messages while sessions become ready
162 $schema->ensureTable('irc_waiting_message',
163 array(new ColumnDef('id', 'integer', null,
164 false, 'PRI', null, null, true),
165 new ColumnDef('data', 'blob', null, false),
166 new ColumnDef('prioritise', 'tinyint', 1, false),
167 new ColumnDef('created', 'datetime', null, false),
168 new ColumnDef('claimed', 'datetime')));
174 * Get a microid URI for the given screenname
176 * @param string $screenname Screenname
177 * @return string microid URI
179 public function microiduri($screenname) {
180 return 'irc:' . $screenname;
184 * Send a message to a given screenname
186 * @param string $screenname Screenname to send to
187 * @param string $body Text to send
188 * @return boolean true on success
190 public function send_message($screenname, $body) {
191 $lines = explode("\n", $body);
192 foreach ($lines as $line) {
193 $this->fake_irc->doPrivmsg($screenname, $line);
194 $this->enqueue_outgoing_raw(array('type' => 'message', 'prioritise' => 0, 'data' => $this->fake_irc->would_be_sent));
200 * Accept a queued input message.
202 * @return boolean true if processing completed, false if message should be reprocessed
204 public function receive_raw_message($data) {
205 if (strpos($data['source'], '#') === 0) {
206 $message = $data['message'];
207 $parts = explode(' ', $message, 2);
208 $command = $parts[0];
209 if (in_array($command, $this->whiteList)) {
210 $this->handle_channel_incoming($data['sender'], $data['source'], $message);
212 $this->handle_incoming($data['sender'], $message);
215 $this->handle_incoming($data['sender'], $data['message']);
221 * Helper for handling incoming messages from a channel requiring response
222 * to the channel instead of via PM
224 * @param string $nick Screenname the message was sent from
225 * @param string $channel Channel the message originated from
226 * @param string $message Message text
227 * @param boolean true on success
229 protected function handle_channel_incoming($nick, $channel, $notice_text) {
230 $user = $this->get_user($nick);
231 // For common_current_user to work
236 $this->send_from_site($nick, 'Unknown user; go to ' .
237 common_local_url('imsettings') .
238 ' to add your address to your account');
239 common_log(LOG_WARNING, 'Message from unknown user ' . $nick);
242 if ($this->handle_channel_command($user, $channel, $notice_text)) {
243 common_log(LOG_INFO, "Command message by $nick handled.");
245 } else if ($this->is_autoreply($notice_text)) {
246 common_log(LOG_INFO, 'Ignoring auto reply from ' . $nick);
248 } else if ($this->is_otr($notice_text)) {
249 common_log(LOG_INFO, 'Ignoring OTR from ' . $nick);
252 common_log(LOG_INFO, 'Posting a notice from ' . $user->nickname);
253 $this->add_notice($nick, $user, $notice_text);
263 * Attempt to handle a message from a channel as a command
265 * @param User $user User the message is from
266 * @param string $channel Channel the message originated from
267 * @param string $body Message text
268 * @return boolean true if the message was a command and was executed, false if it was not a command
270 protected function handle_channel_command($user, $channel, $body) {
271 $inter = new CommandInterpreter();
272 $cmd = $inter->handle_command($user, $body);
274 $chan = new ChannelResponseChannel($this, $channel);
275 $cmd->execute($chan);
283 * Send a confirmation code to a user
285 * @param string $screenname screenname sending to
286 * @param string $code the confirmation code
287 * @param User $user user sending to
288 * @return boolean success value
290 public function send_confirmation_code($screenname, $code, $user, $checked = false) {
291 $body = sprintf(_('User "%s" on %s has said that your %s screenname belongs to them. ' .
292 'If that\'s true, you can confirm by clicking on this URL: ' .
294 ' . (If you cannot click it, copy-and-paste it into the ' .
295 'address bar of your browser). If that user isn\'t you, ' .
296 'or if you didn\'t request this confirmation, just ignore this message.'),
297 $user->nickname, common_config('site', 'name'), $this->getDisplayName(), common_local_url('confirmaddress', array('code' => $code)));
299 if ($this->regcheck && !$checked) {
300 return $this->checked_send_confirmation_code($screenname, $code, $user);
302 return $this->send_message($screenname, $body);
307 * Only sends the confirmation message if the nick is
310 * @param string $screenname Screenname sending to
311 * @param string $code The confirmation code
312 * @param User $user User sending to
313 * @return boolean true on succes
315 public function checked_send_confirmation_code($screenname, $code, $user) {
316 $this->fake_irc->doPrivmsg('NickServ', 'INFO '.$screenname);
317 $this->enqueue_outgoing_raw(
319 'type' => 'nickcheck',
321 'data' => $this->fake_irc->would_be_sent,
324 'screenname' => $screenname,
338 public function initialize() {
339 if (!isset($this->host)) {
340 throw new Exception('must specify a host');
342 if (!isset($this->username)) {
343 throw new Exception('must specify a username');
345 if (!isset($this->realname)) {
346 throw new Exception('must specify a "real name"');
348 if (!isset($this->nick)) {
349 throw new Exception('must specify a nickname');
352 if (!isset($this->port)) {
355 if (!isset($this->transporttype)) {
356 $this->transporttype = 'tcp';
358 if (!isset($this->encoding)) {
359 $this->encoding = 'UTF-8';
362 if (!isset($this->regcheck)) {
363 $this->regcheck = true;
366 $this->fake_irc = new Fake_Irc;
369 * Commands allowed to return output to a channel
371 $this->whiteList = array('stats', 'last', 'get');
377 * Get plugin information
379 * @param array $versions Array to insert information into
382 public function onPluginVersion(&$versions) {
383 $versions[] = array('name' => 'IRC',
384 'version' => STATUSNET_VERSION,
385 'author' => 'Luke Fitzgerald',
386 'homepage' => 'http://status.net/wiki/Plugin:IRC',
388 _m('The IRC plugin allows users to send and receive notices over an IRC network.'));