IrcPlugin.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  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. if (!defined('STATUSNET')) {
  31. // This check helps protect against security problems;
  32. // your code file can't be executed directly from the web.
  33. exit(1);
  34. }
  35. // We bundle the Phergie library...
  36. set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib/phergie');
  37. /**
  38. * Plugin for IRC
  39. *
  40. * @category Plugin
  41. * @package StatusNet
  42. * @author Luke Fitzgerald <lw.fitzgerald@googlemail.com>
  43. * @copyright 2010 StatusNet, Inc.
  44. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
  45. * @link http://status.net/
  46. */
  47. class IrcPlugin extends ImPlugin {
  48. public $host = null;
  49. public $port = null;
  50. public $username = null;
  51. public $realname = null;
  52. public $nick = null;
  53. public $password = null;
  54. public $nickservidentifyregexp = null;
  55. public $nickservpassword = null;
  56. public $channels = null;
  57. public $transporttype = null;
  58. public $encoding = null;
  59. public $pinginterval = null;
  60. public $regcheck = null;
  61. public $unregregexp = null;
  62. public $regregexp = null;
  63. public $transport = 'irc';
  64. protected $whiteList;
  65. protected $fake_irc;
  66. /**
  67. * Get the internationalized/translated display name of this IM service
  68. *
  69. * @return string Name of service
  70. */
  71. public function getDisplayName() {
  72. // TRANS: Service name for IRC.
  73. return _m('IRC');
  74. }
  75. /**
  76. * Normalize a screenname for comparison
  77. *
  78. * @param string $screenname Screenname to normalize
  79. * @return string An equivalent screenname in normalized form
  80. */
  81. public function normalize($screenname) {
  82. $screenname = str_replace(" ","", $screenname);
  83. return strtolower($screenname);
  84. }
  85. /**
  86. * Get the screenname of the daemon that sends and receives messages
  87. *
  88. * @return string Screenname
  89. */
  90. public function daemonScreenname() {
  91. return $this->nick;
  92. }
  93. /**
  94. * Validate (ensure the validity of) a screenname
  95. *
  96. * @param string $screenname Screenname to validate
  97. * @return boolean true if screenname is valid
  98. */
  99. public function validate($screenname) {
  100. if (preg_match('/\A[a-z0-9\-_]{1,1000}\z/i', $screenname)) {
  101. return true;
  102. } else {
  103. return false;
  104. }
  105. }
  106. /**
  107. * Load related modules when needed
  108. *
  109. * @param string $cls Name of the class to be loaded
  110. * @return boolean hook value; true means continue processing, false means stop.
  111. */
  112. public function onAutoload($cls) {
  113. // in the beginning of this file, we have added an include path
  114. if (substr($cls, 0, 7) == 'Phergie') {
  115. include_once str_replace('_', DIRECTORY_SEPARATOR, $cls) . '.php';
  116. return false;
  117. }
  118. return parent::onAutoload($cls);
  119. }
  120. /*
  121. * Start manager on daemon start
  122. *
  123. * @param array &$versions Array to insert manager into
  124. * @return boolean
  125. */
  126. public function onStartImDaemonIoManagers(&$classes) {
  127. parent::onStartImDaemonIoManagers($classes);
  128. $classes[] = new IrcManager($this); // handles sending/receiving
  129. return true;
  130. }
  131. /**
  132. * Ensure the database table is present
  133. *
  134. */
  135. public function onCheckSchema() {
  136. $schema = Schema::get();
  137. // For storing messages while sessions become ready
  138. $schema->ensureTable('irc_waiting_message', Irc_waiting_message::schemaDef());
  139. return true;
  140. }
  141. /**
  142. * Get a microid URI for the given screenname
  143. *
  144. * @param string $screenname Screenname
  145. * @return string microid URI
  146. */
  147. public function microiduri($screenname) {
  148. return 'irc:' . $screenname;
  149. }
  150. /**
  151. * Send a message to a given screenname
  152. *
  153. * @param string $screenname Screenname to send to
  154. * @param string $body Text to send
  155. * @return boolean true on success
  156. */
  157. public function sendMessage($screenname, $body) {
  158. $lines = explode("\n", $body);
  159. foreach ($lines as $line) {
  160. $this->fake_irc->doPrivmsg($screenname, $line);
  161. $this->enqueueOutgoingRaw(array('type' => 'message', 'prioritise' => 0, 'data' => $this->fake_irc->would_be_sent));
  162. }
  163. return true;
  164. }
  165. /**
  166. * Accept a queued input message.
  167. *
  168. * @return boolean true if processing completed, false if message should be reprocessed
  169. */
  170. public function receiveRawMessage($data) {
  171. if (strpos($data['source'], '#') === 0) {
  172. $message = $data['message'];
  173. $parts = explode(' ', $message, 2);
  174. $command = $parts[0];
  175. if (in_array($command, $this->whiteList)) {
  176. $this->handle_channel_incoming($data['sender'], $data['source'], $message);
  177. } else {
  178. $this->handleIncoming($data['sender'], $message);
  179. }
  180. } else {
  181. $this->handleIncoming($data['sender'], $data['message']);
  182. }
  183. return true;
  184. }
  185. /**
  186. * Helper for handling incoming messages from a channel requiring response
  187. * to the channel instead of via PM
  188. *
  189. * @param string $nick Screenname the message was sent from
  190. * @param string $channel Channel the message originated from
  191. * @param string $message Message text
  192. * @param boolean true on success
  193. */
  194. protected function handle_channel_incoming($nick, $channel, $notice_text) {
  195. $user = $this->getUser($nick);
  196. // For common_current_user to work
  197. global $_cur;
  198. $_cur = $user;
  199. if (!$user) {
  200. $this->sendFromSite($nick, 'Unknown user; go to ' .
  201. common_local_url('imsettings') .
  202. ' to add your address to your account');
  203. common_log(LOG_WARNING, 'Message from unknown user ' . $nick);
  204. return;
  205. }
  206. if ($this->handle_channel_command($user, $channel, $notice_text)) {
  207. common_log(LOG_INFO, "Command message by $nick handled.");
  208. return;
  209. } else if ($this->isAutoreply($notice_text)) {
  210. common_log(LOG_INFO, 'Ignoring auto reply from ' . $nick);
  211. return;
  212. } else if ($this->isOtr($notice_text)) {
  213. common_log(LOG_INFO, 'Ignoring OTR from ' . $nick);
  214. return;
  215. } else {
  216. common_log(LOG_INFO, 'Posting a notice from ' . $user->nickname);
  217. $this->addNotice($nick, $user, $notice_text);
  218. }
  219. $user->free();
  220. unset($user);
  221. unset($_cur);
  222. unset($message);
  223. }
  224. /**
  225. * Attempt to handle a message from a channel as a command
  226. *
  227. * @param User $user User the message is from
  228. * @param string $channel Channel the message originated from
  229. * @param string $body Message text
  230. * @return boolean true if the message was a command and was executed, false if it was not a command
  231. */
  232. protected function handle_channel_command($user, $channel, $body) {
  233. $inter = new CommandInterpreter();
  234. $cmd = $inter->handle_command($user, $body);
  235. if ($cmd) {
  236. $chan = new ChannelResponseChannel($this, $channel);
  237. $cmd->execute($chan);
  238. return true;
  239. } else {
  240. return false;
  241. }
  242. }
  243. /**
  244. * Send a confirmation code to a user
  245. *
  246. * @param string $screenname screenname sending to
  247. * @param string $code the confirmation code
  248. * @param User $user user sending to
  249. * @return boolean success value
  250. */
  251. public function sendConfirmationCode($screenname, $code, $user, $checked = false) {
  252. // TRANS: Body text for e-mail confirmation message for IRC.
  253. // TRANS: %1$s is a user nickname, %2$s is the StatusNet sitename,
  254. // TRANS: %3$s is the plugin display name ("IRC"), %4$s is the confirm address URL.
  255. $body = sprintf(_m('User "%1$s" on %2$s has said that your %3$s screenname belongs to them. ' .
  256. 'If that\'s true, you can confirm by clicking on this URL: ' .
  257. '%4$s' .
  258. ' . (If you cannot click it, copy-and-paste it into the ' .
  259. 'address bar of your browser). If that user is not you, ' .
  260. 'or if you did not request this confirmation, just ignore this message.'),
  261. $user->nickname, common_config('site', 'name'), $this->getDisplayName(), common_local_url('confirmaddress', array('code' => $code)));
  262. if ($this->regcheck && !$checked) {
  263. return $this->checked_sendConfirmationCode($screenname, $code, $user);
  264. } else {
  265. return $this->sendMessage($screenname, $body);
  266. }
  267. }
  268. /**
  269. * Only sends the confirmation message if the nick is
  270. * registered
  271. *
  272. * @param string $screenname Screenname sending to
  273. * @param string $code The confirmation code
  274. * @param User $user User sending to
  275. * @return boolean true on succes
  276. */
  277. public function checked_sendConfirmationCode($screenname, $code, $user) {
  278. $this->fake_irc->doPrivmsg('NickServ', 'INFO '.$screenname);
  279. $this->enqueueOutgoingRaw(
  280. array(
  281. 'type' => 'nickcheck',
  282. 'prioritise' => 1,
  283. 'data' => $this->fake_irc->would_be_sent,
  284. 'nickdata' =>
  285. array(
  286. 'screenname' => $screenname,
  287. 'code' => $code,
  288. 'user' => $user
  289. )
  290. )
  291. );
  292. return true;
  293. }
  294. /**
  295. * Initialize plugin
  296. *
  297. * @return boolean
  298. */
  299. public function initialize() {
  300. if (!isset($this->host)) {
  301. // TRANS: Exception thrown when initialising the IRC plugin fails because of an incorrect configuration.
  302. throw new Exception(_m('You must specify a host.'));
  303. }
  304. if (!isset($this->username)) {
  305. // TRANS: Exception thrown when initialising the IRC plugin fails because of an incorrect configuration.
  306. throw new Exception(_m('You must specify a username.'));
  307. }
  308. if (!isset($this->realname)) {
  309. // TRANS: Exception thrown when initialising the IRC plugin fails because of an incorrect configuration.
  310. throw new Exception(_m('You must specify a "real name".'));
  311. }
  312. if (!isset($this->nick)) {
  313. // TRANS: Exception thrown when initialising the IRC plugin fails because of an incorrect configuration.
  314. throw new Exception(_m('You must specify a nickname.'));
  315. }
  316. if (!isset($this->port)) {
  317. $this->port = 6667;
  318. }
  319. if (!isset($this->transporttype)) {
  320. $this->transporttype = 'tcp';
  321. }
  322. if (!isset($this->encoding)) {
  323. $this->encoding = 'UTF-8';
  324. }
  325. if (!isset($this->pinginterval)) {
  326. $this->pinginterval = 120;
  327. }
  328. if (!isset($this->regcheck)) {
  329. $this->regcheck = true;
  330. }
  331. $this->fake_irc = new FakeIrc;
  332. /*
  333. * Commands allowed to return output to a channel
  334. */
  335. $this->whiteList = array('stats', 'last', 'get');
  336. return true;
  337. }
  338. /**
  339. * Get plugin information
  340. *
  341. * @param array $versions Array to insert information into
  342. * @return void
  343. */
  344. public function onPluginVersion(&$versions) {
  345. $versions[] = array('name' => 'IRC',
  346. 'version' => GNUSOCIAL_VERSION,
  347. 'author' => 'Luke Fitzgerald',
  348. 'homepage' => 'http://status.net/wiki/Plugin:IRC',
  349. 'rawdescription' =>
  350. // TRANS: Plugin description.
  351. _m('The IRC plugin allows users to send and receive notices over an IRC network.'));
  352. return true;
  353. }
  354. }