PageRenderTime 55ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/System/Xmpp/XmppClient.php

https://bitbucket.org/Kadet/xpbot
PHP | 418 lines | 247 code | 63 blank | 108 comment | 23 complexity | 0bb73a10339b8d132887aa644b620217 MD5 | raw file
  1. <?php
  2. namespace XPBot\System\Xmpp;
  3. use XPBot\System\Network\XmppSocket;
  4. use XPBot\System\Utils\Delegate;
  5. use XPBot\System\Utils\Event;
  6. use XPBot\System\Utils\Timer;
  7. use XPBot\System\Utils\XmlBranch;
  8. use XPBot\System\Xmpp\Jid;
  9. /**
  10. * XmppClient, uberclass.
  11. * @package XPBot\System\Xmpp
  12. * @author Kadet <kadet1090@gmai.com>
  13. */
  14. class XmppClient extends XmppSocket
  15. {
  16. # events
  17. /**
  18. * Event fired when client is authed (or not).
  19. * Takes one argument of type SimpleXMLElement.
  20. * @var \XPBot\System\Utils\Event
  21. */
  22. public $onAuth;
  23. /**
  24. * Event fired when stream is opened and ready to accept data.
  25. * Takes no arguments.
  26. * @var \XPBot\System\Utils\Event
  27. */
  28. public $onStreamOpen;
  29. /**
  30. * Event fired when bot is ready (stream is opened, client is successfully authed and session is registered)
  31. * Takes no arguments.
  32. * @var \XPBot\System\Utils\Event
  33. */
  34. public $onReady;
  35. /**
  36. * Event fired on every loop tick.
  37. * Takes no arguments.
  38. * @var \XPBot\System\Utils\Event
  39. */
  40. public $onTick;
  41. /**
  42. * Event fired when presence packet came.
  43. * Takes one argument of type SimpleXMLElement.
  44. * @var \XPBot\System\Utils\Event
  45. */
  46. public $onPresence;
  47. /**
  48. * Event fired when iq packet came.
  49. * Takes one argument of type SimpleXMLElement.
  50. * @var \XPBot\System\Utils\Event
  51. */
  52. public $onIq;
  53. /**
  54. * Event fired when message packet came.
  55. * Takes one argument of type SimpleXMLElement.
  56. * @var \XPBot\System\Utils\Event
  57. */
  58. public $onMessage;
  59. public $onJoin;
  60. public $onLeave;
  61. /**
  62. * Jabber account Jid
  63. * @var Jid
  64. */
  65. protected $jid;
  66. /**
  67. * Password to jabber account.
  68. * @var string
  69. */
  70. protected $password;
  71. /**
  72. * If client is connected and authed is true.
  73. * @var bool
  74. */
  75. public $isReady;
  76. public $rooms = array();
  77. /**
  78. * @param Jid $jid
  79. * @param string $password
  80. * @param int $port
  81. * @param int $timeout
  82. */
  83. public function __construct(Jid $jid, $password, $port = 5222, $timeout = 30)
  84. {
  85. parent::__construct($jid->server, $port, $timeout);
  86. $this->jid = $jid;
  87. $this->password = $password;
  88. $this->onConnect->add(new Delegate(array($this, '_onConnect')));
  89. $this->onAuth = new Event();
  90. $this->onStreamOpen = new Event();
  91. $this->onReady = new Event();
  92. $this->onTick = new Event();
  93. $this->onPresence = new Event();
  94. $this->onMessage = new Event();
  95. $this->onIq = new Event();
  96. $this->onJoin = new Event();
  97. $this->onLeave = new Event();
  98. $this->onAuth->add(new Delegate(array($this, '_onAuth')));
  99. $this->onStreamOpen->add(new Delegate(array($this, '_onStreamOpen')));
  100. $this->onReady->add(new Delegate(array($this, '_onReady')));
  101. $this->onPresence->add(new Delegate(array($this, '_onPresence')));
  102. $this->onMessage->add(new Delegate(array($this, '_onMessage')));
  103. }
  104. /**
  105. * Should be private, but... php sucks!
  106. * DO NOT RUN IT, TRUST ME.
  107. */
  108. public function _onConnect()
  109. {
  110. $stream = new XmlBranch('stream:stream');
  111. $stream
  112. ->addAttribute('to', 'aqq.eu')
  113. ->addAttribute('xmlns', 'jabber:client')
  114. ->addAttribute('xmlns:stream', 'http://etherx.jabber.org/streams');
  115. $this->write(XmlBranch::XML . "\n" . str_replace('/>', '>', $stream->asXML()));
  116. $this->wait('stream', '', new Delegate(array($this->onStreamOpen, 'run')));
  117. $this->work();
  118. }
  119. /**
  120. * Should be private, but... php sucks!
  121. * DO NOT RUN IT, TRUST ME.
  122. */
  123. public function _onStreamOpen()
  124. {
  125. $iq = new xmlBranch("iq");
  126. $iq->addAttribute("type", "set");
  127. $iq->addAttribute("id", "auth");
  128. $iq->addAttribute("to", $this->jid->server);
  129. $iq->addChild(new xmlBranch("query"));
  130. $iq->query[0]->addAttribute("xmlns", "jabber:iq:auth");
  131. $iq->query[0]->addChild(new xmlBranch("username"))->setContent($this->jid->name);
  132. $iq->query[0]->addChild(new xmlBranch("password"))->setContent($this->password);
  133. $iq->query[0]->addChild(new xmlBranch("resource"))->setContent($this->jid->resource);
  134. $this->write($iq->asXML());
  135. $this->wait('iq', 'auth', new Delegate(array($this->onAuth, 'run')));
  136. }
  137. /**
  138. * Should be private, but... php sucks!
  139. * DO NOT RUN IT, TRUST ME.
  140. */
  141. public function _onAuth($result)
  142. {
  143. if ($result['type'] == 'result') {
  144. $iq = new xmlBranch("iq");
  145. $iq->addAttribute("type", "set");
  146. $iq->addAttribute("id", "sess");
  147. $iq->addChild(new xmlBranch("session"))->addAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-session");
  148. $this->write($iq->asXML());
  149. $this->isReady = true;
  150. $this->onReady->run();
  151. }
  152. }
  153. /**
  154. * Should be private, but... php sucks!
  155. * DO NOT RUN IT, TRUST ME.
  156. */
  157. public function _onReady()
  158. {
  159. $this->keepAliveTimer->start();
  160. }
  161. /**
  162. * Should be private, but... php sucks!
  163. * DO NOT RUN IT, TRUST ME.
  164. */
  165. public function keepAliveTick()
  166. {
  167. $xml = new xmlBranch("iq");
  168. $xml->addAttribute("from", $this->jid->__toString());
  169. $xml->addAttribute("to", $this->jid->server);
  170. $xml->addAttribute("id", uniqid('ping_'));
  171. $xml->addAttribute("type", "get");
  172. $xml->addChild(new xmlBranch("ping"))->addAttribute("xmlns", "urn:xmpp:ping");
  173. $this->write($xml->asXML());
  174. }
  175. /**
  176. * Should be private, but... php sucks!
  177. * DO NOT RUN IT, TRUST ME.
  178. *
  179. * @param \SimpleXMLElement $packet
  180. */
  181. public function _onPacket(\SimpleXMLElement $packet)
  182. {
  183. parent::_onPacket($packet);
  184. switch ($packet->getName()) {
  185. case 'presence':
  186. $this->onPresence->run($packet);
  187. break;
  188. case 'iq':
  189. $this->onIq->run($packet);
  190. break;
  191. case 'message':
  192. $this->onMessage->run($packet);
  193. break;
  194. }
  195. }
  196. public function _onPresence(\SimpleXMLElement $packet)
  197. {
  198. $channelJid = strstr($packet['from'], '/', true);
  199. $jid = new Jid($channelJid);
  200. if (!$jid->isChannel() || !isset($this->rooms[$channelJid])) return;
  201. if ($packet['type'] != 'unavailable') {
  202. $user = $this->rooms[$channelJid]->addUser(User::fromPresence($packet, $this));
  203. // avoid firing event on presence broadcast
  204. if($this->rooms[$channelJid]->subject !== false)
  205. $this->onJoin->run($this->rooms[$channelJid], $user);
  206. } else {
  207. $user = $this->rooms[$channelJid]->users[substr(strstr($packet['from'], '/'), 1)];
  208. $this->onLeave->run($this->rooms[$channelJid], $user);
  209. $this->rooms[$channelJid]->removeUser($user);
  210. }
  211. }
  212. public function _onMessage(\SimpleXMLElement $packet)
  213. {
  214. $jid = new Jid($packet['from']);
  215. if ($packet['type'] != 'groupchat' || !isset($this->rooms[$jid->bare()])) return;
  216. if(isset($packet->subject))
  217. $this->rooms[$jid->bare()]->setSubject($packet->subject);
  218. }
  219. /**
  220. * Starts bot reading loop.
  221. * @todo [PHP 5.5] write it using coroutines.
  222. */
  223. private function work()
  224. {
  225. while (true) {
  226. if ($this->isReady)
  227. $this->onTick->run();
  228. Timer::update();
  229. $this->read();
  230. usleep(100);
  231. }
  232. }
  233. /**
  234. * Connects client to the server.
  235. */
  236. public function connect($blocking = false) {
  237. parent::connect($blocking);
  238. }
  239. /**
  240. * @param Jid $user
  241. * @return User|null
  242. */
  243. public function getUserByJid(Jid $user) {
  244. if(!$user->fromChannel()) return null;
  245. return $this->rooms[$user->bare()]->users[$user->resource];
  246. }
  247. /**
  248. * Sends message to specified jid. You could use it to send message to groupchat, but it is highly not recommended.
  249. * @param Jid $jid
  250. * @param string $message
  251. * @param string $type chat or groupchat
  252. */
  253. public function message(Jid $jid, $message, $type = 'chat')
  254. {
  255. $msg = new XmlBranch('message');
  256. $msg->addAttribute('from', $this->jid->__toString())
  257. ->addAttribute('to', $jid->__toString())
  258. ->addAttribute('type', $type);
  259. $msg->addChild(new XmlBranch('body'))->setContent($message);
  260. $this->write($msg->asXML());
  261. }
  262. /**
  263. * Changes bot status on server.
  264. * @param string $show
  265. * @param string $status
  266. */
  267. public function presence($show = "available", $status = "")
  268. {
  269. $xml = new xmlBranch("presence");
  270. $xml->addAttribute("from", $this->jid->__toString())
  271. ->addAttribute("id", uniqid());
  272. $xml->addChild(new xmlBranch("show"))->setContent($show);
  273. $xml->addChild(new xmlBranch("status"))->setContent($status);
  274. $xml->addChild(new xmlBranch("priority"))->setContent(50);
  275. $this->write($xml->asXML());
  276. }
  277. public function version(Jid $jid, Delegate $delegate)
  278. {
  279. $id = uniqid('osversion_');
  280. $xml = new xmlBranch("iq");
  281. $xml->addAttribute("from", $this->jid)
  282. ->addAttribute("to", $jid)
  283. ->addAttribute("type", "get")
  284. ->addAttribute("id", $id);
  285. $xml->addChild(new xmlBranch("query"))->addAttribute("xmlns", "jabber:iq:version");
  286. $this->write($xml->asXML());
  287. $this->wait('iq', $id, $delegate);
  288. }
  289. public function ping(Jid $jid, Delegate $delegate)
  290. {
  291. $id = uniqid('ping_');
  292. $xml = new xmlBranch("iq");
  293. $xml->addAttribute("from", $this->jid)
  294. ->addAttribute("to", $jid)
  295. ->addAttribute("type", "get")
  296. ->addAttribute("id", $id);
  297. $xml->addChild(new xmlBranch("ping"))->addAttribute("xmlns", "urn:xmpp:ping");
  298. $this->write($xml->asXML());
  299. $this->wait('iq', $id, $delegate);
  300. }
  301. public function join(Jid $room, $nick)
  302. {
  303. if (!$room->isChannel()) throw new \InvalidArgumentException('room'); // YOU SHALL NOT PASS
  304. $xml = new xmlBranch("presence");
  305. $xml->addAttribute("from", $this->jid->__toString())
  306. ->addAttribute("to", $room->bare() . '/' . $nick)
  307. ->addAttribute("id", uniqid('mucjoin_'));
  308. $xml->addChild(new xmlBranch("x"))->addAttribute("xmlns", "http://jabber.org/protocol/muc");
  309. $this->write($xml->asXML());
  310. return $this->rooms[$room->__toString()] = new Room($this, $room);
  311. }
  312. public function leave(Jid $room)
  313. {
  314. if (!$room->isChannel() || !isset($this->rooms[$room->bare()])) throw new \InvalidArgumentException('room');
  315. $xml = new xmlBranch("presence");
  316. $xml->addAttribute("from", $this->jid->__toString())
  317. ->addAttribute("to", $room->bare())
  318. ->addAttribute("id", uniqid('mucout_'));
  319. $xml->addChild(new xmlBranch("x"))->addAttribute("xmlns", "http://jabber.org/protocol/muc");
  320. $this->write($xml->asXML());
  321. unset($this->rooms[$room->bare()]);
  322. }
  323. public function role(Jid $room, $nick, $role, $reason = '')
  324. {
  325. if (!in_array($role, array('visitor', 'none', 'participant', 'moderator')))
  326. throw new \InvalidArgumentException('role');
  327. $xml = new xmlBranch("iq");
  328. $xml->addAttribute("type", "set")
  329. ->addAttribute("to", $room->__toString())
  330. ->addAttribute("id", uniqid('role_'));
  331. $xml->addChild(new xmlBranch("query"));
  332. $xml->query[0]->addAttribute("xmlns", "http://jabber.org/protocol/muc#admin");
  333. $xml->query[0]->addChild(new xmlBranch("item"));
  334. $xml->query[0]->item[0]->addAttribute("nick", $nick);
  335. $xml->query[0]->item[0]->addAttribute("role", $role);
  336. if (!empty($reason)) $xml->query[0]->item[0]->addChild(new xmlBranch("reason"))->setContent($reason);
  337. $this->write($xml->asXML());
  338. }
  339. public function affiliate(Jid $room, Jid $user, $affiliation, $reason = '')
  340. {
  341. if (!in_array($affiliation, array('none', 'outcast', 'member', 'admin', 'owner')))
  342. throw new \InvalidArgumentException('affiliation');
  343. $xml = new xmlBranch("iq");
  344. $xml->addAttribute("type", "set")
  345. ->addAttribute("to", $room->__toString())
  346. ->addAttribute("id", uniqid('affiliate_'));
  347. $xml->addChild(new xmlBranch("query"));
  348. $xml->query[0]->addAttribute("xmlns", "http://jabber.org/protocol/muc#admin");
  349. $xml->query[0]->addChild(new xmlBranch("item"));
  350. $xml->query[0]->item[0]->addAttribute("jid", $user->bare());
  351. $xml->query[0]->item[0]->addAttribute("affiliation", $affiliation);
  352. if (!empty($reason)) $xml->query[0]->item[0]->addChild(new xmlBranch("reason"))->setContent($reason);
  353. $this->write($xml->asXML());
  354. }
  355. }