]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Irc/IrcPlugin.php
Merge branch '0.9.x' into 1.0.x
[quix0rs-gnu-social.git] / plugins / Irc / IrcPlugin.php
1 <?php
2 /**
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2010, StatusNet, Inc.
5  *
6  * Send and receive notices using an IRC network
7  *
8  * PHP version 5
9  *
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.
14  *
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.
19  *
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/>.
22  *
23  * @category  IM
24  * @package   StatusNet
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/
29  */
30
31 if (!defined('STATUSNET')) {
32     // This check helps protect against security problems;
33     // your code file can't be executed directly from the web.
34     exit(1);
35 }
36
37 // We bundle the Phergie library...
38 set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib/phergie');
39
40 /**
41  * Plugin for IRC
42  *
43  * @category  Plugin
44  * @package   StatusNet
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/
49  */
50
51 class IrcPlugin extends ImPlugin {
52     public $host =  null;
53     public $port = null;
54     public $username = null;
55     public $realname = null;
56     public $nick = 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;
63     public $pinginterval = null;
64
65     public $regcheck = null;
66     public $unregregexp = null;
67     public $regregexp = null;
68
69     public $transport = 'irc';
70     protected $whiteList;
71     protected $fake_irc;
72
73     /**
74      * Get the internationalized/translated display name of this IM service
75      *
76      * @return string Name of service
77      */
78     public function getDisplayName() {
79         return _m('IRC');
80     }
81
82     /**
83      * Normalize a screenname for comparison
84      *
85      * @param string $screenname Screenname to normalize
86      * @return string An equivalent screenname in normalized form
87      */
88     public function normalize($screenname) {
89         $screenname = str_replace(" ","", $screenname);
90         return strtolower($screenname);
91     }
92
93     /**
94      * Get the screenname of the daemon that sends and receives messages
95      *
96      * @return string Screenname
97      */
98     public function daemonScreenname() {
99         return $this->nick;
100     }
101
102     /**
103      * Validate (ensure the validity of) a screenname
104      *
105      * @param string $screenname Screenname to validate
106      * @return boolean true if screenname is valid
107      */
108     public function validate($screenname) {
109         if (preg_match('/\A[a-z0-9\-_]{1,1000}\z/i', $screenname)) {
110             return true;
111         } else {
112             return false;
113         }
114     }
115
116     /**
117      * Load related modules when needed
118      *
119      * @param string $cls Name of the class to be loaded
120      * @return boolean hook value; true means continue processing, false means stop.
121      */
122     public function onAutoload($cls) {
123         $dir = dirname(__FILE__);
124
125         switch ($cls) {
126             case 'IrcManager':
127                 include_once $dir . '/'.strtolower($cls).'.php';
128                 return false;
129             case 'Fake_Irc':
130             case 'Irc_waiting_message':
131             case 'ChannelResponseChannel':
132                 include_once $dir . '/'. $cls .'.php';
133                 return false;
134             default:
135                 if (substr($cls, 0, 7) == 'Phergie') {
136                     include_once str_replace('_', DIRECTORY_SEPARATOR, $cls) . '.php';
137                     return false;
138                 }
139                 return true;
140         }
141     }
142
143     /*
144      * Start manager on daemon start
145      *
146      * @param array &$versions Array to insert manager into
147      * @return boolean
148      */
149     public function onStartImDaemonIoManagers(&$classes) {
150         parent::onStartImDaemonIoManagers(&$classes);
151         $classes[] = new IrcManager($this); // handles sending/receiving
152         return true;
153     }
154
155     /**
156     * Ensure the database table is present
157     *
158     */
159     public function onCheckSchema() {
160         $schema = Schema::get();
161
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')));
171
172         return true;
173     }
174
175     /**
176     * Get a microid URI for the given screenname
177     *
178     * @param string $screenname Screenname
179     * @return string microid URI
180     */
181     public function microiduri($screenname) {
182         return 'irc:' . $screenname;
183     }
184
185     /**
186      * Send a message to a given screenname
187      *
188      * @param string $screenname Screenname to send to
189      * @param string $body Text to send
190      * @return boolean true on success
191      */
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));
197         }
198         return true;
199     }
200
201     /**
202      * Accept a queued input message.
203      *
204      * @return boolean true if processing completed, false if message should be reprocessed
205      */
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);
213             } else {
214                 $this->handleIncoming($data['sender'], $message);
215             }
216         } else {
217             $this->handleIncoming($data['sender'], $data['message']);
218         }
219         return true;
220     }
221
222     /**
223      * Helper for handling incoming messages from a channel requiring response
224      * to the channel instead of via PM
225      *
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
230      */
231     protected function handle_channel_incoming($nick, $channel, $notice_text) {
232         $user = $this->getUser($nick);
233         // For common_current_user to work
234         global $_cur;
235         $_cur = $user;
236
237         if (!$user) {
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);
242             return;
243         }
244         if ($this->handle_channel_command($user, $channel, $notice_text)) {
245             common_log(LOG_INFO, "Command message by $nick handled.");
246             return;
247         } else if ($this->isAutoreply($notice_text)) {
248             common_log(LOG_INFO, 'Ignoring auto reply from ' . $nick);
249             return;
250         } else if ($this->isOtr($notice_text)) {
251             common_log(LOG_INFO, 'Ignoring OTR from ' . $nick);
252             return;
253         } else {
254             common_log(LOG_INFO, 'Posting a notice from ' . $user->nickname);
255             $this->addNotice($nick, $user, $notice_text);
256         }
257
258         $user->free();
259         unset($user);
260         unset($_cur);
261         unset($message);
262     }
263
264     /**
265      * Attempt to handle a message from a channel as a command
266      *
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
271      */
272     protected function handle_channel_command($user, $channel, $body) {
273         $inter = new CommandInterpreter();
274         $cmd = $inter->handle_command($user, $body);
275         if ($cmd) {
276             $chan = new ChannelResponseChannel($this, $channel);
277             $cmd->execute($chan);
278             return true;
279         } else {
280             return false;
281         }
282     }
283
284     /**
285      * Send a confirmation code to a user
286      *
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
291      */
292     public function sendConfirmationCode($screenname, $code, $user, $checked = false) {
293         $body = sprintf(_('User "%s" on %s has said that your %s screenname belongs to them. ' .
294           'If that\'s true, you can confirm by clicking on this URL: ' .
295           '%s' .
296           ' . (If you cannot click it, copy-and-paste it into the ' .
297           'address bar of your browser). If that user isn\'t you, ' .
298           'or if you didn\'t request this confirmation, just ignore this message.'),
299           $user->nickname, common_config('site', 'name'), $this->getDisplayName(), common_local_url('confirmaddress', array('code' => $code)));
300
301         if ($this->regcheck && !$checked) {
302             return $this->checked_sendConfirmationCode($screenname, $code, $user);
303         } else {
304             return $this->sendMessage($screenname, $body);
305         }
306     }
307
308     /**
309     * Only sends the confirmation message if the nick is
310     * registered
311     *
312     * @param string $screenname Screenname sending to
313     * @param string $code The confirmation code
314     * @param User $user User sending to
315     * @return boolean true on succes
316     */
317     public function checked_sendConfirmationCode($screenname, $code, $user) {
318         $this->fake_irc->doPrivmsg('NickServ', 'INFO '.$screenname);
319         $this->enqueueOutgoingRaw(
320             array(
321                 'type' => 'nickcheck',
322                 'prioritise' => 1,
323                 'data' => $this->fake_irc->would_be_sent,
324                 'nickdata' =>
325                     array(
326                         'screenname' => $screenname,
327                         'code' => $code,
328                         'user' => $user
329                     )
330             )
331         );
332         return true;
333     }
334
335     /**
336     * Initialize plugin
337     *
338     * @return boolean
339     */
340     public function initialize() {
341         if (!isset($this->host)) {
342             throw new Exception('must specify a host');
343         }
344         if (!isset($this->username)) {
345             throw new Exception('must specify a username');
346         }
347         if (!isset($this->realname)) {
348             throw new Exception('must specify a "real name"');
349         }
350         if (!isset($this->nick)) {
351             throw new Exception('must specify a nickname');
352         }
353
354         if (!isset($this->port)) {
355             $this->port = 6667;
356         }
357         if (!isset($this->transporttype)) {
358             $this->transporttype = 'tcp';
359         }
360         if (!isset($this->encoding)) {
361             $this->encoding = 'UTF-8';
362         }
363         if (!isset($this->pinginterval)) {
364             $this->pinginterval = 120;
365         }
366
367         if (!isset($this->regcheck)) {
368             $this->regcheck = true;
369         }
370
371         $this->fake_irc = new Fake_Irc;
372
373         /*
374          * Commands allowed to return output to a channel
375          */
376         $this->whiteList = array('stats', 'last', 'get');
377
378         return true;
379     }
380
381     /**
382      * Get plugin information
383      *
384      * @param array $versions Array to insert information into
385      * @return void
386      */
387     public function onPluginVersion(&$versions) {
388         $versions[] = array('name' => 'IRC',
389                             'version' => STATUSNET_VERSION,
390                             'author' => 'Luke Fitzgerald',
391                             'homepage' => 'http://status.net/wiki/Plugin:IRC',
392                             'rawdescription' =>
393                             _m('The IRC plugin allows users to send and receive notices over an IRC network.'));
394         return true;
395     }
396 }