From 3fb13fb923a5ff9327442fac6fddf516e50f3c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 3 Jun 2018 17:09:27 +0200 Subject: [PATCH 1/3] Add BufferInfoModel and MessageModel to represent complex data types --- README.md | 7 ++- examples/02-chatbot.php | 8 ++- examples/03-pingbot.php | 14 +++-- examples/04-connect.php | 12 ++-- src/Client.php | 3 +- src/Io/Protocol.php | 36 +++++------ src/Models/BufferInfoModel.php | 68 ++++++++++++++++++++ src/Models/MessageModel.php | 92 ++++++++++++++++++++++++++++ tests/Models/BufferInfoModelTest.php | 28 +++++++++ tests/Models/MessageModelTest.php | 50 +++++++++++++++ 10 files changed, 281 insertions(+), 37 deletions(-) create mode 100644 src/Models/BufferInfoModel.php create mode 100644 src/Models/MessageModel.php create mode 100644 tests/Models/BufferInfoModelTest.php create mode 100644 tests/Models/MessageModelTest.php diff --git a/README.md b/README.md index 5854988..a55618f 100644 --- a/README.md +++ b/README.md @@ -213,9 +213,10 @@ anything about it. There are only few noticable exceptions to this rule: -* Incoming chat messages use a plain Unix timestamp integers, while all other - `data` events usually use `DateTime` objects. - This library always converts this to `DateTime` for consistency reasons. +* Incoming buffers/channels and chat messages use complex data models, so they + are represented by `BufferInfoModel` and `MessageModel` respectively. All other + data types use plain structured data, so you can access it's array-based + structure very similar to a JSON-like data structure. * The legacy protocol uses plain times for heartbeat messages while the newer datastream protocol uses `DateTime` objects. This library always converts this to `DateTime` for consistency reasons. diff --git a/examples/02-chatbot.php b/examples/02-chatbot.php index 39617a7..4a562a1 100644 --- a/examples/02-chatbot.php +++ b/examples/02-chatbot.php @@ -3,6 +3,7 @@ use Clue\React\Quassel\Factory; use Clue\React\Quassel\Client; use Clue\React\Quassel\Io\Protocol; +use Clue\React\Quassel\Models\MessageModel; require __DIR__ . '/../vendor/autoload.php'; @@ -43,11 +44,12 @@ // chat message received if (isset($message[0]) && $message[0] === Protocol::REQUEST_RPCCALL && $message[1] === '2displayMsg(Message)') { $data = $message[2]; + assert($data instanceof MessageModel); - if (strpos($data['content'], $keyword) !== false) { - $client->writeBufferInput($data['bufferInfo'], 'Hello from clue/quassel-react :-)'); + if (strpos($data->getContents(), $keyword) !== false) { + $client->writeBufferInput($data->getBufferInfo(), 'Hello from clue/quassel-react :-)'); - echo date('Y-m-d H:i:s') . ' Replied to ' . $data['bufferInfo']['name'] . '/' . explode('!', $data['sender'], 2)[0] . ': "' . $data['content'] . '"' . PHP_EOL; + echo date('Y-m-d H:i:s') . ' Replied to ' . $data->getBufferInfo()->getName() . '/' . explode('!', $data->getSender())[0] . ': "' . $data->getContents() . '"' . PHP_EOL; } } }); diff --git a/examples/03-pingbot.php b/examples/03-pingbot.php index 97edbdb..eeb538a 100644 --- a/examples/03-pingbot.php +++ b/examples/03-pingbot.php @@ -3,6 +3,7 @@ use Clue\React\Quassel\Factory; use Clue\React\Quassel\Client; use Clue\React\Quassel\Io\Protocol; +use Clue\React\Quassel\Models\MessageModel; require __DIR__ . '/../vendor/autoload.php'; @@ -65,26 +66,27 @@ // chat message received if (isset($message[0]) && $message[0] === Protocol::REQUEST_RPCCALL && $message[1] === '2displayMsg(Message)') { $data = $message[2]; + assert($data instanceof MessageModel); $reply = null; // we may be connected to multiple networks with different nicks // find correct nick for current network - $nick = isset($nicks[$data['bufferInfo']['network']]) ? $nicks[$data['bufferInfo']['network']] : null; + $nick = isset($nicks[$data->getBufferInfo()->getNetworkId()]) ? $nicks[$data->getBufferInfo()->getNetworkId()] : null; // received "nick: ping" in any buffer/channel - if ($nick !== null && strtolower($data['content']) === ($nick . ': ping')) { - $reply = explode('!', $data['sender'], 2)[0] . ': pong :-)'; + if ($nick !== null && strtolower($data->getContents()) === ($nick . ': ping')) { + $reply = explode('!', $data->getSender())[0] . ': pong :-)'; } // received "ping" in direct query buffer (user to user) - if (strtolower($data['content']) === 'ping' && $data['bufferInfo']['type'] === 0x04) { + if (strtolower($data->getContents()) === 'ping' && $data->getBufferInfo()->getType() === 0x04) { $reply = 'pong :-)'; } if ($reply !== null) { - $client->writeBufferInput($data['bufferInfo'], $reply); + $client->writeBufferInput($data->getBufferInfo(), $reply); - echo date('Y-m-d H:i:s') . ' Replied to ' . $data['bufferInfo']['name'] . '/' . explode('!', $data['sender'], 2)[0] . ': "' . $data['content'] . '"' . PHP_EOL; + echo date('Y-m-d H:i:s') . ' Replied to ' . $data->getBufferInfo()->getName() . '/' . explode('!', $data->getSender())[0] . ': "' . $data->getContents() . '"' . PHP_EOL; } } }); diff --git a/examples/04-connect.php b/examples/04-connect.php index 5b0ed3d..aa18cca 100644 --- a/examples/04-connect.php +++ b/examples/04-connect.php @@ -3,6 +3,8 @@ use Clue\React\Quassel\Factory; use Clue\React\Quassel\Client; use Clue\React\Quassel\Io\Protocol; +use Clue\React\Quassel\Models\MessageModel; +use Clue\React\Quassel\Models\BufferInfoModel; require __DIR__ . '/../vendor/autoload.php'; @@ -36,9 +38,10 @@ } foreach ($message['SessionState']['BufferInfos'] as $buffer) { - if ($buffer['type'] === 2) { // type == 4 for user - var_dump('requesting IrcChannel for ' . $buffer['name']); - $client->writeInitRequest('IrcChannel', $buffer['network'] . '/' . $buffer['id']); + assert($buffer instanceof BufferInfoModel); + if ($buffer->getType() === 2) { // type == 4 for user + var_dump('requesting IrcChannel for ' . $buffer->getName()); + $client->writeInitRequest('IrcChannel', $buffer->getNetworkId() . '/' . $buffer->getId()); } } @@ -59,7 +62,8 @@ if ($type === Protocol::REQUEST_RPCCALL && $message[1] === '2displayMsg(Message)') { $data = $message[2]; - echo $data['timestamp']->format(\DateTime::ISO8601) . ' in ' . $data['bufferInfo']['name'] . ' by ' . explode('!', $data['sender'], 2)[0] . ': ' . $data['content'] . PHP_EOL; + assert($data instanceof MessageModel); + echo date(DATE_ISO8601, $data->getTimestamp()) . ' in ' . $data->getBufferInfo()->getName() . ' by ' . explode('!', $data->getSender())[0] . ': ' . $data->getContents() . PHP_EOL; return; } diff --git a/src/Client.php b/src/Client.php index 29cfd27..587910e 100644 --- a/src/Client.php +++ b/src/Client.php @@ -10,6 +10,7 @@ use React\Stream\DuplexStreamInterface; use React\Stream\Util; use React\Stream\WritableStreamInterface; +use Clue\React\Quassel\Models\BufferInfoModel; class Client extends EventEmitter implements DuplexStreamInterface { @@ -191,7 +192,7 @@ public function writeHeartBeatReply(\DateTime $dt) )); } - public function writeBufferInput($bufferInfo, $input) + public function writeBufferInput(BufferInfoModel $bufferInfo, $input) { return $this->write(array( Protocol::REQUEST_RPCCALL, diff --git a/src/Io/Protocol.php b/src/Io/Protocol.php index 207bb61..4fc5124 100644 --- a/src/Io/Protocol.php +++ b/src/Io/Protocol.php @@ -4,6 +4,8 @@ use Clue\QDataStream\Writer; use Clue\QDataStream\Reader; +use Clue\React\Quassel\Models\BufferInfoModel; +use Clue\React\Quassel\Models\MessageModel; /** @internal */ abstract class Protocol @@ -55,12 +57,12 @@ public function __construct() return $reader->readUInt(); }, 'BufferInfo' => function (Reader $reader) { - return array( - 'id' => $reader->readUInt(), - 'network' => $reader->readUInt(), - 'type' => $reader->readUShort(), - 'group' => $reader->readUInt(), - 'name' => $reader->readQByteArray(), + return new BufferInfoModel( + $reader->readUInt(), + $reader->readUInt(), + $reader->readUShort(), + $reader->readUInt(), + $reader->readQByteArray() ); }, // all required by "Network" InitRequest @@ -72,20 +74,14 @@ public function __construct() return $reader->readUInt(); }, 'Message' => function (Reader $reader) { - // create DateTime object with local time zone from given unix timestamp - $datetime = function ($timestamp) { - $d = new \DateTime('@' . $timestamp); - $d->setTimeZone(new \DateTimeZone(date_default_timezone_get())); - return $d; - }; - return array( - 'id' => $reader->readUInt(), - 'timestamp' => $datetime($reader->readUInt()), - 'type' => $reader->readUInt(), - 'flags' => $reader->readUChar(), - 'bufferInfo' => $reader->readQUserTypeByName('BufferInfo'), - 'sender' => $reader->readQByteArray(), - 'content' => $reader->readQByteArray() + return new MessageModel( + $reader->readUInt(), + $reader->readUInt(), + $reader->readUInt(), + $reader->readUChar(), + $reader->readQUserTypeByName('BufferInfo'), + $reader->readQByteArray(), + $reader->readQByteArray() ); }, 'MsgId' => function (Reader $reader) { diff --git a/src/Models/BufferInfoModel.php b/src/Models/BufferInfoModel.php new file mode 100644 index 0000000..35bc1c2 --- /dev/null +++ b/src/Models/BufferInfoModel.php @@ -0,0 +1,68 @@ +id = $id; + $this->networkId = $networkId; + $this->type = $type; + $this->groupId = $groupId; + $this->name = $name; + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @return int + */ + public function getNetworkId() + { + return $this->networkId; + } + + /** + * @return int + */ + public function getType() + { + return $this->type; + } + + /** + * @return int + */ + public function getGroupId() + { + return $this->groupId; + } + + /** + * @return string buffer/channel name `#channel` or `user` or empty string + */ + public function getName() + { + return $this->name; + } +} diff --git a/src/Models/MessageModel.php b/src/Models/MessageModel.php new file mode 100644 index 0000000..31bb143 --- /dev/null +++ b/src/Models/MessageModel.php @@ -0,0 +1,92 @@ +id = $id; + $this->timestamp = $timestamp; + $this->type = $type; + $this->flags = $flags; + $this->bufferInfo = $bufferInfo; + $this->sender = $sender; + $this->content = $content; + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @return int UNIX timestamp + */ + public function getTimestamp() + { + return $this->timestamp; + } + + /** + * @return int + */ + public function getType() + { + return $this->type; + } + + /** + * @return int + */ + public function getFlags() + { + return $this->flags; + } + + /** + * @return BufferInfoModel + */ + public function getBufferInfo() + { + return $this->bufferInfo; + } + + /** + * @return string `nick!user@host` or just host or empty string depending on type/flags + * @see self::getSenderNick() + */ + public function getSender() + { + return $this->sender; + } + + /** + * @return string message contents contains the chat message or info which may be empty depending on type + */ + public function getContents() + { + return $this->content; + } +} diff --git a/tests/Models/BufferInfoModelTest.php b/tests/Models/BufferInfoModelTest.php new file mode 100644 index 0000000..85ee20b --- /dev/null +++ b/tests/Models/BufferInfoModelTest.php @@ -0,0 +1,28 @@ +assertSame(1, $model->getId()); + $this->assertSame(3, $model->getNetworkId()); + $this->assertSame(0x02, $model->getType()); + $this->assertSame(0, $model->getGroupId()); + $this->assertSame('#reactphp', $model->getName()); + } + + public function testBufferInfoForUserQuery() + { + $model = new BufferInfoModel(2, 1, 0x04, 0, 'another_clue'); + + $this->assertSame(2, $model->getId()); + $this->assertSame(1, $model->getNetworkId()); + $this->assertSame(0x04, $model->getType()); + $this->assertSame(0, $model->getGroupId()); + $this->assertSame('another_clue', $model->getName()); + } +} diff --git a/tests/Models/MessageModelTest.php b/tests/Models/MessageModelTest.php new file mode 100644 index 0000000..1a6cd79 --- /dev/null +++ b/tests/Models/MessageModelTest.php @@ -0,0 +1,50 @@ +getMockBuilder('Clue\React\Quassel\Models\BufferInfoModel')->disableOriginalConstructor()->getMock(); + $message = new MessageModel( + 1000, + 1528039705, + 0x01, + 0x00, + $buffer, + 'another_clue!user@host', + 'Hello world!' + ); + + $this->assertSame(1000, $message->getId()); + $this->assertSame(1528039705, $message->getTimestamp()); + $this->assertSame(0x01, $message->getType()); + $this->assertSame(0x00, $message->getFlags()); + $this->assertSame($buffer, $message->getBufferInfo()); + $this->assertSame('another_clue!user@host', $message->getSender()); + $this->assertSame('Hello world!', $message->getContents()); + } + + public function testJoinMessage() + { + $buffer = $this->getMockBuilder('Clue\React\Quassel\Models\BufferInfoModel')->disableOriginalConstructor()->getMock(); + $message = new MessageModel( + 999, + 1528039704, + 0x20, + 0x00, + $buffer, + 'another_clue!user@host', + '#reactphp' + ); + + $this->assertSame(999, $message->getId()); + $this->assertSame(1528039704, $message->getTimestamp()); + $this->assertSame(0x20, $message->getType()); + $this->assertSame(0x00, $message->getFlags()); + $this->assertSame($buffer, $message->getBufferInfo()); + $this->assertSame('another_clue!user@host', $message->getSender()); + $this->assertSame('#reactphp', $message->getContents()); + } +} From 2591e32e76e5f4b089e447579f17385989fee011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 3 Jun 2018 17:47:59 +0200 Subject: [PATCH 2/3] Add TYPE_* and FLAG_* constants for BufferInfoModel and MessageModel --- examples/03-pingbot.php | 3 ++- examples/04-connect.php | 2 +- src/Models/BufferInfoModel.php | 11 +++++++-- src/Models/MessageModel.php | 37 +++++++++++++++++++++++++--- tests/Models/BufferInfoModelTest.php | 8 +++--- tests/Models/MessageModelTest.php | 16 ++++++------ 6 files changed, 57 insertions(+), 20 deletions(-) diff --git a/examples/03-pingbot.php b/examples/03-pingbot.php index eeb538a..36de94b 100644 --- a/examples/03-pingbot.php +++ b/examples/03-pingbot.php @@ -3,6 +3,7 @@ use Clue\React\Quassel\Factory; use Clue\React\Quassel\Client; use Clue\React\Quassel\Io\Protocol; +use Clue\React\Quassel\Models\BufferInfoModel; use Clue\React\Quassel\Models\MessageModel; require __DIR__ . '/../vendor/autoload.php'; @@ -79,7 +80,7 @@ } // received "ping" in direct query buffer (user to user) - if (strtolower($data->getContents()) === 'ping' && $data->getBufferInfo()->getType() === 0x04) { + if (strtolower($data->getContents()) === 'ping' && $data->getBufferInfo()->getType() === BufferInfoModel::TYPE_QUERY) { $reply = 'pong :-)'; } diff --git a/examples/04-connect.php b/examples/04-connect.php index aa18cca..3dd4a07 100644 --- a/examples/04-connect.php +++ b/examples/04-connect.php @@ -39,7 +39,7 @@ foreach ($message['SessionState']['BufferInfos'] as $buffer) { assert($buffer instanceof BufferInfoModel); - if ($buffer->getType() === 2) { // type == 4 for user + if ($buffer->getType() === BufferInfoModel::TYPE_CHANNEL) { var_dump('requesting IrcChannel for ' . $buffer->getName()); $client->writeInitRequest('IrcChannel', $buffer->getNetworkId() . '/' . $buffer->getId()); } diff --git a/src/Models/BufferInfoModel.php b/src/Models/BufferInfoModel.php index 35bc1c2..e303346 100644 --- a/src/Models/BufferInfoModel.php +++ b/src/Models/BufferInfoModel.php @@ -4,6 +4,13 @@ class BufferInfoModel { + // @link https://github.com/quassel/quassel/blob/e17fca767d60c06ca02bc5898ced04f06d3670bd/src/common/bufferinfo.h#L32 + const TYPE_INVALID = 0x00; + const TYPE_STATUS = 0x01; + const TYPE_CHANNEL = 0x02; + const TYPE_QUERY = 0x04; + const TYPE_GROUP = 0x08; + private $id; private $networkId; private $type; @@ -13,7 +20,7 @@ class BufferInfoModel /** * @param int $id * @param int $networkId - * @param int $type + * @param int $type single type constant, see self::TYPE_* * @param int $groupId * @param string $name buffer/channel name `#channel`, `user` or empty string */ @@ -43,7 +50,7 @@ public function getNetworkId() } /** - * @return int + * @return int single type constant, see self::TYPE_* */ public function getType() { diff --git a/src/Models/MessageModel.php b/src/Models/MessageModel.php index 31bb143..2e19f54 100644 --- a/src/Models/MessageModel.php +++ b/src/Models/MessageModel.php @@ -4,6 +4,35 @@ class MessageModel { + // @link https://github.com/quassel/quassel/blob/e17fca767d60c06ca02bc5898ced04f06d3670bd/src/common/message.h#L35 + const TYPE_PLAIN = 0x00001; + const TYPE_NOTICE = 0x00002; + const TYPE_ACTION = 0x00004; + const TYPE_NICK = 0x00008; + const TYPE_MODE = 0x00010; + const TYPE_JOIN = 0x00020; + const TYPE_PART = 0x00040; + const TYPE_QUIT = 0x00080; + const TYPE_KICK = 0x00100; + const TYPE_KILL = 0x00200; + const TYPE_SERVER = 0x00400; + const TYPE_INFO = 0x00800; + const TYPE_ERROR = 0x01000; + const TYPE_DAY_CHANGE = 0x02000; + const TYPE_TOPIC = 0x04000; + const TYPE_NETSPLIT_JOIN = 0x08000; + const TYPE_NETSPLIT_QUIT = 0x10000; + const TYPE_INVITE = 0x20000; + + // @link https://github.com/quassel/quassel/blob/e17fca767d60c06ca02bc5898ced04f06d3670bd/src/common/message.h#L59 + const FLAG_NONE = 0x00; + const FLAG_SELF = 0x01; + const FLAG_HIGHLIGHT = 0x02; + const FLAG_REDIRECTED = 0x04; + const FLAG_SERVER_MESSAGE = 0x08; + const FLAG_STATUS_MESSAGE = 0x10; + const FLAG_BACKLOG = 0x80; + private $id; private $timestamp; private $type; @@ -16,8 +45,8 @@ class MessageModel * * @param int $id * @param int $timestamp UNIX timestamp - * @param int $type - * @param int $flags + * @param int $type single type constant, see self::TYPE_* constants + * @param int $flags bitmask of flag constants, see self::FLAG_* constants * @param BufferInfoModel $bufferInfo * @param string $sender sender in the form `nick!user@host` or only `host` or empty string * @param string $content @@ -50,7 +79,7 @@ public function getTimestamp() } /** - * @return int + * @return int single type constant, see self::TYPE_* constants */ public function getType() { @@ -58,7 +87,7 @@ public function getType() } /** - * @return int + * @return int bitmask of flag constants, see self::FLAG_* constants */ public function getFlags() { diff --git a/tests/Models/BufferInfoModelTest.php b/tests/Models/BufferInfoModelTest.php index 85ee20b..336c84e 100644 --- a/tests/Models/BufferInfoModelTest.php +++ b/tests/Models/BufferInfoModelTest.php @@ -6,22 +6,22 @@ class BufferInfoModelTest extends TestCase { public function testBufferInfoForNormalChannel() { - $model = new BufferInfoModel(1, 3, 0x02, 0, '#reactphp'); + $model = new BufferInfoModel(1, 3, BufferInfoModel::TYPE_CHANNEL, 0, '#reactphp'); $this->assertSame(1, $model->getId()); $this->assertSame(3, $model->getNetworkId()); - $this->assertSame(0x02, $model->getType()); + $this->assertSame(BufferInfoModel::TYPE_CHANNEL, $model->getType()); $this->assertSame(0, $model->getGroupId()); $this->assertSame('#reactphp', $model->getName()); } public function testBufferInfoForUserQuery() { - $model = new BufferInfoModel(2, 1, 0x04, 0, 'another_clue'); + $model = new BufferInfoModel(2, 1, BufferInfoModel::TYPE_QUERY, 0, 'another_clue'); $this->assertSame(2, $model->getId()); $this->assertSame(1, $model->getNetworkId()); - $this->assertSame(0x04, $model->getType()); + $this->assertSame(BufferInfoModel::TYPE_QUERY, $model->getType()); $this->assertSame(0, $model->getGroupId()); $this->assertSame('another_clue', $model->getName()); } diff --git a/tests/Models/MessageModelTest.php b/tests/Models/MessageModelTest.php index 1a6cd79..3c1ad10 100644 --- a/tests/Models/MessageModelTest.php +++ b/tests/Models/MessageModelTest.php @@ -10,8 +10,8 @@ public function testChatMessage() $message = new MessageModel( 1000, 1528039705, - 0x01, - 0x00, + MessageModel::TYPE_PLAIN, + MessageModel::FLAG_NONE, $buffer, 'another_clue!user@host', 'Hello world!' @@ -19,8 +19,8 @@ public function testChatMessage() $this->assertSame(1000, $message->getId()); $this->assertSame(1528039705, $message->getTimestamp()); - $this->assertSame(0x01, $message->getType()); - $this->assertSame(0x00, $message->getFlags()); + $this->assertSame(MessageModel::TYPE_PLAIN, $message->getType()); + $this->assertSame(MessageModel::FLAG_NONE, $message->getFlags()); $this->assertSame($buffer, $message->getBufferInfo()); $this->assertSame('another_clue!user@host', $message->getSender()); $this->assertSame('Hello world!', $message->getContents()); @@ -32,8 +32,8 @@ public function testJoinMessage() $message = new MessageModel( 999, 1528039704, - 0x20, - 0x00, + MessageModel::TYPE_JOIN, + MessageModel::FLAG_NONE, $buffer, 'another_clue!user@host', '#reactphp' @@ -41,8 +41,8 @@ public function testJoinMessage() $this->assertSame(999, $message->getId()); $this->assertSame(1528039704, $message->getTimestamp()); - $this->assertSame(0x20, $message->getType()); - $this->assertSame(0x00, $message->getFlags()); + $this->assertSame(MessageModel::TYPE_JOIN, $message->getType()); + $this->assertSame(MessageModel::FLAG_NONE, $message->getFlags()); $this->assertSame($buffer, $message->getBufferInfo()); $this->assertSame('another_clue!user@host', $message->getSender()); $this->assertSame('#reactphp', $message->getContents()); From 5c10161d9cb8c8356c67371b51df22c7ecdc3605 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Sun, 3 Jun 2018 18:02:41 +0200 Subject: [PATCH 3/3] Remove unneeded "Model" from class names and improve documentation --- README.md | 4 +-- examples/02-chatbot.php | 12 ++++---- examples/03-pingbot.php | 20 ++++++------- examples/04-connect.php | 14 +++++----- src/Client.php | 13 +++++++-- src/Io/Protocol.php | 8 +++--- .../{BufferInfoModel.php => BufferInfo.php} | 5 +++- src/Models/{MessageModel.php => Message.php} | 28 ++++++++++--------- ...erInfoModelTest.php => BufferInfoTest.php} | 12 ++++---- .../{MessageModelTest.php => MessageTest.php} | 28 +++++++++---------- 10 files changed, 78 insertions(+), 66 deletions(-) rename src/Models/{BufferInfoModel.php => BufferInfo.php} (92%) rename src/Models/{MessageModel.php => Message.php} (76%) rename tests/Models/{BufferInfoModelTest.php => BufferInfoTest.php} (57%) rename tests/Models/{MessageModelTest.php => MessageTest.php} (61%) diff --git a/README.md b/README.md index a55618f..3d41de6 100644 --- a/README.md +++ b/README.md @@ -214,8 +214,8 @@ anything about it. There are only few noticable exceptions to this rule: * Incoming buffers/channels and chat messages use complex data models, so they - are represented by `BufferInfoModel` and `MessageModel` respectively. All other - data types use plain structured data, so you can access it's array-based + are represented by `BufferInfo` and `Message` respectively. All other data + types use plain structured data, so you can access it's array-based structure very similar to a JSON-like data structure. * The legacy protocol uses plain times for heartbeat messages while the newer datastream protocol uses `DateTime` objects. diff --git a/examples/02-chatbot.php b/examples/02-chatbot.php index 4a562a1..daa46c5 100644 --- a/examples/02-chatbot.php +++ b/examples/02-chatbot.php @@ -3,7 +3,7 @@ use Clue\React\Quassel\Factory; use Clue\React\Quassel\Client; use Clue\React\Quassel\Io\Protocol; -use Clue\React\Quassel\Models\MessageModel; +use Clue\React\Quassel\Models\Message; require __DIR__ . '/../vendor/autoload.php'; @@ -43,13 +43,13 @@ // chat message received if (isset($message[0]) && $message[0] === Protocol::REQUEST_RPCCALL && $message[1] === '2displayMsg(Message)') { - $data = $message[2]; - assert($data instanceof MessageModel); + $in = $message[2]; + assert($in instanceof Message); - if (strpos($data->getContents(), $keyword) !== false) { - $client->writeBufferInput($data->getBufferInfo(), 'Hello from clue/quassel-react :-)'); + if (strpos($in->getContents(), $keyword) !== false) { + $client->writeBufferInput($in->getBufferInfo(), 'Hello from clue/quassel-react :-)'); - echo date('Y-m-d H:i:s') . ' Replied to ' . $data->getBufferInfo()->getName() . '/' . explode('!', $data->getSender())[0] . ': "' . $data->getContents() . '"' . PHP_EOL; + echo date('Y-m-d H:i:s') . ' Replied to ' . $in->getBufferInfo()->getName() . '/' . explode('!', $in->getSender())[0] . ': "' . $in->getContents() . '"' . PHP_EOL; } } }); diff --git a/examples/03-pingbot.php b/examples/03-pingbot.php index 36de94b..871aa62 100644 --- a/examples/03-pingbot.php +++ b/examples/03-pingbot.php @@ -3,8 +3,8 @@ use Clue\React\Quassel\Factory; use Clue\React\Quassel\Client; use Clue\React\Quassel\Io\Protocol; -use Clue\React\Quassel\Models\BufferInfoModel; -use Clue\React\Quassel\Models\MessageModel; +use Clue\React\Quassel\Models\BufferInfo; +use Clue\React\Quassel\Models\Message; require __DIR__ . '/../vendor/autoload.php'; @@ -66,28 +66,28 @@ // chat message received if (isset($message[0]) && $message[0] === Protocol::REQUEST_RPCCALL && $message[1] === '2displayMsg(Message)') { - $data = $message[2]; - assert($data instanceof MessageModel); + $in = $message[2]; + assert($in instanceof Message); $reply = null; // we may be connected to multiple networks with different nicks // find correct nick for current network - $nick = isset($nicks[$data->getBufferInfo()->getNetworkId()]) ? $nicks[$data->getBufferInfo()->getNetworkId()] : null; + $nick = isset($nicks[$in->getBufferInfo()->getNetworkId()]) ? $nicks[$in->getBufferInfo()->getNetworkId()] : null; // received "nick: ping" in any buffer/channel - if ($nick !== null && strtolower($data->getContents()) === ($nick . ': ping')) { - $reply = explode('!', $data->getSender())[0] . ': pong :-)'; + if ($nick !== null && strtolower($in->getContents()) === ($nick . ': ping')) { + $reply = explode('!', $in->getSender())[0] . ': pong :-)'; } // received "ping" in direct query buffer (user to user) - if (strtolower($data->getContents()) === 'ping' && $data->getBufferInfo()->getType() === BufferInfoModel::TYPE_QUERY) { + if (strtolower($in->getContents()) === 'ping' && $in->getBufferInfo()->getType() === BufferInfo::TYPE_QUERY) { $reply = 'pong :-)'; } if ($reply !== null) { - $client->writeBufferInput($data->getBufferInfo(), $reply); + $client->writeBufferInput($in->getBufferInfo(), $reply); - echo date('Y-m-d H:i:s') . ' Replied to ' . $data->getBufferInfo()->getName() . '/' . explode('!', $data->getSender())[0] . ': "' . $data->getContents() . '"' . PHP_EOL; + echo date('Y-m-d H:i:s') . ' Replied to ' . $in->getBufferInfo()->getName() . '/' . explode('!', $in->getSender())[0] . ': "' . $in->getContents() . '"' . PHP_EOL; } } }); diff --git a/examples/04-connect.php b/examples/04-connect.php index 3dd4a07..f14e0a4 100644 --- a/examples/04-connect.php +++ b/examples/04-connect.php @@ -3,8 +3,8 @@ use Clue\React\Quassel\Factory; use Clue\React\Quassel\Client; use Clue\React\Quassel\Io\Protocol; -use Clue\React\Quassel\Models\MessageModel; -use Clue\React\Quassel\Models\BufferInfoModel; +use Clue\React\Quassel\Models\Message; +use Clue\React\Quassel\Models\BufferInfo; require __DIR__ . '/../vendor/autoload.php'; @@ -38,8 +38,8 @@ } foreach ($message['SessionState']['BufferInfos'] as $buffer) { - assert($buffer instanceof BufferInfoModel); - if ($buffer->getType() === BufferInfoModel::TYPE_CHANNEL) { + assert($buffer instanceof BufferInfo); + if ($buffer->getType() === BufferInfo::TYPE_CHANNEL) { var_dump('requesting IrcChannel for ' . $buffer->getName()); $client->writeInitRequest('IrcChannel', $buffer->getNetworkId() . '/' . $buffer->getId()); } @@ -61,9 +61,9 @@ } if ($type === Protocol::REQUEST_RPCCALL && $message[1] === '2displayMsg(Message)') { - $data = $message[2]; - assert($data instanceof MessageModel); - echo date(DATE_ISO8601, $data->getTimestamp()) . ' in ' . $data->getBufferInfo()->getName() . ' by ' . explode('!', $data->getSender())[0] . ': ' . $data->getContents() . PHP_EOL; + $in = $message[2]; + assert($in instanceof Message); + echo date(DATE_ISO8601, $in->getTimestamp()) . ' in ' . $in->getBufferInfo()->getName() . ' by ' . explode('!', $in->getSender())[0] . ': ' . $in->getContents() . PHP_EOL; return; } diff --git a/src/Client.php b/src/Client.php index 587910e..11aef10 100644 --- a/src/Client.php +++ b/src/Client.php @@ -6,11 +6,11 @@ use Clue\QDataStream\Types; use Clue\React\Quassel\Io\PacketSplitter; use Clue\React\Quassel\Io\Protocol; +use Clue\React\Quassel\Models\BufferInfo; use Evenement\EventEmitter; use React\Stream\DuplexStreamInterface; use React\Stream\Util; use React\Stream\WritableStreamInterface; -use Clue\React\Quassel\Models\BufferInfoModel; class Client extends EventEmitter implements DuplexStreamInterface { @@ -192,13 +192,20 @@ public function writeHeartBeatReply(\DateTime $dt) )); } - public function writeBufferInput(BufferInfoModel $bufferInfo, $input) + /** + * Sends a chat message to the given buffer/channel + * + * @param BufferInfo $bufferInfo buffer/channel to send to (from previous Message object or SessionInit message) + * @param string $contents buffer input (chat message) to send + * @return bool + */ + public function writeBufferInput(BufferInfo $bufferInfo, $contents) { return $this->write(array( Protocol::REQUEST_RPCCALL, "2sendInput(BufferInfo,QString)", new QVariant($bufferInfo, 'BufferInfo'), - (string)$input + (string)$contents )); } diff --git a/src/Io/Protocol.php b/src/Io/Protocol.php index 4fc5124..dda3edf 100644 --- a/src/Io/Protocol.php +++ b/src/Io/Protocol.php @@ -4,8 +4,8 @@ use Clue\QDataStream\Writer; use Clue\QDataStream\Reader; -use Clue\React\Quassel\Models\BufferInfoModel; -use Clue\React\Quassel\Models\MessageModel; +use Clue\React\Quassel\Models\BufferInfo; +use Clue\React\Quassel\Models\Message; /** @internal */ abstract class Protocol @@ -57,7 +57,7 @@ public function __construct() return $reader->readUInt(); }, 'BufferInfo' => function (Reader $reader) { - return new BufferInfoModel( + return new BufferInfo( $reader->readUInt(), $reader->readUInt(), $reader->readUShort(), @@ -74,7 +74,7 @@ public function __construct() return $reader->readUInt(); }, 'Message' => function (Reader $reader) { - return new MessageModel( + return new Message( $reader->readUInt(), $reader->readUInt(), $reader->readUInt(), diff --git a/src/Models/BufferInfoModel.php b/src/Models/BufferInfo.php similarity index 92% rename from src/Models/BufferInfoModel.php rename to src/Models/BufferInfo.php index e303346..b5401cc 100644 --- a/src/Models/BufferInfoModel.php +++ b/src/Models/BufferInfo.php @@ -2,7 +2,7 @@ namespace Clue\React\Quassel\Models; -class BufferInfoModel +class BufferInfo { // @link https://github.com/quassel/quassel/blob/e17fca767d60c06ca02bc5898ced04f06d3670bd/src/common/bufferinfo.h#L32 const TYPE_INVALID = 0x00; @@ -18,11 +18,14 @@ class BufferInfoModel private $name; /** + * [Internal] Instantiation is handled internally and should not be called manually. + * * @param int $id * @param int $networkId * @param int $type single type constant, see self::TYPE_* * @param int $groupId * @param string $name buffer/channel name `#channel`, `user` or empty string + * @internal */ public function __construct($id, $networkId, $type, $groupId, $name) { diff --git a/src/Models/MessageModel.php b/src/Models/Message.php similarity index 76% rename from src/Models/MessageModel.php rename to src/Models/Message.php index 2e19f54..2a0434e 100644 --- a/src/Models/MessageModel.php +++ b/src/Models/Message.php @@ -2,7 +2,7 @@ namespace Clue\React\Quassel\Models; -class MessageModel +class Message { // @link https://github.com/quassel/quassel/blob/e17fca767d60c06ca02bc5898ced04f06d3670bd/src/common/message.h#L35 const TYPE_PLAIN = 0x00001; @@ -39,19 +39,21 @@ class MessageModel private $flags; private $bufferInfo; private $sender; - private $content; + private $contents; /** + * [Internal] Instantiation is handled internally and should not be called manually. * - * @param int $id - * @param int $timestamp UNIX timestamp - * @param int $type single type constant, see self::TYPE_* constants - * @param int $flags bitmask of flag constants, see self::FLAG_* constants - * @param BufferInfoModel $bufferInfo - * @param string $sender sender in the form `nick!user@host` or only `host` or empty string - * @param string $content + * @param int $id + * @param int $timestamp UNIX timestamp + * @param int $type single type constant, see self::TYPE_* constants + * @param int $flags bitmask of flag constants, see self::FLAG_* constants + * @param BufferInfo $bufferInfo + * @param string $sender sender in the form `nick!user@host` or only `host` or empty string + * @param string $contents + * @internal */ - public function __construct($id, $timestamp, $type, $flags, BufferInfoModel $bufferInfo, $sender, $content) + public function __construct($id, $timestamp, $type, $flags, BufferInfo $bufferInfo, $sender, $contents) { $this->id = $id; $this->timestamp = $timestamp; @@ -59,7 +61,7 @@ public function __construct($id, $timestamp, $type, $flags, BufferInfoModel $buf $this->flags = $flags; $this->bufferInfo = $bufferInfo; $this->sender = $sender; - $this->content = $content; + $this->contents = $contents; } /** @@ -95,7 +97,7 @@ public function getFlags() } /** - * @return BufferInfoModel + * @return BufferInfo reference to the buffer/channel this message was received in */ public function getBufferInfo() { @@ -116,6 +118,6 @@ public function getSender() */ public function getContents() { - return $this->content; + return $this->contents; } } diff --git a/tests/Models/BufferInfoModelTest.php b/tests/Models/BufferInfoTest.php similarity index 57% rename from tests/Models/BufferInfoModelTest.php rename to tests/Models/BufferInfoTest.php index 336c84e..620acec 100644 --- a/tests/Models/BufferInfoModelTest.php +++ b/tests/Models/BufferInfoTest.php @@ -1,27 +1,27 @@ assertSame(1, $model->getId()); $this->assertSame(3, $model->getNetworkId()); - $this->assertSame(BufferInfoModel::TYPE_CHANNEL, $model->getType()); + $this->assertSame(BufferInfo::TYPE_CHANNEL, $model->getType()); $this->assertSame(0, $model->getGroupId()); $this->assertSame('#reactphp', $model->getName()); } public function testBufferInfoForUserQuery() { - $model = new BufferInfoModel(2, 1, BufferInfoModel::TYPE_QUERY, 0, 'another_clue'); + $model = new BufferInfo(2, 1, BufferInfo::TYPE_QUERY, 0, 'another_clue'); $this->assertSame(2, $model->getId()); $this->assertSame(1, $model->getNetworkId()); - $this->assertSame(BufferInfoModel::TYPE_QUERY, $model->getType()); + $this->assertSame(BufferInfo::TYPE_QUERY, $model->getType()); $this->assertSame(0, $model->getGroupId()); $this->assertSame('another_clue', $model->getName()); } diff --git a/tests/Models/MessageModelTest.php b/tests/Models/MessageTest.php similarity index 61% rename from tests/Models/MessageModelTest.php rename to tests/Models/MessageTest.php index 3c1ad10..82185c3 100644 --- a/tests/Models/MessageModelTest.php +++ b/tests/Models/MessageTest.php @@ -1,17 +1,17 @@ getMockBuilder('Clue\React\Quassel\Models\BufferInfoModel')->disableOriginalConstructor()->getMock(); - $message = new MessageModel( + $buffer = $this->getMockBuilder('Clue\React\Quassel\Models\BufferInfo')->disableOriginalConstructor()->getMock(); + $message = new Message( 1000, 1528039705, - MessageModel::TYPE_PLAIN, - MessageModel::FLAG_NONE, + Message::TYPE_PLAIN, + Message::FLAG_NONE, $buffer, 'another_clue!user@host', 'Hello world!' @@ -19,8 +19,8 @@ public function testChatMessage() $this->assertSame(1000, $message->getId()); $this->assertSame(1528039705, $message->getTimestamp()); - $this->assertSame(MessageModel::TYPE_PLAIN, $message->getType()); - $this->assertSame(MessageModel::FLAG_NONE, $message->getFlags()); + $this->assertSame(Message::TYPE_PLAIN, $message->getType()); + $this->assertSame(Message::FLAG_NONE, $message->getFlags()); $this->assertSame($buffer, $message->getBufferInfo()); $this->assertSame('another_clue!user@host', $message->getSender()); $this->assertSame('Hello world!', $message->getContents()); @@ -28,12 +28,12 @@ public function testChatMessage() public function testJoinMessage() { - $buffer = $this->getMockBuilder('Clue\React\Quassel\Models\BufferInfoModel')->disableOriginalConstructor()->getMock(); - $message = new MessageModel( + $buffer = $this->getMockBuilder('Clue\React\Quassel\Models\BufferInfo')->disableOriginalConstructor()->getMock(); + $message = new Message( 999, 1528039704, - MessageModel::TYPE_JOIN, - MessageModel::FLAG_NONE, + Message::TYPE_JOIN, + Message::FLAG_NONE, $buffer, 'another_clue!user@host', '#reactphp' @@ -41,8 +41,8 @@ public function testJoinMessage() $this->assertSame(999, $message->getId()); $this->assertSame(1528039704, $message->getTimestamp()); - $this->assertSame(MessageModel::TYPE_JOIN, $message->getType()); - $this->assertSame(MessageModel::FLAG_NONE, $message->getFlags()); + $this->assertSame(Message::TYPE_JOIN, $message->getType()); + $this->assertSame(Message::FLAG_NONE, $message->getFlags()); $this->assertSame($buffer, $message->getBufferInfo()); $this->assertSame('another_clue!user@host', $message->getSender()); $this->assertSame('#reactphp', $message->getContents());