1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Jerodev\PhpIrcClient; |
6
|
|
|
|
7
|
|
|
use Exception; |
8
|
|
|
use Jerodev\PhpIrcClient\Helpers\Event; |
9
|
|
|
use Jerodev\PhpIrcClient\Helpers\EventHandlerCollection; |
10
|
|
|
use Jerodev\PhpIrcClient\Messages\IrcMessage; |
11
|
|
|
use Jerodev\PhpIrcClient\Options\ConnectionOptions; |
12
|
|
|
use React\EventLoop\LoopInterface; |
13
|
|
|
use React\Socket\ConnectionInterface; |
14
|
|
|
|
15
|
|
|
class IrcConnection |
16
|
|
|
{ |
17
|
|
|
private bool $connected = false; |
18
|
|
|
private ?ConnectionInterface $connection = null; |
19
|
|
|
private EventHandlerCollection $eventHandlerCollection; |
20
|
|
|
private bool $floodProtected; |
21
|
|
|
private LoopInterface $loop; |
22
|
|
|
private IrcMessageParser $messageParser; |
23
|
|
|
|
24
|
|
|
/** @var array<int, string> */ |
25
|
|
|
private array $messageQueue = []; |
26
|
|
|
|
27
|
|
|
public function __construct( |
28
|
|
|
private string $server, |
29
|
|
|
?ConnectionOptions $options = null |
30
|
|
|
) { |
31
|
|
|
$options = $options ?? new ConnectionOptions(); |
32
|
|
|
|
33
|
|
|
$this->eventHandlerCollection = new EventHandlerCollection(); |
34
|
|
|
$this->floodProtected = $options->floodProtectionDelay > 0; |
35
|
|
|
$this->loop = \React\EventLoop\Factory::create(); |
|
|
|
|
36
|
|
|
$this->messageParser = new IrcMessageParser(); |
37
|
|
|
|
38
|
|
|
if ($this->floodProtected) { |
39
|
|
|
$this->loop->addPeriodicTimer($options->floodProtectionDelay / 1000, function () { |
40
|
|
|
if ($msg = array_shift($this->messageQueue)) { |
41
|
|
|
$this->connection->write($msg); |
|
|
|
|
42
|
|
|
} |
43
|
|
|
}); |
44
|
|
|
} |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Open a connection to the IRC server. |
49
|
|
|
*/ |
50
|
|
|
public function open(): void |
51
|
|
|
{ |
52
|
|
|
if ($this->isConnected()) { |
53
|
|
|
return; |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
$tcpConnector = new \React\Socket\TcpConnector($this->loop); |
57
|
|
|
$dnsResolverFactory = new \React\Dns\Resolver\Factory(); |
58
|
|
|
$dns = $dnsResolverFactory->createCached('1.1.1.1', $this->loop); |
59
|
|
|
$dnsConnector = new \React\Socket\DnsConnector($tcpConnector, $dns); |
60
|
|
|
|
61
|
|
|
$dnsConnector->connect($this->server)->then(function (ConnectionInterface $connection) { |
62
|
|
|
$this->connection = $connection; |
63
|
|
|
$this->connected = true; |
64
|
|
|
|
65
|
|
|
$this->connection->on('data', function ($data) { |
66
|
|
|
foreach ($this->messageParser->parse($data) as $msg) { |
67
|
|
|
$this->handleMessage($msg); |
68
|
|
|
} |
69
|
|
|
}); |
70
|
|
|
|
71
|
|
|
$this->connection->on('close', function () { |
72
|
|
|
$this->connected = false; |
73
|
|
|
}); |
74
|
|
|
$this->connection->on('end', function () { |
75
|
|
|
$this->connected = false; |
76
|
|
|
}); |
77
|
|
|
}); |
78
|
|
|
|
79
|
|
|
$this->loop->run(); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Close the current IRC server connection. |
84
|
|
|
*/ |
85
|
|
|
public function close(): void |
86
|
|
|
{ |
87
|
|
|
if ($this->isConnected()) { |
88
|
|
|
$this->connection->close(); |
89
|
|
|
$this->loop->stop(); |
90
|
|
|
} |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* Test if there is an open connection to the IRC server. |
95
|
|
|
*/ |
96
|
|
|
public function isConnected(): bool |
97
|
|
|
{ |
98
|
|
|
return $this->connection && $this->connected; |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Set a callback for received IRC data. |
103
|
|
|
* An IrcMessage object will be passed to the callback. |
104
|
|
|
* @param callable $function The function to be called. |
105
|
|
|
*/ |
106
|
|
|
public function onData(callable $function): void |
107
|
|
|
{ |
108
|
|
|
$this->eventHandlerCollection->addHandler('data', $function); |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* Send a command to the IRC server. |
113
|
|
|
* @param string $command The raw IRC command. |
114
|
|
|
* @throws Exception if no open connection is available. |
115
|
|
|
*/ |
116
|
|
|
public function write(string $command): void |
117
|
|
|
{ |
118
|
|
|
if (!$this->isConnected()) { |
119
|
|
|
throw new Exception('No open connection was found to write commands to.'); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
// Make sure the command ends in a newline character |
123
|
|
|
if (substr($command, -1) !== "\n") { |
124
|
|
|
$command .= "\n"; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
if ($this->floodProtected) { |
128
|
|
|
$this->messageQueue[] = $command; |
129
|
|
|
} else { |
130
|
|
|
$this->connection->write($command); |
131
|
|
|
} |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* Handle a single parsed IrcMessage. |
136
|
|
|
*/ |
137
|
|
|
private function handleMessage(IrcMessage $message): void |
138
|
|
|
{ |
139
|
|
|
$this->eventHandlerCollection->invoke(new Event('data', [$message])); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
public function getServer(): string |
143
|
|
|
{ |
144
|
|
|
return $this->server; |
145
|
|
|
} |
146
|
|
|
} |
147
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.