Skip to content
This repository was archived by the owner on May 4, 2018. It is now read-only.

Commit a481940

Browse files
committed
Initial commit.
0 parents  commit a481940

File tree

5 files changed

+386
-0
lines changed

5 files changed

+386
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
client.php

LICENSE

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2015 Alex Ingram
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# PHP IRC Library
2+
This is a library for communicating with IRC servers using PHP.

example.php

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
require_once("irc_client.php");
3+
4+
class ExampleBot extends IRCClient {
5+
// Change configuration options.
6+
public $ConnectionTimeout = 60;
7+
public $CallbackTimeout = 15;
8+
public $VerboseLog = true;
9+
public $LogSockets = false;
10+
public $CTCPVersionReply = "PHP IRC Library (c) 2015 Alex Ingram :: https://github.com/ReimuHakurei/php-irc";
11+
12+
protected function recv($Message) {
13+
if ($Message != NULL) {
14+
//$this->log("Received message.");
15+
} else {
16+
//$this->log("Received empty callback.");
17+
}
18+
}
19+
}
20+
21+
$Client = new ExampleBot('irc://irc.myserver.net:+6697/test','TestBot','Bot','An IRC Bot');
22+
?>

irc_client.php

+342
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
<?php
2+
/*
3+
PHP IRC Library
4+
irc_client.php
5+
6+
Copyright (c) 2015 Alex Ingram
7+
8+
Permission is hereby granted, free of charge, to any person obtaining a copy
9+
of this software and associated documentation files (the "Software"), to deal
10+
in the Software without restriction, including without limitation the rights
11+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
copies of the Software, and to permit persons to whom the Software is
13+
furnished to do so, subject to the following conditions:
14+
15+
The above copyright notice and this permission notice shall be included in
16+
all copies or substantial portions of the Software.
17+
18+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
THE SOFTWARE.
25+
*/
26+
27+
class IRCClient {
28+
// This will be set to "false" if a fatal error has been encountered inside
29+
// of the class.
30+
protected $SanityCheckOK = false;
31+
private $ServerSocket;
32+
33+
34+
// Server connection information:
35+
protected $ServerHostname = '';
36+
protected $ServerPort = 0;
37+
protected $ServerUseSSL = false;
38+
39+
protected $ClientNick = '';
40+
protected $ClientIdent = '';
41+
protected $ClientGecos = '';
42+
43+
44+
// Configurable options:
45+
public $ConnectionTimeout = 30;
46+
public $CallbackTimeout = 60;
47+
public $VerboseLog = true;
48+
public $LogSockets = false;
49+
public $CTCPVersionReply = "PHP IRC Library (c) 2015 Alex Ingram :: https://github.com/ReimuHakurei/php-irc";
50+
51+
// Internal database arrays:
52+
protected $CurrentChannels = [];
53+
protected $QueuedChannels = [];
54+
55+
public function __construct($URI, $Nick, $Ident, $Gecos) {
56+
// In the construct, we will fill in all of the internal server
57+
// connection information variables.
58+
//
59+
// The format of an IRC URI is simple. It can, in general usage,
60+
// also include a list of channels. If those are found here,
61+
// they will be added to an array of a to-join channel queue.
62+
//
63+
// An IRC URI is of the following format:
64+
// irc://serverHostname:[+]port[/channel1,channel2]
65+
//
66+
// The + in the port section signifies an SSL connection.
67+
//
68+
// Although the IRC protocol does allow for channels starting
69+
// with characters other than # (most commonly things such
70+
// as +, &, and $), any channels in an IRC URI are assumed
71+
// to use the standard # prefix, and as such, the prefix
72+
// should be omitted. Channels should be comma-delimited.
73+
74+
$this->log("PHP-IRCClient (c) 2015 Alex Ingram");
75+
$this->log("Starting up...");
76+
77+
$ExplodedURI = explode("/",$URI);
78+
if (count($ExplodedURI) >= 3) {
79+
$HostnamePortBlock = $ExplodedURI[2];
80+
$ChannelList = '';
81+
82+
if (array_key_exists(3,$ExplodedURI)) {
83+
$this->QueuedChannels = explode(",",$ExplodedURI[3]);
84+
85+
foreach ($this->QueuedChannels as &$channel) {
86+
$channel = '#' . $channel;
87+
}
88+
}
89+
90+
$HostnamePort = explode(":",$HostnamePortBlock);
91+
92+
if (count($HostnamePort) == 2) {
93+
$this->ServerHostname = $HostnamePort[0];
94+
$this->ServerPort = $HostnamePort[1];
95+
} else {
96+
$this->error("Malformed IRC URI in construct.");
97+
}
98+
99+
if ($this->ServerPort[0] == "+") {
100+
$Port = str_split($this->ServerPort);
101+
array_shift($Port);
102+
103+
$this->ServerPort = implode($Port);
104+
$this->ServerUseSSL = true;
105+
}
106+
} else {
107+
$this->error("Malformed IRC URI in construct.");
108+
}
109+
110+
// Just for debugging.
111+
$this->log(" Server : " . $this->ServerHostname . ":" . $this->ServerPort . ", using SSL: " . var_export($this->ServerUseSSL, true));
112+
$this->log(" Channels : " . implode(", ", $this->QueuedChannels));
113+
114+
$this->ClientNick = $Nick;
115+
$this->ClientIdent = $Ident;
116+
$this->ClientGecos = $Gecos;
117+
118+
$this->SanityCheckOK = true;
119+
120+
$this->connect();
121+
}
122+
123+
124+
125+
// Logging functions
126+
127+
protected function log($LogText) {
128+
if ($this->VerboseLog) {
129+
echo "[" . date("Y-m-d H:i:s") . "] " . implode(explode("\r\n",$LogText)) . "\n";
130+
}
131+
}
132+
133+
protected function error($ErrorText) {
134+
$this->log("ERROR: " . $ErrorText);
135+
throw new Exception($ErrorText);
136+
}
137+
138+
139+
// Connection handling functions
140+
141+
protected function connect() {
142+
// Note: This function will block until the bot disconnects.
143+
144+
145+
if (!$this->SanityCheckOK) {
146+
$this->log("WARNING: The constructor did not complete, expect major issues!");
147+
}
148+
149+
150+
$this->log("Connecting...");
151+
152+
if ($this->ServerUseSSL) {
153+
$ServerURI = "tls://" . $this->ServerHostname . ":" . $this->ServerPort;
154+
} else {
155+
$ServerURI = "tcp://" . $this->ServerHostname . ":" . $this->ServerPort;
156+
}
157+
158+
// Almost no IRC servers have valid SSL certificates, so we'll just ignore them.
159+
$Context = stream_context_create();
160+
stream_context_set_option($Context, 'ssl', 'verify_peer', false);
161+
stream_context_set_option($Context, 'ssl', 'verify_peer_name', false);
162+
163+
164+
@$this->ServerSocket = stream_socket_client($ServerURI, $errno, $errstr, $this->ConnectionTimeout, STREAM_CLIENT_CONNECT, $Context);
165+
166+
stream_set_timeout($this->ServerSocket,$this->CallbackTimeout);
167+
168+
if (!$this->ServerSocket) {
169+
$this->log("Server connection failed: $errstr ($errno)");
170+
} else {
171+
$this->log("Connection established. Registering with server...");
172+
173+
$RegistrationComplete = false;
174+
175+
$this->nick($this->ClientNick);
176+
$this->send("USER " . $this->ClientIdent . " 0 * :" . $this->ClientGecos);
177+
178+
while (!feof($this->ServerSocket)) {
179+
if ($Message = fgets($this->ServerSocket, 512)) {
180+
if ($this->LogSockets) {
181+
$this->log(">> " . $Message);
182+
}
183+
184+
$Message = $this->parse_message($Message);
185+
186+
if (($Message->Command == "001" || $Message->Command == "002" || $Message->Command == "003" || $Message->Command == "004") && (!$RegistrationComplete )) {
187+
$RegistrationComplete = true;
188+
189+
$this->log("Registration complete.");
190+
191+
foreach($this->QueuedChannels as $Channel) {
192+
$this->join($Channel);
193+
}
194+
}
195+
196+
if ($RegistrationComplete) {
197+
$this->recv($Message);
198+
}
199+
} else {
200+
$this->recv(NULL);
201+
}
202+
}
203+
}
204+
205+
$this->log("Socket disconnected.");
206+
}
207+
208+
private function parse_message($Message) {
209+
// This function handles the IRC protocol itself (ie: responding to PINGs, CTCP requests, etc), and
210+
// will return a message object which will be passed along to the user's code.
211+
212+
// We want to keep a copy of the raw message, in case the user wants it.
213+
$RawMessage = $Message;
214+
215+
216+
// RFC 2812 says that any messages lacking a prefix can be assumed to originate from the connection itself.
217+
// As such, we will set the prefix of any non-prefixed message to the server hostname, for parsing purposes.
218+
// This also allows us to process all messages in the same block of code.
219+
if ($Message[0] != ':') {
220+
$Message = ":" . $this->ServerHostname . " " . $Message;
221+
}
222+
223+
// We're now going to remove that ':' we just added above.
224+
$MessageArray = str_split($Message);
225+
array_shift($MessageArray);
226+
$Message = implode($MessageArray);
227+
unset($MessageArray);
228+
229+
// Remove the CR-LF from the end...
230+
$Message = substr($Message,0,sizeof($Message) - 3);
231+
232+
// RFC 2812 says that the trailing parameter (the one starting with a :), if present, should be treated
233+
// exactly the same as any other parameter.
234+
// Pull the trailing parameter, if present...
235+
$Trailing = NULL;
236+
$MessageArray = explode(":",$Message);
237+
if (sizeof($MessageArray) > 1) {
238+
$Trailing = $MessageArray[1];
239+
$Message = $MessageArray[0];
240+
}
241+
unset($MessageArray);
242+
243+
// Pull the middle parameters...
244+
$MessageArray = explode(" ",$Message);
245+
// ...and slap the trailing parameter back on the end.
246+
$MessageArray[] = $Trailing;
247+
248+
if (sizeof($MessageArray) >= 3) {
249+
// Time to chop up that prefix into its' three segments!
250+
$MessagePrefix = split('[!@]',$MessageArray[0]);
251+
array_shift($MessageArray);
252+
if (sizeof($MessagePrefix) == 3) {
253+
$Nick = $MessagePrefix[0];
254+
$User = $MessagePrefix[1];
255+
$Host = $MessagePrefix[2];
256+
} else {
257+
$Nick = $MessagePrefix[0];
258+
$User = NULL;
259+
$Host = NULL;
260+
}
261+
262+
$Command = $MessageArray[0];
263+
array_shift($MessageArray);
264+
265+
$Parameters = array_values(array_filter($MessageArray));
266+
267+
$Message = (object) array('Nick' => $Nick, 'User' => $User, 'Host' => $Host, 'Command' => $Command, 'Parameters' => $Parameters, 'RawMessage' => $RawMessage);
268+
269+
// Respond to PINGs
270+
if ($Command == "PING") {
271+
$this->send("PONG :" . $Parameters[0]);
272+
}
273+
274+
// Handle CTCP requests
275+
if (($Command == "PRIVMSG" || $Command == "NOTICE") && array_key_exists(1,$Parameters)) {
276+
if (($Parameters[1][0] == "\001") && ($Parameters[1][sizeof($Parameters[1])-1] == "\001")) {
277+
if ($Parameters[1] == "\001VERSION\001") {
278+
$this->send("NOTICE $Nick :" . $this->CTCPVersionReply);
279+
280+
$this->log("Received CTCP VERSION request from $Nick.");
281+
}
282+
283+
if ($Parameters[1][1] == 'P' && $Parameters[1][2] == 'I' && $Parameters[1][3] == 'N' && $Parameters[1][4] == 'G') {
284+
$this->send("NOTICE $Nick :" . $Parameters[1]);
285+
286+
$this->log("Received CTCP PING request from $Nick.");
287+
}
288+
}
289+
}
290+
291+
return $Message;
292+
} else {
293+
$this->log("Notice: Malformed message detected.");
294+
$this->log($RawMessage);
295+
}
296+
}
297+
298+
299+
300+
// Socket functions
301+
302+
protected function send($Message) {
303+
if (sizeof($Message) > 510) {
304+
$this->log("Notice: An attempt was made to send an excessively long string. The string will be trimmed to 510 characters.");
305+
}
306+
307+
if ($this->LogSockets) {
308+
$this->log("<< " . substr($Message, 0, 510));
309+
}
310+
311+
fwrite($this->ServerSocket, substr($Message, 0, 510) . "\r\n", 512);
312+
}
313+
314+
315+
protected function recv($Message) {
316+
return 0;
317+
}
318+
319+
320+
// Protocol abstraction functions
321+
322+
protected function nick($Nick) {
323+
$this->send("NICK :" . $Nick);
324+
}
325+
326+
protected function join($Channel) {
327+
$this->send("JOIN :" . $Channel);
328+
}
329+
330+
protected function part($Channel, $Message) {
331+
$this->send("PART " . $Channel . " :" . $Message);
332+
}
333+
334+
protected function quit($Message) {
335+
$this->send("QUIT :" . $Message);
336+
}
337+
338+
protected function kick($Channel, $Person, $Message) {
339+
$this->send("KICK " . $Channel . " " . $Person . " :" . $Message);
340+
}
341+
}
342+
?>

0 commit comments

Comments
 (0)