Completed
Push — master ( ac9c3e...191d3f )
by Jeroen
01:42
created

IrcClient::setNick()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 5
nc 4
nop 1
dl 0
loc 11
rs 10
c 0
b 0
f 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 Jerodev\PhpIrcClient\Messages\NameReplyMessage;
9
use React\EventLoop\LoopInterface;
10
use React\Socket\ConnectionInterface;
11
12
class IrcClient
13
{
14
    /** @var IrcChannel[] */
15
    private $channels;
16
17
    /** @var ConnectionInterface */
18
    private $connection;
19
20
    /** @var IrcMessageParser */
21
    private $ircMessageParser;
22
23
    /**
24
     * Used to track if the username has been sent to the server.
25
     *
26
     * @var bool
27
     */
28
    private $isAuthenticated;
29
30
    /** @var LoopInterface */
31
    private $loop;
32
33
    /** @var EventHandlerCollection */
34
    private $messageEventHandlers;
35
36
    /** @var string */
37
    private $server;
38
39
    /** @var IrcUser|null */
40
    private $user;
41
42
    /**
43
     *  Create a new IrcClient instance.
44
     *
45
     *  @param string $server The server address to connect to including the port: `address:port`.
46
     *  @param null|string $username The username to use on the server. Can be set in more detail using `setUser()`.
47
     *  @param null|string|string[] $channels The channels to join on connect.
48
     */
49
    public function __construct(string $server, $username = null, $channels = null)
50
    {
51
        $this->server = $server;
52
        $this->user = $username === null ? null : new IrcUser($username);
53
        $this->channels = [];
54
        $this->ircMessageParser = new IrcMessageParser();
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 setNick($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
    }
132
133
    /**
134
     *  Test wether a connection is currently open for this client.
135
     *
136
     *  @return bool
137
     */
138
    public function isConnected(): bool
139
    {
140
        return $this->connection !== null;
141
    }
142
143
    /**
144
     *  Register an event handler for irc messages.
145
     *
146
     *  @param callable|string $event The name of the event to listen for. Pass a callable to this parameter to catch all events.
147
     *  @param callable|null $function The callable that will be invoked on event.
148
     */
149
    public function onMessage($event, ?callable $function = null)
150
    {
151
        $this->messageEventHandlers->addHandler($event, $function);
152
    }
153
154
    /**
155
     *  Send a raw command string to the irc server.
156
     *
157
     *  @param string $command The full command string to send.
158
     */
159
    public function sendCommand(string $command): void
160
    {
161
        // Make sure the command ends in a newline character
162
        if (substr($command, -1) !== "\n") {
163
            $command .= "\n";
164
        }
165
166
        $this->connection->write($command);
167
    }
168
169
    /**
170
     *  Send a message to a channel or user.
171
     *  To send to a channel, make sure the `$target` starts with a `#`.
172
     *
173
     *  @param string $target The channel or user to message.
174
     *  @param string $message The message to send.
175
     */
176
    public function sendMessage(string $target, string $message): void
177
    {
178
        $this->sendCommand("PRIVMSG $target :$message");
179
    }
180
181
    /**
182
     *  Take actions required for received irc messages and invoke the correct event handlers.
183
     *
184
     *  @param IrcMessage $message The message object for the received line.
185
     */
186
    private function handleIrcMessage(IrcMessage $message): void
187
    {
188
        var_dump($message);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($message) looks like debug code. Are you sure you do not want to remove it?
Loading history...
189
190
        switch ($message->command) {
191
            case 'PING':
192
                $this->sendCommand("PONG :$message->payload");
193
                break;
194
195
            case IrcCommand::RPL_WELCOME:
196
                $this->sendCommand('JOIN #pokedextest');
197
                $this->sendMessage('#pokedextest', 'A wild IrcBot appeared!');
198
                break;
199
200
            case IrcCommand::RPL_NAMREPLY:
201
                if ($message instanceof NameReplyMessage) {
202
                    $this->getChannel($message->channel)->setUsers($message->names);
203
                    var_dump($this->channels);
204
                }
205
                break;
206
        }
207
208
        if (!$this->isAuthenticated && $this->user) {
209
            $this->sendCommand("USER {$this->user->nickname} * * :{$this->user->nickname}");
210
            $this->sendCommand("NICK {$this->user->nickname}");
211
            $this->isAuthenticated = true;
212
        }
213
    }
214
215
    /**
216
     *  Grab channel information by its name.
217
     *  This function makes sure the channel exists on this client first.
218
     *
219
     *  @param string $name The name of this channel.
220
     *
221
     *  @return IrcChannel
222
     */
223
    private function getChannel(string $name): IrcChannel
224
    {
225
        if (($this->channels[$name] ?? null) === null) {
226
            $this->channels[$name] = new IrcChannel($name);
227
        }
228
229
        return $this->channels[$name];
230
    }
231
}
232