diff --git a/.dunitconfig b/.dunitconfig index a209a65..ccdaa46 100644 --- a/.dunitconfig +++ b/.dunitconfig @@ -1,8 +1,8 @@ // the list of docker images to run against images=" - vectorface/php5.4 vectorface/php5.5 vectorface/php5.6 + vectorface/php-nightly vectorface/hhvm"; diff --git a/README.md b/README.md index 559dc76..5e91d4b 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,10 @@ return array( 'shortenTimeout' => 15 // If after this amount of seconds no url shortener has come up with a short URL the normal URL will be used. (Not in effect when there are no shorteners listening.) + // or + + 'filter' => null // Any valid filter implementing Phergie\Irc\Plugin\React\EventFilter\FilterInterface to filter which messages should be handled + )), ) @@ -87,6 +91,19 @@ Selection of response headers from: [en.wikipedia.org/wiki/List_of_HTTP_header_f * `%header-server%` * `%header-x-powered-by%` +## UrlSectionFilter + +This plugin comes with the `UrlSectionFilter` that lets you filter on the different key value pairs coming out of [`parse_url`](http://php.net/parse_url). The following example filter allows `www.phergie.org`, `www2.phergie.org`, and `phergie.org`: + +```php +new OrFilter([ + new UrlSectionFilter('host', '*.phergie.org'), + new UrlSectionFilter('host', 'phergie.org'), +]) +``` + +The filter comes with a third `strict` parameter where instead of declaring out of scope on missing an URL part it return `false`. + ## Tests To run the unit test suite: diff --git a/composer.json b/composer.json index 5ad8367..304fe59 100644 --- a/composer.json +++ b/composer.json @@ -16,8 +16,9 @@ "php": "^5.5|^7.0", "phergie/phergie-irc-bot-react": "~2", "nojimage/twitter-text-php": "1.1.1", - "phergie/phergie-irc-plugin-http": "^4", - "react/promise": "~1.0|~2.0" + "phergie/phergie-irc-plugin-http": "^4.0", + "react/promise": "~1.0|~2.0", + "phergie/phergie-irc-plugin-react-eventfilter": "^1.0||^2.0" }, "require-dev": { "phake/phake": "dev-VerifierResultConstraint-issue", diff --git a/composer.lock b/composer.lock index c2880d7..8e88359 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "7626892017d5178352ab99728c18e13b", - "content-hash": "3a219ee43e432ba1562f642029c78d1a", + "hash": "1fdefb02dd9ca0c2c1233628d0a8d779", + "content-hash": "ffcc99df122f057d76690c85cdbc92e7", "packages": [ { "name": "evenement/evenement", @@ -113,16 +113,16 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "4d0bdbe1206df7440219ce14c972aa57cc5e4982" + "reference": "f5d04bdd2881ac89abde1fb78cc234bce24327bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/4d0bdbe1206df7440219ce14c972aa57cc5e4982", - "reference": "4d0bdbe1206df7440219ce14c972aa57cc5e4982", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5d04bdd2881ac89abde1fb78cc234bce24327bb", + "reference": "f5d04bdd2881ac89abde1fb78cc234bce24327bb", "shasum": "" }, "require": { @@ -167,7 +167,7 @@ "stream", "uri" ], - "time": "2015-11-03 01:34:55" + "time": "2016-01-23 01:23:02" }, { "name": "guzzlehttp/ringphp", @@ -714,16 +714,16 @@ }, { "name": "phergie/phergie-irc-plugin-http", - "version": "4.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/phergie/plugin-http.git", - "reference": "6f44190806e0817c0fd1dfb06bf221903dddf7cc" + "reference": "b6ad183e193f25ef09119e3fc65057f5be23cffd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phergie/plugin-http/zipball/6f44190806e0817c0fd1dfb06bf221903dddf7cc", - "reference": "6f44190806e0817c0fd1dfb06bf221903dddf7cc", + "url": "https://api.github.com/repos/phergie/plugin-http/zipball/b6ad183e193f25ef09119e3fc65057f5be23cffd", + "reference": "b6ad183e193f25ef09119e3fc65057f5be23cffd", "shasum": "" }, "require": { @@ -760,7 +760,51 @@ "plugin", "react" ], - "time": "2015-12-29 12:41:31" + "time": "2016-01-13 14:24:19" + }, + { + "name": "phergie/phergie-irc-plugin-react-eventfilter", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phergie/phergie-irc-plugin-react-eventfilter.git", + "reference": "d5c4024034f466e510572927cac8956c10c1cac5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phergie/phergie-irc-plugin-react-eventfilter/zipball/d5c4024034f466e510572927cac8956c10c1cac5", + "reference": "d5c4024034f466e510572927cac8956c10c1cac5", + "shasum": "" + }, + "require": { + "phergie/phergie-irc-bot-react": "~2" + }, + "require-dev": { + "phake/phake": "2.0.0-beta2", + "phergie/phergie-irc-plugin-react-usermode": "~2", + "phpunit/phpunit": "4.1.*" + }, + "suggest": { + "phergie/phergie-irc-plugin-react-usermode": "supports filtering of events by channel modes of originating users" + }, + "type": "library", + "autoload": { + "psr-4": { + "Phergie\\Irc\\Plugin\\React\\EventFilter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "description": "Phergie plugin for limiting processing of incoming events based on event metadata", + "keywords": [ + "bot", + "irc", + "plugin", + "react" + ], + "time": "2015-12-22 03:29:44" }, { "name": "psr/http-message", @@ -2660,16 +2704,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v2.8.1", + "version": "v2.8.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc" + "reference": "ee278f7c851533e58ca307f66305ccb9188aceda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a5eb815363c0388e83247e7e9853e5dbc14999cc", - "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ee278f7c851533e58ca307f66305ccb9188aceda", + "reference": "ee278f7c851533e58ca307f66305ccb9188aceda", "shasum": "" }, "require": { @@ -2716,7 +2760,7 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2015-10-30 20:15:42" + "time": "2016-01-13 10:28:07" }, { "name": "symfony/filesystem", diff --git a/src/Filter/UrlEvent.php b/src/Filter/UrlEvent.php new file mode 100644 index 0000000..9a101d9 --- /dev/null +++ b/src/Filter/UrlEvent.php @@ -0,0 +1,85 @@ +url = $url; + $this->parsedUrl = parse_url($url); + $this->event = $event; + } + + public function setMessage($message) + { + return $this->event->setMessage($message); + } + + public function getMessage() + { + return $this->event->getMessage(); + } + + public function setConnection(ConnectionInterface $connection) + { + return $this->event->setConnection($connection); + } + + public function getConnection() + { + return $this->event->getConnection(); + } + + public function setParams(array $params) + { + return $this->event->setParams($params); + } + + public function getParams() + { + return $this->event->getParams(); + } + + public function setCommand($command) + { + return $this->event->setCommand($command); + } + + public function getCommand() + { + return $this->event->getCommand(); + } + + public function getUrlSection($section) + { + if (isset($this->parsedUrl[$section])) { + return $this->parsedUrl[$section]; + } + + return null; + } +} diff --git a/src/Filter/UrlSectionFilter.php b/src/Filter/UrlSectionFilter.php new file mode 100644 index 0000000..a14aca8 --- /dev/null +++ b/src/Filter/UrlSectionFilter.php @@ -0,0 +1,61 @@ +section = $section; + $this->value = $value; + if ($strict === true) { + $this->strictResponse = false; + } + } + + /** + * @param EventInterface $event + * @return bool|null + */ + public function filter(EventInterface $event) + { + if (!($event instanceof UrlEvent)) { + return null; + } + + $section = $event->getUrlSection($this->section); + if ($section === null) { + return $this->strictResponse; + } + + $pattern = '/^' . str_replace('*', '.*', $this->value) . '$/'; + if (preg_match($pattern, $section)) { + return true; + } + + return false; + } +} diff --git a/src/Plugin.php b/src/Plugin.php index b624d94..57aedfb 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -11,11 +11,13 @@ namespace Phergie\Irc\Plugin\React\Url; use GuzzleHttp\Message\Response; +use Phergie\Irc\Plugin\React\Url\Filter\UrlEvent; use React\EventLoop\LoopInterface; use Phergie\Irc\Bot\React\AbstractPlugin; use Phergie\Irc\Bot\React\EventQueue; use Phergie\Irc\Client\React\LoopAwareInterface; use Phergie\Irc\Event\UserEvent; +use Phergie\Irc\Plugin\React\EventFilter\FilterInterface; use React\Promise\Deferred; use Phergie\Plugin\Http\Request; @@ -27,8 +29,6 @@ */ class Plugin extends AbstractPlugin implements LoopAwareInterface { - const URL_HANDLER_INTERFACE = 'Phergie\Irc\Plugin\React\Url\UrlHandlerInterface'; - /** * @var UrlHandlerInterface */ @@ -42,6 +42,11 @@ class Plugin extends AbstractPlugin implements LoopAwareInterface */ protected $hostUrlEmitsOnly = false; + /** + * @var FilterInterface + */ + protected $filter = null; + /** * @var LoopInterface */ @@ -61,7 +66,7 @@ public function __construct(array $config = []) { if ( isset($config['handler']) && - in_array(static::URL_HANDLER_INTERFACE, class_implements($config['handler'])) + in_array(UrlHandlerInterface::class, class_implements($config['handler'])) ) { $this->handler = $config['handler']; } else { @@ -73,6 +78,12 @@ public function __construct(array $config = []) if (isset($config['hostUrlEmitsOnly'])) { $this->hostUrlEmitsOnly = boolval($config['hostUrlEmitsOnly']); } + if ( + isset($config['filter']) && + in_array(FilterInterface::class, class_implements($config['filter'])) + ) { + $this->filter = $config['filter']; + } } /** @@ -118,6 +129,12 @@ public function handleIrcReceived(UserEvent $event, EventQueue $queue) $urls = $extractor->extractURLs(); foreach ($urls as $url) { + if ( + $this->filter !== null && + $this->filter->filter(new UrlEvent($url, $event)) !== false + ) { + continue; + } $this->handleUrl($url, $event, $queue); } } diff --git a/tests/Filter/UrlEventTest.php b/tests/Filter/UrlEventTest.php new file mode 100644 index 0000000..af518f3 --- /dev/null +++ b/tests/Filter/UrlEventTest.php @@ -0,0 +1,56 @@ + 'http', + 'host' => 'phergie.org', + 'port' => null, + ] + ]; + + yield [ + 'https://phergie.org:80/', + [ + 'scheme' => 'https', + 'host' => 'phergie.org', + 'port' => 80, + ] + ]; + } + + /** + * @dataProvider provideGetUrlSection + */ + public function testGetUrlSection($url, array $iterations) + { + $event = Phake::mock(EventInterface::class); + $urlEvent = new UrlEvent($url, $event); + foreach ($iterations as $key => $value) { + $this->assertSame($value, $urlEvent->getUrlSection($key)); + } + } +} diff --git a/tests/Filter/UrlSectionFilterTest.php b/tests/Filter/UrlSectionFilterTest.php new file mode 100644 index 0000000..64082db --- /dev/null +++ b/tests/Filter/UrlSectionFilterTest.php @@ -0,0 +1,93 @@ +assertSame($output, $filter->filter($urlEvent)); + } + + public function provideStrictFilter() + { + yield [ + 'host', + 'phergie.org', + 'http://phergie.org/', + true, + ]; + + yield [ + 'host', + 'phergie.org', + 'http://www.phergie.org/', + false, + ]; + + yield [ + 'host', + '*.org', + 'http://phergie.org/', + true, + ]; + + yield [ + 'port', + 80, + 'http://phergie.org/', + false, + ]; + } + + /** + * @dataProvider provideStrictFilter + */ + public function testStrictFilter($section, $value, $url, $output) + { + $event = Phake::mock(EventInterface::class); + $urlEvent = new UrlEvent($url, $event); + $filter = new UrlSectionFilter($section, $value, true); + $this->assertSame($output, $filter->filter($urlEvent)); + } +} diff --git a/tests/PluginTest.php b/tests/PluginTest.php index f4c4c21..7ce3811 100644 --- a/tests/PluginTest.php +++ b/tests/PluginTest.php @@ -12,6 +12,11 @@ use GuzzleHttp\Message\Response; use Phake; +use Phergie\Irc\Bot\React\EventQueue; +use Phergie\Irc\Event\EventInterface; +use Phergie\Irc\Event\UserEvent; +use Phergie\Irc\Plugin\React\EventFilter\FilterInterface; +use Phergie\Irc\Plugin\React\EventFilter\NotFilter; use Phergie\Irc\Plugin\React\Url\Plugin; use React\Promise\FulfilledPromise; @@ -351,4 +356,42 @@ public function testHandleUrlRequest() { )) ); } + + public function provideFiltered() + { + yield [ + '', + 0, + ]; + + yield [ + 'http://phergie.org', + 1, + ]; + + yield [ + 'http://phergie.org http://wyrihaximus.net', + 2, + ]; + } + + /** + * @dataProvider provideFiltered + */ + public function testFiltered($text, $times) + { + $event = Phake::mock(UserEvent::class); + Phake::when($event)->getParams()->thenReturn([ + 'text' => $text, + ]); + $queue = Phake::mock(EventQueue::class); + $filter = Phake::mock(FilterInterface::class); + Phake::when($filter)->filter($this->isInstanceOf(EventInterface::class))->thenReturn(true); + + (new Plugin([ + 'filter' => $filter, + ]))->handleIrcReceived($event, $queue); + + Phake::verify($filter, Phake::times($times))->filter($this->isInstanceOf(EventInterface::class)); + } }