Completed
Push — master ( 7c39f2...831177 )
by Jeroen
04:12
created

IrcClient::getChannels()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Jerodev\PhpIrcClient;
4
5
use Exception;
6
use Jerodev\PhpIrcClient\Helpers\EventHandlerCollection;
7
use Jerodev\PhpIrcClient\Messages\IrcMessage;
8
use React\EventLoop\LoopInterface;
9
use React\Socket\ConnectionInterface;
10
11
class IrcClient
12
{
13
    /** @var IrcChannel[] */
14
    private $channels;
15
16
    /** @var ConnectionInterface|null */
17
    private $connection;
18
19
    /** @var IrcMessageParser */
20
    private $ircMessageParser;
21
22
    /**
23
     * Used to track if the username has been sent to the server.
24
     *
25
     * @var bool
26
     */
27
    private $isAuthenticated;
28
29
    /** @var LoopInterface */
30
    private $loop;
31
32
    /** @var EventHandlerCollection */
33
    private $messageEventHandlers;
34
35
    /** @var string */
36
    private $server;
37
38
    /** @var IrcUser|null */
39
    private $user;
40
41
    /**
42
     *  Create a new IrcClient instance.
43
     *
44
     *  @param string $server The server address to connect to including the port: `address:port`.
45
     *  @param null|string $username The username to use on the server. Can be set in more detail using `setUser()`.
46
     *  @param null|string|string[] $channels The channels to join on connect.
47
     */
48
    public function __construct(string $server, $username = null, $channels = null)
49
    {
50
        $this->server = $server;
51
        $this->user = $username === null ? null : new IrcUser($username);
52
        $this->channels = [];
53
        $this->ircMessageParser = new IrcMessageParser();
54
        $this->messageEventHandlers = new EventHandlerCollection();
55
56
        if (!empty($channels)) {
57
            if (is_string($channels)) {
58
                $channels = [$channels];
59
            }
60
61
            foreach ($channels as $channel) {
62
                $this->channels[$channel] = new IrcChannel($channel);
63
            }
64
        }
65
    }
66
67
    /**
68
     *  Set the user credentials for the connections.
69
     *  When a connection is already open, this function can be used to change the nickname of the client.
70
     *
71
     *  @param IrcUser|string $user The user information.
72
     */
73
    public function setUser($user): void
74
    {
75
        if (is_string($user)) {
76
            $user = new IrcUser($user);
77
        }
78
79
        if ($this->isConnected() && $this->user->nickname !== $user->nickname) {
80
            $this->sendCommand("NICK :$user->nickname");
81
        }
82
83
        $this->user = $user;
84
    }
85
86
    /**
87
     *  Connect to the irc server and start listening for messages.
88
     *
89
     *  @throws Exception if no user information is provided before connecting.
90
     */
91
    public function connect(): void
92
    {
93
        if (!$this->user) {
94
            throw new Exception('A nickname must be set before connecting to an irc server.');
95
        }
96
97
        if ($this->isConnected()) {
98
            return;
99
        }
100
101
        $this->isAuthenticated = false;
102
103
        $this->loop = \React\EventLoop\Factory::create();
104
        $tcpConnector = new \React\Socket\TcpConnector($this->loop);
105
        $dnsResolverFactory = new \React\Dns\Resolver\Factory();
106
        $dns = $dnsResolverFactory->createCached('1.1.1.1', $this->loop);
107
        $dnsConnector = new \React\Socket\DnsConnector($tcpConnector, $dns);
108
109
        $dnsConnector->connect($this->server)->then(function (ConnectionInterface $connection) {
110
            $this->connection = $connection;
111
112
            $this->connection->on('data', function ($data) {
113
                foreach ($this->ircMessageParser->parse($data) as $msg) {
114
                    $this->handleIrcMessage($msg);
115
                }
116
            });
117
        });
118
119
        $this->loop->run();
120
    }
121
122
    /**
123
     *  Close the current connection, if any.
124
     */
125
    public function disconnect(): void
126
    {
127
        if ($this->isConnected()) {
128
            $this->connection->close();
129
            $this->loop->stop();
130
            
131
            $this->connection = null;
132
        }
133
    }
134
135
    /**
136
     *  Test wether a connection is currently open for this client.
137
     *
138
     *  @return bool
139
     */
140
    public function isConnected(): bool
141
    {
142
        return $this->connection !== null;
143
    }
144
145
    /**
146
     *  Register an event handler for irc messages.
147
     *
148
     *  @param callable|string $event The name of the event to listen for. Pass a callable to this parameter to catch all events.
149
     *  @param callable|null $function The callable that will be invoked on event.
150
     */
151
    public function addMessageHandler($event, ?callable $function = null)
152
    {
153
        $this->messageEventHandlers->addHandler($event, $function);
154
    }
155
156
    /**
157
     *  Send a raw command string to the irc server.
158
     *
159
     *  @param string $command The full command string to send.
160
     */
161
    public function sendCommand(string $command): void
162
    {
163
        // Make sure the command ends in a newline character
164
        if (substr($command, -1) !== "\n") {
165
            $command .= "\n";
166
        }
167
168
        $this->connection->write($command);
0 ignored issues
show
Bug introduced by
The method write() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

168
        $this->connection->/** @scrutinizer ignore-call */ 
169
                           write($command);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
169
    }
170
171
    /**
172
     *  Send a message to a channel or user.
173
     *  To send to a channel, make sure the `$target` starts with a `#`.
174
     *
175
     *  @param string $target The channel or user to message.
176
     *  @param string $message The message to send.
177
     */
178
    public function sendMessage(string $target, string $message): void
179
    {
180
        $this->sendCommand("PRIVMSG $target :$message");
181
    }
182
    
183
    /**
184
     *  Grab channel information by its name.
185
     *  This function makes sure the channel exists on this client first.
186
     *
187
     *  @param string $name The name of this channel.
188
     *
189
     *  @return IrcChannel
190
     */
191
    public function getChannel(string $name): IrcChannel
192
    {
193
        if (($this->channels[$name] ?? null) === null) {
194
            $this->channels[$name] = new IrcChannel($name);
195
        }
196
197
        return $this->channels[$name];
198
    }
199
    
200
    /**
201
     *  Return a list of all channels
202
     *
203
     *  @return IrcChannel[]
204
     */
205
    public function getChannels(): array
206
    {
207
        return $this->channels;
208
    }
209
210
    /**
211
     *  Take actions required for received irc messages and invoke the correct event handlers.
212
     *
213
     *  @param IrcMessage $message The message object for the received line.
214
     */
215
    private function handleIrcMessage(IrcMessage $message): void
216
    {
217
        $message->handle($this);
218
219
        if (!$this->isAuthenticated && $this->user) {
220
            $this->sendCommand("USER {$this->user->nickname} * * :{$this->user->nickname}");
221
            $this->sendCommand("NICK {$this->user->nickname}");
222
            $this->isAuthenticated = true;
223
        }
224
        
225
        $this->messageEventHandlers->invoke($message->command, [$message]);
226
    }
227
}
228