├── README.md ├── index.php └── library └── Irc ├── Bot.php ├── Client.php ├── EventEmitter.php ├── Exception.php ├── Message.php ├── Socket.php └── SocketException.php /README.md: -------------------------------------------------------------------------------- 1 | # PHP IRC Bot 2 | 3 | Nothing special, really. 4 | 5 | ## What is this? 6 | 7 | This is a IRC Client and/or Bot written in PHP. 8 | It should be called via the CLI (Command-Line Interface) 9 | 10 | 11 | ## How to use it? 12 | 13 | Create a new bot instance (Or client, Bot actually has no functionality right now) 14 | 15 | $bot = new Irc\Bot( 'MyAwesomeBot', 'irc.example.com' ); 16 | 17 | and connect it 18 | 19 | $bot->connect(); 20 | 21 | You can listen to some events and react to them 22 | 23 | $bot->on( 'chat', function( $e, $bot ) { 24 | 25 | $bot->chat( $e->channel, 'Heeeey, youve written something!!' ); 26 | } ); 27 | 28 | 29 | Now open a console and fire this thing up 30 | 31 | $ php my-bot-file.php 32 | 33 | There are a ton of possible events and methods to call on the bot. 34 | A tiny documentation of them can be found below. 35 | 36 | ## Status 37 | 38 | This thing may have bugs. Actually, it may have a shitload of bugs because I didn't test most of its features. 39 | What I know is, that it works. It can connect, it sends events, it can react to them. 40 | It can communicate with your IRC server just fine. 41 | Everything else is missing development time. 42 | 43 | ## Events 44 | 45 | All event callbacks should have 2 parameters 46 | 47 | function( $e, $bot ) 48 | 49 | or 50 | 51 | function( $e, $client ) 52 | 53 | `$e` is always an object with some properties that contain event specific data (Event Args) 54 | `$client` is always the sender of the event (The Bot/Client, that called it) 55 | 56 | 57 | ### connecting, disconnecting 58 | Triggers, when the client starts (dis)connecting 59 | 60 | ### connected, disconnected 61 | Triggers, whent the client is (dis)connected 62 | 63 | ### reconnecting 64 | Triggers, when the client was disconnected by force and tries reconnecting 65 | 66 | ### tick 67 | Triggers permanently in the receive-loop. Actually, don't use this (unless you specified a tickInterval, not recommended) 68 | 69 | ### message, message:[Command] 70 | Triggers, when the client receives any kind of message from the IRC server. 71 | You can access `$e->message` to get the message instance, `$e->raw` to get the raw message sent. 72 | 73 | Command is the actual command send by the IRC server. 74 | You can find them here: http://tools.ietf.org/html/rfc2812 75 | 76 | ### send, send:[Command], sent, sent:[Command] 77 | Triggers, when the client sends or sent a message (respectively) 78 | Provides `$e->message` and `$e->raw` 79 | 80 | ### join, join:[Channel], join:[Nick], join:[Channel]:[Nick] 81 | Triggers, when someone joins a channel you're in (Including yourself). 82 | Provides `$e->nick` and `$e->channel` for you to work with. 83 | 84 | ### part, part:[Channel], part:[Nick], part:[Channel]:[Nick] 85 | Same as join, but for leaving a channel (/part) 86 | Provides `$e->nick` and `$e->channel`. 87 | 88 | ### kick, kick:[Channel], kick:[Nick], kick:[Channel]:[Nick] 89 | Same as join/part, but for users getting kicked out of a channel 90 | Provides `$e->nick` and `$e->channel`. 91 | 92 | ### notice, notice:[To], notice:[From], notice:[To]:[From] 93 | Received when someone sends a notice (Probably to yourself, so [To] is pretty useless now that I see it...) 94 | Provides `$e->from`, `$e->to` and `$e->text`. Text is the raw string of text the user sent. 95 | 96 | ### chat, chat:[Channel], chat:[Channel]:[Nick] 97 | Received when someone sends something in a channel. 98 | Provides `$e->from`, `$e->channel` and `$e->text`. 99 | 100 | ### pm, pm:[To], pm:[To]:[From] 101 | Received when someone sends you a PM. 102 | Same as notice actually. 103 | 104 | ### names, names:[Channel] 105 | Received when a NAMES request for a channel was finished. 106 | Provides `$e->names` contains an object with a `names` and a `channel` property. 107 | Self-explanatory 108 | 109 | ### list 110 | Received when a LIST request is finished. 111 | Provides `$e->list` which contains an associative array of channels (keys) and channel data (values). 112 | Channel data contains `userCount`, `topic` and `channel` for you to do awesome stuff with it. 113 | 114 | ### welcome 115 | Triggers, whent he server recognizes the client and its authentication the first time. 116 | This should be your main entry point for everything. 117 | Don't send stuff, before you got this event. It works, but it makes no sense. 118 | 119 | ### options 120 | Triggers, when the client received an ISUPPORT request that contains a bunch of options of the server. 121 | The client parses and saves those cleanly for you to do something with it. 122 | Some client methods rely on them (isChannel(), isUser() etc.) 123 | 124 | 125 | Awesome, isn't it? 126 | 127 | Now... 128 | 129 | ## Methods 130 | 131 | These are the methods you can call on your `$bot`-Object. 132 | I won't explain all of them, since the method name is somewhat self-explanatory 133 | 134 | ### getNick()/setNick( string $nickName ) 135 | 136 | ### getName()/setName( string $name ) 137 | 138 | ### getRealName()/setRealName( string $realName ) 139 | 140 | ### getServerPassword()/setServerPassword( string $password ) 141 | 142 | ### getReconnectInterval()/setReconnectInterval( int $intervalInMilliseconds ) 143 | 144 | ### getTickInterval()/setTickInterval( int $intervalInMilliseconds ) 145 | 146 | ### enableReconnection()/disableReconnection() 147 | 148 | ### enableRawMode()/disableRawMode() 149 | The client usually cares about pings etc. automatically, you don't need to handle that. 150 | But you can! RawMode disables automatic handling and you can handle everything by yourself. 151 | This also disables some events, such as names, list, welcome, options, notice, pm, chat etc. 152 | The `message` event will still work, though. 153 | 154 | ### getOption( string $option )/getOptions() 155 | Gets a server option. Just dump getOptions() once you're connected to see what options there are. 156 | 157 | ### inOptionValues( string $option, mixed $value )/inOptionKeys( string $option, mixed $value ) 158 | Checks if some value is contained in either they keys of an array option or the values. 159 | 160 | ### isChannel( string $nick )/isUser( string $nick ) 161 | Checks, if the nick provided is a channel or a user. This respects received server options. 162 | 163 | ### connect()/disconnect()/reconnect() 164 | Does exactly what you think it does 165 | 166 | ### send( string $command [, string $arg1 [, string $arg2 [, ...] ] ] ) 167 | Automatically formats your desired message parameters and sends them to the IRC server. 168 | This is the main communication method. 169 | If you want to send something, send it with this! 170 | e.g. `$bot->send( 'PRIVMSG', 'SomeOtherNick', 'Heey, this is a private message. Awesome, isn\'t it? ' )` 171 | 172 | ### sendPrefixed( string $prefix, string $command [, string $arg1 [, string $arg2 [, ...] ] ] ) 173 | Some messages require a prefix which is your own user in most cases. 174 | Right now you gotta put this together by yourself. 175 | It's either a hostname (`irc.devmonks.net`) or a user (`nickName!userName@userHost`) 176 | 177 | ### join( mixed $channel [, string $password ] ) 178 | Join a channel. Eats either one channel string, an array of channels or an associative array with channel names as keys and passwords as values. 179 | 180 | ### part( mixed $channel ) 181 | Leaves a channel. Can eat either one channel string or an array of channels. 182 | 183 | ### names( mixed $channel ) 184 | Sends a NAMES request to one or more channels (array of channels if you want more). 185 | NAMES replies with some basic channel info, like the users that are in it and their operator status 186 | 187 | ### list( [ mixed $channel[, string $server ] ] ) 188 | Same as names(), but can also be called for the whole server. 189 | Just calling `$bot->list()` will list all channels on the server. 190 | 191 | ### chat( string $channel, string $message ), pm( string $channel, string $message ) 192 | chat should be used for channels, pm for users, but actually, it doesn't matter at all. 193 | This sends a message to either a channel or a user (private message) 194 | 195 | ### nick( string $nick ) 196 | Sets a new nick name for the client. 197 | 198 | 199 | 200 | That was it! 201 | 202 | If you want to know more about this, you may contact me anytime. 203 | Merge requests for new features are welcome. 204 | 205 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | on( 'connecting', function() { 25 | 26 | echo "Connecting...\n"; 27 | } ) 28 | 29 | //When we connected successfully (But didn't send the login yet!) 30 | ->on( 'connected', function() { 31 | 32 | echo "Connected!\n"; 33 | } ) 34 | 35 | //When we send a message 36 | ->on( 'send', function( $e ) { 37 | 38 | echo "Sending $e->message\n"; 39 | } ) 40 | 41 | //When we received a message 42 | ->on( 'message', function( $e ) { 43 | 44 | echo "Received $e->message\n"; 45 | } ) 46 | 47 | //When we're authenticated and the server welcomes us 48 | ->on( 'welcome', function( $e, $bot ) { 49 | 50 | //We list all the channels. This will trigger the 'list' event 51 | $bot->listChannels(); 52 | } ) 53 | 54 | //This triggers once for the 'list' event 55 | ->once( 'list', function( $e, $bot ) { 56 | 57 | //Once the 'list' event is triggered, join all channels that have been listed 58 | $bot->join( array_keys( $e->list ) ); 59 | } ) 60 | 61 | //When WE joined a channel 62 | ->on( 'join:TestBot', function( $e, $bot ) { 63 | 64 | $bot->chat( $e->channel, 'What\'s up?' ); 65 | } ) 66 | 67 | //When there was a 'NAMES' request (Retrieve channel user infos) 68 | ->on( 'names', function( $e, $bot ) { } ) 69 | 70 | //When someone writes in some channel 71 | ->on( 'chat', function( $e, $bot ) { 72 | 73 | echo "CHAT($e->channel): $e->from: $e->text\n"; 74 | } ) 75 | 76 | //When someone writes us a PM 77 | ->on( 'pm', function( $e, $bot ) { 78 | 79 | echo "PM($e->to): $e->from: $e->text\n"; 80 | } ) 81 | 82 | //When someone writes a notice 83 | ->on( 'notice', function( $e, $bot ) { 84 | 85 | echo "NOTICE($e->to): $e->from: $e->text\n"; 86 | } ) 87 | 88 | //When we retrieved the server options 89 | ->on( 'options', function( $e, $bot ) { 90 | 91 | echo "Received server options.\n"; 92 | } ) 93 | 94 | 95 | //Now finally connect that thing. 96 | ->connect(); -------------------------------------------------------------------------------- /library/Irc/Bot.php: -------------------------------------------------------------------------------- 1 | nick = $nick; 160 | 161 | //attach basic triggers 162 | $this->on( 'message', array( $this, 'handleMessage' ) ); 163 | $this->on( 'connected', array( $this, 'sendLogin' ) ); 164 | 165 | parent::__construct( $server, $port ); 166 | } 167 | 168 | public function getNick() { 169 | 170 | return $this->nick; 171 | } 172 | 173 | public function setNick( $nick ) { 174 | 175 | if( $this->isConnected() ) { 176 | 177 | //Change nick via NICK command 178 | //The response of it will change it internally then 179 | //It's possible that the nick is taken, we try to stay in sync 180 | $this->nick( $nick ); 181 | } else { 182 | //Else we just change it internally directly 183 | $this->nick = $nick; 184 | } 185 | 186 | return $this; 187 | } 188 | 189 | public function getName() { 190 | 191 | return $this->name; 192 | } 193 | 194 | public function setName( $name ) { 195 | 196 | if( !$this->isConnected() ) { 197 | 198 | $this->name = $name; 199 | } else { 200 | 201 | //Set it on the next disconnect 202 | $this->once( 'disconnected', function( $e, $c ) use( $name ) { 203 | 204 | $c->setName( $name ); 205 | } ); 206 | } 207 | 208 | return $this; 209 | } 210 | 211 | public function getRealName() { 212 | 213 | return $this->realName; 214 | } 215 | 216 | public function setRealName( $realName ) { 217 | 218 | if( !$this->isConnected() ) { 219 | 220 | $this->realName = $realName; 221 | } else { 222 | 223 | //Set it on the next disconnect 224 | $this->once( 'disconnected', function( $e, $c ) use( $realName ) { 225 | 226 | $c->setRealName( $realName ); 227 | } ); 228 | } 229 | 230 | return $this; 231 | } 232 | 233 | public function getServerPassword() { 234 | 235 | return $this->serverPassword; 236 | } 237 | 238 | public function setServerPassword( $password ) { 239 | 240 | if( !$this->isConnected() ) { 241 | 242 | $this->serverPassword = $password; 243 | } 244 | 245 | return $this; 246 | } 247 | 248 | public function getReconnectInterval() { 249 | 250 | return $this->reconnectInterval; 251 | } 252 | 253 | public function setReconnectInterval( $interval ) { 254 | 255 | $this->reconnectInterval = $interval; 256 | } 257 | 258 | public function getTickInterval() { 259 | 260 | return $this->tickInterval; 261 | } 262 | 263 | public function setTickInterval( $interval ) { 264 | 265 | $this->tickInterval = $interval; 266 | 267 | return $this; 268 | } 269 | 270 | public function enableReconnection() { 271 | 272 | $this->reconnect = true; 273 | 274 | return $this; 275 | } 276 | 277 | public function disableReconnection() { 278 | 279 | $this->reconnect = false; 280 | 281 | return $this; 282 | } 283 | 284 | public function enableRawMode() { 285 | 286 | $this->rawMode = true; 287 | 288 | return $this; 289 | } 290 | 291 | public function disableRawMode() { 292 | 293 | $this->rawMode = false; 294 | 295 | return $this; 296 | } 297 | 298 | public function getOption( $option, $defaultValue = null ) { 299 | 300 | $o = strtoupper( $option ); 301 | 302 | if( empty( $this->options[ $o ] ) ) 303 | if( empty( $this->options[ $option ] ) ) 304 | return $defaultValue; 305 | else 306 | return $this->options[ $option ]; 307 | 308 | return $this->options[ $o ]; 309 | } 310 | 311 | public function getOptions() { 312 | 313 | return $this->options; 314 | } 315 | 316 | public function inOptionValues( $option, $value ) { 317 | 318 | $oValue = $this->getOption( $option, array() ); 319 | 320 | return in_array( $value, $oValue ); 321 | } 322 | 323 | public function inOptionKeys( $option, $value ) { 324 | 325 | $oValue = $this->getOption( $option, array() ); 326 | 327 | return in_array( $value, array_keys( $oValue ) ); 328 | } 329 | 330 | public function isChannel( $nick ) { 331 | 332 | return $this->inOptionKeys( 'chantypes', $nick[ 0 ] ); 333 | } 334 | 335 | public function isUser( $nick ) { 336 | 337 | return !$this->isChannel( $nick ); 338 | } 339 | 340 | public function connect( $force = false ) { 341 | 342 | if( $this->isConnected() ) { 343 | //Already connected 344 | if( $force ) 345 | $this->disconnect(); 346 | else 347 | return $this; 348 | } 349 | 350 | parent::connect(); 351 | 352 | //The Message Receive Task 353 | do { 354 | 355 | $this->emit( 'tick' ); 356 | //check, if we still got a connection 357 | if( !$this->isConnected() ) { 358 | 359 | //We got disconnected! 360 | //Try to reconnect. 361 | if( $this->reconnect ) { 362 | $this->emit( 'reconnecting', array( 363 | 'server' => $this->server, 364 | 'port' => $this->port, 365 | 'interval' => $this->reconnectInterval 366 | ) ); 367 | sleep( $this->reconnectInterval ); 368 | $this->connect(); 369 | //After this point, connect() will be stuck in an endless loop in the best case. 370 | //"connected" will be triggered, once it reconnects 371 | } 372 | break; 373 | } 374 | 375 | $message = trim( $this->receiveLine() ); 376 | 377 | if( empty( $message ) ) 378 | continue; 379 | 380 | $msg = Message::parse( $message ); 381 | $this->emit( "message, message:$msg->command", array( 'message' => $msg, 'raw' => trim( $message ) ) ); 382 | //slow this down a bit 383 | sleep( $this->tickInterval ); 384 | } while( true ); 385 | 386 | return $this; 387 | } 388 | 389 | public function reconnect() { 390 | 391 | return $this->disconnect() 392 | ->connect(); 393 | } 394 | 395 | public function send( $command ) { 396 | 397 | $args = func_get_args(); 398 | unset( $args[ 0 ] ); 399 | $args = array_values( array_filter( $args, function( $arg ) { 400 | $arg = trim( $arg ); 401 | return !empty( $arg ); 402 | } ) ); 403 | 404 | $message = $command instanceof Message ? $command : new Message( $command, $args ); 405 | 406 | if( !$this->isConnected() ) { 407 | //We can't send anything, when we're not connected. 408 | //Should we really throw an error or let the user handle it via events? 409 | return $this; 410 | } 411 | 412 | $this->emit( "send, send:$message->command", array( 'message' => $message ) ); 413 | $this->sendLine( (string)$message ); 414 | $this->emit( "sent, sent:$message->command", array( 'message' => $message ) ); 415 | 416 | return $this; 417 | } 418 | 419 | public function sendPrefixed( $prefix, $command ) { 420 | 421 | $args = func_get_args(); 422 | unset( $args[ 0 ] ); 423 | unset( $args[ 1 ] ); 424 | $args = array_values( array_filter( $args, function( $arg ) { 425 | $arg = trim( $arg ); 426 | return !empty( $arg ); 427 | } ) ); 428 | 429 | $message = new Message( $command, $args, $prefix ); 430 | 431 | return $this->send( $message ); 432 | } 433 | 434 | public function join( $channel, $password = null ) { 435 | 436 | $channelString = ''; 437 | $passString = ''; 438 | 439 | if( is_array( $channel ) ) { 440 | if( array_keys( $channel ) !== range( 0, count( $channel ) - 1 ) ) { 441 | //channel => password array 442 | $channelString = implode( ',', array_keys( $channel ) ); 443 | $passString = implode( ',', array_values( $channel ) ); 444 | } else { 445 | $channelString = implode( ',', $channel ); 446 | } 447 | } else { 448 | 449 | $channelString = $channel; 450 | if( $password ) 451 | $passString = $password; 452 | } 453 | 454 | $this->send( self::CMD_JOIN, $channelString, $passString ); 455 | 456 | return $this; 457 | } 458 | 459 | public function part( $channel ) { 460 | 461 | $channel = is_array( $channel ) ? implode( ',', $channel ) : $channel; 462 | 463 | $this->send( self::CMD_PART, $channel ); 464 | 465 | return $this; 466 | } 467 | 468 | public function names( $channel = null, $server = null ) { 469 | 470 | $channel = is_array( $channel ) ? implode( ',', $channel ) : $channel; 471 | 472 | $this->send( self::CMD_NAMES, $channel, $server ); 473 | 474 | return $this; 475 | } 476 | 477 | public function listChannels( $channel = null, $server = null ) { 478 | 479 | $channel = is_array( $channel ) ? implode( ',', $channel ) : $channel; 480 | 481 | $this->send( self::CMD_LIST, $channel, $server ); 482 | 483 | return $this; 484 | } 485 | 486 | public function chat( $channel, $message ) { 487 | 488 | $this->sendPrefixed( "$this->nick!$this->name", self::CMD_PRIVMSG, $channel, $message ); 489 | 490 | return $this; 491 | } 492 | 493 | public function pm( $nick, $message ) { 494 | 495 | //actually just an alias..... 496 | return $this->chat( $nick, $message ); 497 | } 498 | 499 | public function nick( $nick = null ) { 500 | 501 | if( !$nick ) 502 | $nick = $this->nick; 503 | 504 | $this->send( self::CMD_NICK, $nick ); 505 | } 506 | 507 | protected function handleMessage( $e ) { 508 | 509 | /* This one handles basic server reponses so that the user 510 | can care about useful functionality instead. 511 | 512 | You can use rawMode to disable automatic interaction in here. 513 | */ 514 | 515 | if( $this->rawMode ) 516 | return; 517 | 518 | $message = $e->message; 519 | $raw = $e->raw; 520 | 521 | static $namesReply = null, 522 | $listReply = null; 523 | switch( $message->command ) { 524 | case self::CMD_PING: 525 | //Reply to pings 526 | $this->send( self::CMD_PONG, $message->getArg( 0, $this->server ) ); 527 | break; 528 | case self::CMD_JOIN: 529 | //Emit channel join events 530 | $nick = $message->nick ? $message->nick : $this->nick; 531 | $channel = $message->getArg( 0 ); 532 | 533 | $this->emit( "join, join:$channel, join:$nick, join:$channel:$nick", array( 534 | 'nick' => $nick, 535 | 'channel' => $channel 536 | ) ); 537 | break; 538 | case self::CMD_PART: 539 | //Emit channel part events 540 | $nick = $message->nick ? $message->nick : $this->nick; 541 | $channel = $this->addAllChannel( $message->getArg( 0 ) ); 542 | 543 | $this->emit( "part, part:$channel, part:$nick, part:$channel:$nick", array( 544 | 'nick' => $nick, 545 | 'channel' => $channel 546 | ) ); 547 | break; 548 | case self::CMD_KICK: 549 | //Emit kick events 550 | $channel = $message->getArg( 0 ); 551 | $nick = $message->getArg( 1 ); 552 | 553 | $this->emit( "kick, kick:$channel, kick:$nick, kick:$channel:$nick", array( 554 | 'nick' => $nick, 555 | 'channel' => $channel 556 | ) ); 557 | break; 558 | case self::CMD_NOTICE: 559 | //Emit notice message events 560 | $from = $message->nick; 561 | $to = $message->getArg( 0 ); 562 | $text = $message->getArg( 1, '' ); 563 | 564 | $this->emit( "notice, notice:$to, notice:$to:$from", array( 565 | 'from' => $from, 566 | 'to' => $to, 567 | 'text' => $text 568 | ) ); 569 | break; 570 | case self::CMD_PRIVMSG: 571 | //Handle private messages (Normal chat messages) 572 | $from = $message->nick; 573 | $to = $message->getArg( 0 ); 574 | $text = $message->getArg( 1, '' ); 575 | 576 | if( $this->isChannel( $to ) ) { 577 | 578 | $this->emit( "chat, chat:$to, chat:$to:$from", array( 579 | 'from' => $from, 580 | 'channel' => $to, 581 | 'text' => $text 582 | ) ); 583 | break; 584 | } 585 | 586 | $this->emit( "pm, pm:$to, pm:$to:$from", array( 587 | 'from' => $from, 588 | 'to' => $to, 589 | 'text' => $text 590 | ) ); 591 | break; 592 | case self::RPL_NAMREPLY: 593 | 594 | $namesReply = (object)array( 595 | 'nick' => $message->getArg( 0 ), 596 | 'channelType' => $message->getArg( 1 ), 597 | 'channel' => $message->getArg( 2 ), 598 | 'names' => array_map( 'trim', explode( ' ', $message->getArg( 3 ) ) ) 599 | ); 600 | case self::RPL_ENDOFNAMES: 601 | 602 | if( empty( $namesReply ) ) 603 | break; 604 | 605 | $channel = $namesReply->channel; 606 | 607 | $this->emit( "names, names:$channel", array( 608 | 'names' => $namesReply, 609 | 'channel' => $channel 610 | ) ); 611 | $namesReply = null; 612 | break; 613 | case self::RPL_LISTSTART: 614 | 615 | $listReply = array(); 616 | break; 617 | case self::RPL_LIST: 618 | 619 | $channel = $message->getArg( 1 ); 620 | $listReply[ $channel ] = (object)array( 621 | 'channel' => $channel, 622 | 'userCount' => $message->getArg( 2 ), 623 | 'topic' => $message->getArg( 3 ) 624 | ); 625 | break; 626 | case self::RPL_LISTEND: 627 | 628 | $this->emit( 'list', array( 'list' => $listReply ) ); 629 | $listReply = null; 630 | break; 631 | case self::RPL_WELCOME: 632 | //correct internal nickname, if given a new one by the server 633 | $this->nick = $message->getArg( 0, $this->nick ); 634 | 635 | $this->emit( 'welcome' ); 636 | break; 637 | case self::RPL_ISUPPORT: 638 | 639 | $args = $message->args; 640 | unset( $args[ 0 ], $args[ count( $args ) - 1 ] ); 641 | 642 | foreach( $args as $arg ) { 643 | 644 | list( $key, $val ) = explode( '=', $arg ); 645 | 646 | //handle some keys specifically 647 | switch( strtolower( $key ) ) { 648 | case 'prefix': 649 | 650 | list( $modes, $prefixes ) = explode( ')', ltrim( $val, '(' ) ); 651 | $modes = str_split( $modes ); 652 | $prefixes = str_split( $prefixes ); 653 | $val = array(); 654 | foreach( $modes as $k => $v ) 655 | $val[ $prefixes[ $k ] ] = $v; 656 | 657 | break; 658 | case 'chantypes': 659 | case 'statusmsg': 660 | case 'elist': 661 | 662 | $val = str_split( $val ); 663 | break; 664 | case 'chanmodes': 665 | case 'language': 666 | 667 | $val = explode( ',', $val ); 668 | break; 669 | } 670 | 671 | $this->options[ $key ] = $val; 672 | } 673 | 674 | $this->emit( 'options', array( 'options' => $this->options ) ); 675 | break; 676 | } 677 | } 678 | 679 | protected function sendLogin( $e ) { 680 | 681 | if( $this->rawMode ) 682 | return; 683 | 684 | if( !empty( $this->serverPassword ) ) 685 | $this->send( self::CMD_PASS, $this->serverPassword ); 686 | 687 | if( empty( $this->name ) ) 688 | $this->name = $this->nick; 689 | 690 | if( empty( $this->realName ) ) 691 | $this->realName = $this->name; 692 | 693 | $this->nick(); 694 | $this->send( self::CMD_USER, $this->name, 8, '*', $this->realName ); 695 | } 696 | 697 | 698 | } -------------------------------------------------------------------------------- /library/Irc/EventEmitter.php: -------------------------------------------------------------------------------- 1 | on( trim( $event ), $callback ); 18 | } 19 | return $this; 20 | } 21 | 22 | if( empty( $this->eventCallbacks[ $event ] ) ) 23 | $this->eventCallbacks[ $event ] = array(); 24 | 25 | $this->eventCallbacks[ $event ][] = $callback; 26 | 27 | return $this; 28 | } 29 | 30 | public function off( $event, $callback ) { 31 | 32 | if( empty( $this->eventCallbacks[ $event ] ) ) 33 | return $this; 34 | 35 | $idx = null; 36 | foreach( $this->eventCallbacks[ $event ] as $key => $cb ) 37 | if( $callback === $cb ) { 38 | $idx = $key; 39 | break; 40 | } 41 | 42 | array_splice( $this->eventCallbacks, $idx, 1 ); 43 | 44 | return $this; 45 | } 46 | 47 | public function once( $event, $callback ) { 48 | 49 | if( empty( $this->onceEventCallbacks[ $event ] ) ) 50 | $this->onceEventCallbacks[ $event ] = array(); 51 | 52 | $this->onceEventCallbacks[ $event ][] = $callback; 53 | 54 | return $this; 55 | } 56 | 57 | public function emit( $event, $args = array() ) { 58 | 59 | if( strpos( $event, ',' ) !== false ) { 60 | 61 | $events = explode( ',', $event ); 62 | foreach( $events as $event ) { 63 | 64 | $this->emit( trim( $event ), $args ); 65 | } 66 | return $this; 67 | } 68 | 69 | $args[ 'time' ] = time(); 70 | $args[ 'event' ] = $event; 71 | $args[ 'sender' ] = $this; 72 | 73 | if( !empty( $this->onceEventCallbacks[ $event ] ) ) { 74 | 75 | foreach( $this->onceEventCallbacks[ $event ] as $callback ) 76 | call_user_func( $callback, (object)$args, $this ); 77 | $this->onceEventCallbacks[ $event ] = array(); 78 | } 79 | 80 | if( !empty( $this->eventCallbacks[ $event ] ) ) { 81 | 82 | foreach( $this->eventCallbacks[ $event ] as $callback ) 83 | call_user_func( $callback, (object)$args, $this ); 84 | } 85 | 86 | return $this; 87 | } 88 | } -------------------------------------------------------------------------------- /library/Irc/Exception.php: -------------------------------------------------------------------------------- 1 | command = $command; 16 | $this->args = $args; 17 | 18 | if( !empty( $prefix ) ) { 19 | 20 | if( strpos( $prefix, '!' ) !== false ) { 21 | 22 | $parts = preg_split( '/[!@]/', $prefix ); 23 | $this->nick = !empty( $parts[ 0 ] ) ? $parts[ 0 ] : ''; 24 | $this->name = !empty( $parts[ 1 ] ) ? $parts[ 1 ] : ''; 25 | $this->host = !empty( $parts[ 2 ] ) ? $parts[ 2 ] : ''; 26 | } else { 27 | 28 | $this->nick = $prefix; 29 | } 30 | } 31 | } 32 | 33 | public function __toString() { 34 | 35 | $args = array_map( 'strval', $this->args ); 36 | $len = count( $args ); 37 | $last = $len - 1; 38 | 39 | if( $len > 0 && ( strpos( ' ', $args[ $last ] ) !== -1 || $args[ $last ][ 0 ] === ':' ) ) { 40 | 41 | $args[ $last ] = ':'.$args[ $last ]; 42 | } 43 | 44 | $prefix = $this->getHostString(); 45 | 46 | array_unshift( $args, $this->command ); 47 | if( !empty( $prefix ) ) 48 | array_unshift( $args, ":$prefix" ); 49 | 50 | return implode( ' ', $args ); 51 | } 52 | 53 | public function getHostString() { 54 | 55 | $str = "$this->nick"; 56 | 57 | if( !empty( $this->name ) ) 58 | $str .= "!$this->name"; 59 | 60 | if( !empty( $this->host ) ) 61 | $str .= "@$this->host"; 62 | 63 | return $str; 64 | } 65 | 66 | public function getArg( $index, $defaultValue = null ) { 67 | 68 | return !empty( $this->args[ $index ] ) ? $this->args[ $index ] : $defaultValue; 69 | } 70 | 71 | public static function parse( $message ) { 72 | 73 | $message = trim( $message ); 74 | 75 | if( empty( $message ) ) 76 | return null; 77 | 78 | $prefix = ''; 79 | $command = ''; 80 | $args = array(); 81 | $matches = array(); 82 | 83 | if( preg_match( '/^ 84 | (:(?[^ ]+)\s+)? #the prefix (either "server" or "nick!user@host") 85 | (?[^ ]+) #the command (e.g. NOTICE, PRIVMSG) 86 | (?.*) #The argument string 87 | $/x', $message, $matches ) ) { 88 | 89 | $matches = array_map( 'trim', $matches ); 90 | 91 | if( !empty( $matches[ 'prefix' ] ) ) 92 | $prefix = $matches[ 'prefix' ]; 93 | 94 | if( !empty( $matches[ 'command' ] ) ) 95 | $command = $matches[ 'command' ]; 96 | 97 | if( !empty( $matches[ 'args' ] ) ) { 98 | if( strpos( $matches[ 'args' ], ' :' ) !== false ) { 99 | $parts = explode( ' :', $matches[ 'args' ], 2 ); 100 | $args = explode( ' ', $parts[ 0 ] ); 101 | $args[] = $parts[ 1 ]; 102 | } else if( strpos( $matches[ 'args' ], ':' ) === 0 ) 103 | $args[] = substr( $matches[ 'args' ], 1 ); 104 | else 105 | $args = explode( ' ', $matches[ 'args' ] ); 106 | } 107 | 108 | $args = array_values( array_filter( $args, function( $val ) { 109 | 110 | $val = trim( $val ); 111 | return !empty( $val ); 112 | } ) ); 113 | } else 114 | return new Message( 'UNKNOWN', array( $message ) ); 115 | 116 | return new Message( $command, $args, $prefix ); 117 | } 118 | } -------------------------------------------------------------------------------- /library/Irc/Socket.php: -------------------------------------------------------------------------------- 1 | server = $server; 18 | $this->port = $port; 19 | $this->timeOut = $timeOut; 20 | } 21 | 22 | public function connect() { 23 | 24 | $this->emit( 'connecting', array( 'server' => $this->server, 'port' => $this->port ) ); 25 | $errNo = null; 26 | $errStr = null; 27 | $this->handle = @fsockopen( 28 | strval( $this->server ), 29 | intval( $this->port ), 30 | $errNo, 31 | $errStr, 32 | floatval( $this->timeOut ) 33 | ); 34 | 35 | if( !$this->isConnected() ) { 36 | 37 | throw new SocketException( "Failed to connect($errNo): $errStr" ); 38 | } 39 | 40 | $this->emit( 'connected', array( 'server' => $this->server, 'port' => $this->port ) ); 41 | 42 | return $this; 43 | } 44 | 45 | public function disconnect() { 46 | 47 | $this->emit( 'disconnecting', array( 'server' => $this->server, 'port' => $this->port ) ); 48 | 49 | if( $this->isConnected() ) 50 | fclose( $this->handle ); 51 | 52 | $this->emit( 'disconnected', array( 'server' => $this->server, 'port' => $this->port ) ); 53 | 54 | return $this; 55 | } 56 | 57 | public function isConnected() { 58 | 59 | return is_resource( $this->handle ); 60 | } 61 | 62 | public function send( $message ) { 63 | 64 | fputs( $this->handle, $message ); 65 | 66 | return $this; 67 | } 68 | 69 | public function sendLine( $message ) { 70 | 71 | fputs( $this->handle, "$message\r\n" ); 72 | 73 | return $this; 74 | } 75 | 76 | public function receive( $len = 32 ) { 77 | 78 | return fread( $this->handle, $len ); 79 | } 80 | 81 | public function receiveLine() { 82 | 83 | return fgets( $this->handle, $this->bufferSize ); 84 | } 85 | } -------------------------------------------------------------------------------- /library/Irc/SocketException.php: -------------------------------------------------------------------------------- 1 |