From a67adab13088ab772f7679b351a781b2c6450f4b Mon Sep 17 00:00:00 2001 From: Tim Vos Date: Tue, 13 Jan 2015 21:26:51 +0000 Subject: [PATCH 01/20] Fixed Fetch Function --- Classes/Command/Joke.php | 3 ++- Classes/Command/Weather.php | 6 +++--- Classes/Listener/Youtube.php | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Classes/Command/Joke.php b/Classes/Command/Joke.php index 9328836e..7f454e47 100644 --- a/Classes/Command/Joke.php +++ b/Classes/Command/Joke.php @@ -1,6 +1,7 @@ bot->log("Fetching joke."); - $data = $this->fetch("http://api.icndb.com/jokes/random"); + $data = func::fetch("http://api.icndb.com/jokes/random"); // ICNDB has escaped slashes in JSON response. $data = stripslashes($data); diff --git a/Classes/Command/Weather.php b/Classes/Command/Weather.php index 4fa30992..9e2a1ea1 100644 --- a/Classes/Command/Weather.php +++ b/Classes/Command/Weather.php @@ -124,7 +124,7 @@ public function command() { protected function getWeather($woeid) { $yql = sprintf('select * from weather.forecast where woeid=%d and u="c"', $woeid); - $response = $this->fetch(sprintf($this->weatherUri, urlencode($yql))); + $response = func::fetch(sprintf($this->weatherUri, urlencode($yql))); $jsonResponse = json_decode($response); if (!$jsonResponse) { @@ -153,7 +153,7 @@ protected function getWeather($woeid) { protected function getLocation($location) { $uri = sprintf($this->locationUri, $location, $this->yahooKey); - $response = $this->fetch($uri); + $response = func::fetch($uri); $jsonResponse = json_decode($response); @@ -176,7 +176,7 @@ protected function getLocation($location) { protected function getLocationNameFromIp($ip) { $uri = sprintf($this->ipUri, $ip); - $response = $this->fetch($uri); + $response = func::fetch($uri); $jsonResponse = json_decode($response); diff --git a/Classes/Listener/Youtube.php b/Classes/Listener/Youtube.php index 79d6dcd2..d1104bba 100644 --- a/Classes/Listener/Youtube.php +++ b/Classes/Listener/Youtube.php @@ -1,6 +1,7 @@ apiUri, $matches[0]); - $Ytdata = \Library\FunctionCollection::fetch($ytApi); + $Ytdata = func::fetch($ytApi); preg_match("/(?<=).*(?=<\/title>)/", $Ytdata, $ytTitle); return $ytTitle[0]; } From f70affc0866fbd7bdac414aca36f5a72dbde9de6 Mon Sep 17 00:00:00 2001 From: Tim Vos <TimTims@users.noreply.github.com> Date: Tue, 13 Jan 2015 22:41:09 +0000 Subject: [PATCH 02/20] Updated Usage on Join --- Classes/Command/Join.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/Command/Join.php b/Classes/Command/Join.php index cb81d5dc..6da7be61 100644 --- a/Classes/Command/Join.php +++ b/Classes/Command/Join.php @@ -23,7 +23,7 @@ class Join extends \Library\IRC\Command\Base { * * @var string */ - protected $usage = 'join [#channel]'; + protected $usage = 'join [#channel] [OPTIONAL: channel key]'; /** * The number of arguments the command needs. From 8e6925c9d2a3504589afa5e1f1176a77ade51616 Mon Sep 17 00:00:00 2001 From: Tim Vos <timtimss@outlook.com> Date: Wed, 14 Jan 2015 00:26:05 +0000 Subject: [PATCH 03/20] Changed the Configuration File Name and Default --- config.php => config.example.php | 0 phpbot404.php | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) rename config.php => config.example.php (100%) diff --git a/config.php b/config.example.php similarity index 100% rename from config.php rename to config.example.php diff --git a/phpbot404.php b/phpbot404.php index 301a3898..722eead7 100644 --- a/phpbot404.php +++ b/phpbot404.php @@ -23,10 +23,10 @@ // Make autoload working require 'Classes/Autoloader.php'; - if (file_exists(ROOT_DIR . '/config.local.php')) { - $config = include_once(ROOT_DIR . '/config.local.php'); - } else { + if (file_exists(ROOT_DIR . '/config.php')) { $config = include_once(ROOT_DIR . '/config.php'); + } else { + die("Please Look at the Installaion Documentation.\n"); } $timezone = ini_get('date.timezone'); From af7151eda67824ae18475c52506f99e51aa63029 Mon Sep 17 00:00:00 2001 From: Tim Vos <TimTims@users.noreply.github.com> Date: Wed, 14 Jan 2015 00:36:57 +0000 Subject: [PATCH 04/20] Updated README --- README.md | 102 ++++++++++++++++++++++++++---------------------------- 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 439978e7..0aa1538a 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,6 @@ -# PHP IRC-BOT (WildPHP) -A IRC Bot built in PHP (using sockets) with OOP. Designed to run off a local LAMP, WAMP, or MAMP stack. -Includes a custom [Upstart](http://upstart.ubuntu.com/) script to run as Linux daemon. - -Web -------- -* Official Website: [http://wildphp.com](http://wildphp.com), Source Code: [Github](https://github.com/pogosheep/IRC-Bot) -* Major Contributors: [Super3](http://super3.org), [Pogosheep](http://layne-obserdia.de), [Matejvelikonja](http://velikonja.si) +# Wild IRC Bot +A IRC Bot built in PHP (using sockets) with OOP. +It is designed to run off a local LAMP, WAMP, MAMP stack or just a plain and simple PHP installation. IRC Community & Support ------- @@ -15,67 +10,60 @@ If you need support or just want to idle in the channel our IRC Channel is ## Features and Functions -### Standard Commands +### Commands +Currently the bot commands with many pre-configured commands, we are always increasing the functionality and performance of the commands, along with increasing the amount of commands to be used. -* !say [#channel] [message] - Says message in the specified IRC channel. -* !say [username] [message] - Says message in the specified IRC user. -* !join [#channel] - Joins the specified channel. -* !part [#channel] - Parts the specified channel. -* !timeout [seconds] - Bot leaves for the specified number of seconds. -* !restart - Quits and restarts the script. -* !quit - Quits and stops the script. +The authentication system allows owners & trusted users to be able to run specific commands that normal users are not able to do. -### Entended Commands +If you have any commands you have, that you want added, feel free to make a pull request and we will look into it. -* !ip - Returns IP of a user. -* !weather [location] - Returns weather data for location. -* !poke [#channel] [username] - Pokes the specified IRC user. -* !joke - Returns random joke. Fetched from [ICNDb.com](http://www.icndb.com/). -* !imdb [movie title] - Searches for movie and returns it's information. - -### Command Arguments -A change has been made as of 5/1/2015 which means that `-1` for `$numberOfArguments` will now not work as it used to. `-1` will not accept no arguments, only 1+. -Please use `$numberOfArguments = [0, -1];` to emulate the same functionality as before. ### Listeners -* Joins - Greets users when they join the channel. +Listeners are a plugin that search for a specific input, in the IRC chatroom. Listeners react based on, if this input is found. -## Install & Run +For example the join listener, listens for channel joins and sends a welcome message to them. -### Dependecy +We are re-designing the listener system to make listener writing easier for developers. +If you have a listener and want it to be added to the bot by default, send in a pull request and we will look into it. -proctitle (optional) - Changes the process title when running as service. +## Dependencies, Installation and Running - pecl install proctitle-alpha +### Dependecies -### Config +The bot itself doesn't require anything to run, but for full functionality we recommend installing these dependencies. -Copy configuration file and customize its content. +#### Ubuntu & Debian + [sudo] apt-get install php-pear php5-curl screen + [sudo] pecl install proctitle-alpha + +#### CentOS + [sudo] yum install php-pear php5-curl screen + [sudo] pecl install proctitle-alpha + +#### Other OS' +Packages that need to be installed are php-pear and php5-curl - cp config.php config.local.php -Copy Upstart script to folder and make appropriate changes. - - sudo cp bin/phpbot404.conf /etc/init/ +### Config -### Run +Rename the configuration file and then edit it, to suit you. -Run as PHP + cp config.example.php config.php - php phpbot404.php +### Run -or Upstart service +Running the bot is very simple. We recommend running it in a screen - start phpbot404 +We do not recommend running the bot as root/sudo. -Restart +Running as screen: - restart phpbot404 + screen -dm php phpbot404.php -Stop +Running without screen - stop phpbot404 + php phpbot404.php ### Sample Usage and Output @@ -84,11 +72,19 @@ Stop <random-user> !poke #wildphp random-user * wildphp-bot pokes random-user -### Upcoming Features +### To-Do List + +* Add User Levels +* (Fully Functioning) Restart Command +* More Commands and Listeners +* Channel Manager +* In-Channel Add and Remove Command +* Fix Bugs +* Renew Upstart Command +* Add Full Documentation + +Extra Information +------- +Official Website: [http://wildphp.com](http://wildphp.com) -* Hostname Authentication -* Custom Quit Messages -* Custom Prefixes -* More Plugins -* Bug Fixes -* Extended Documentation +Major Contributors: [Super3](http://super3.org), [Pogosheep](http://layne-obserdia.de), [Matejvelikonja](http://velikonja.si), [Yoshi2889/NanoSector](https://github.com/Yoshi2889), [TimTims](https://timtims.me) From 3f44b580122dcedc9a666ca672c7f597998f70d4 Mon Sep 17 00:00:00 2001 From: Tim Vos <TimTims@users.noreply.github.com> Date: Wed, 14 Jan 2015 00:38:57 +0000 Subject: [PATCH 05/20] Update .gitignore --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 69182fa4..995e81b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ -*.log -Documentation/* +log/* +.log .buildpath .project .settings/ -config.local.php +config.php .idea From 0bb582e3ea82e1e4fe878cd32169ac7bd1369f16 Mon Sep 17 00:00:00 2001 From: Tim Vos <TimTims@users.noreply.github.com> Date: Wed, 14 Jan 2015 19:36:22 +0000 Subject: [PATCH 06/20] Temporarily Fixed Restart Command --- Classes/Command/Restart.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/Command/Restart.php b/Classes/Command/Restart.php index 73aeb356..99cfc1b2 100644 --- a/Classes/Command/Restart.php +++ b/Classes/Command/Restart.php @@ -57,6 +57,6 @@ public function command() { sleep(5); // Reconnect to Server - $this->bot->connectToServer(); + $this->connection->connect(); } } From 804219fe4406df4945906c4d786595702ea502b7 Mon Sep 17 00:00:00 2001 From: Tim Vos <TimTims@users.noreply.github.com> Date: Wed, 14 Jan 2015 19:36:43 +0000 Subject: [PATCH 07/20] Fixed Timeout Command --- Classes/Command/Timeout.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/Command/Timeout.php b/Classes/Command/Timeout.php index 0a83988e..57b9f6a6 100644 --- a/Classes/Command/Timeout.php +++ b/Classes/Command/Timeout.php @@ -48,6 +48,6 @@ public function command() { // Quit, sleep, and reconnect ( CLI and HTML ) $this->connection->sendData('QUIT'); sleep( (int)($this->arguments[0]) ); - $this->bot->connectToServer(); + $this->connection->connect(); } } From 1d6e150f3c05c8dadaee69c9e513e30772cc7cf2 Mon Sep 17 00:00:00 2001 From: Tim Vos <timtimss@outlook.com> Date: Wed, 14 Jan 2015 20:23:24 +0000 Subject: [PATCH 08/20] Added Verification to Say --- Classes/Command/Say.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Classes/Command/Say.php b/Classes/Command/Say.php index 07d88116..df7a82c1 100644 --- a/Classes/Command/Say.php +++ b/Classes/Command/Say.php @@ -25,6 +25,13 @@ class Say extends \Library\IRC\Command\Base { * @var string */ protected $usage = 'say [#channel|username] whatever you want to say'; + + /** + * Verify the user before executing this command. + * + * @var bool + */ + protected $verify = true; /** * The number of arguments the command needs. From 40cae6be30553b18de83dc200ee70b4b12a2b3a9 Mon Sep 17 00:00:00 2001 From: Amunak <git@amunak.net> Date: Fri, 16 Jan 2015 23:54:10 +0100 Subject: [PATCH 09/20] Autoloader refactoring. Require is a language construct, it does not return anything. --- Classes/Autoloader.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Classes/Autoloader.php b/Classes/Autoloader.php index 19ffc405..3a76b222 100644 --- a/Classes/Autoloader.php +++ b/Classes/Autoloader.php @@ -33,11 +33,14 @@ class Autoloader { * @author Daniel Siepmann <coding.layne@me.com> */ public static function load( $class ) { - $filename = __DIR__ . '/' . str_replace( '\\', '/', $class ) . '.php'; - if (file_exists($filename)) { - return require $filename; + $class = str_replace( '\\', '/', $class ); + $filename = __DIR__ . '/' . $class . '.php'; + + if (!file_exists($filename)) { + throw new Exception('File: "' . $filename . '" not found.'); } - throw new Exception('File: "' . $filename . '" not found.'); + + require $filename; } } From f7d48f148b06eaef4b2a6c48fcc0f6c3582745d9 Mon Sep 17 00:00:00 2001 From: Amunak <git@amunak.net> Date: Sat, 17 Jan 2015 01:01:54 +0100 Subject: [PATCH 10/20] Makes the configuration use neon syntax. --- Classes/Autoloader.php | 1 + Classes/Library/IRC/Log.php | 10 +- Classes/Library/Neon/Decoder.php | 326 +++++++++++++++++++++++++++++ Classes/Library/Neon/Encoder.php | 84 ++++++++ Classes/Library/Neon/Entity.php | 31 +++ Classes/Library/Neon/Exception.php | 18 ++ Classes/Library/Neon/Neon.php | 47 +++++ Classes/Library/Neon/license.md | 60 ++++++ README.md | 4 +- config.example.neon | 56 +++++ config.example.php | 52 ----- phpbot404.php | 9 +- 12 files changed, 637 insertions(+), 61 deletions(-) create mode 100644 Classes/Library/Neon/Decoder.php create mode 100644 Classes/Library/Neon/Encoder.php create mode 100644 Classes/Library/Neon/Entity.php create mode 100644 Classes/Library/Neon/Exception.php create mode 100644 Classes/Library/Neon/Neon.php create mode 100644 Classes/Library/Neon/license.md create mode 100644 config.example.neon delete mode 100644 config.example.php diff --git a/Classes/Autoloader.php b/Classes/Autoloader.php index 3a76b222..0a86221a 100644 --- a/Classes/Autoloader.php +++ b/Classes/Autoloader.php @@ -34,6 +34,7 @@ class Autoloader { */ public static function load( $class ) { $class = str_replace( '\\', '/', $class ); + $class = str_replace('Nette/', 'Library/', $class); $filename = __DIR__ . '/' . $class . '.php'; if (!file_exists($filename)) { diff --git a/Classes/Library/IRC/Log.php b/Classes/Library/IRC/Log.php index fd64a0cf..b56e1da3 100644 --- a/Classes/Library/IRC/Log.php +++ b/Classes/Library/IRC/Log.php @@ -64,16 +64,20 @@ class Log */ public function __construct($config) { + $logDir = $config['dir']; + if(substr($logDir, 0, 2) === './') + $logDir = ROOT_DIR . substr($logDir, 2); + // Can't log to a file not set. if (empty($config['file'])) trigger_error('LogManager: A log file needs to be set to use logging. Aborting.', E_USER_ERROR); // Also can't log to a directory we can't write to. - if (!is_writable($config['dir'])) - trigger_error('LogManager: A log file cannot be created in the set directory (' . $config['dir'] . '). Please make it writable. Aborting.', E_USER_ERROR); + if (!is_writable()) + trigger_error('LogManager: A log file cannot be created in the set directory (' . $logDir . '). Please make it writable. Aborting.', E_USER_ERROR); // Start off with the base path to the file. - $this->logFile = $config['dir'] . '/' . $config['file']; + $this->logFile = $logDir . '/' . $config['file']; // Now, we're going to count up until we find a file that doesn't yet exist. $i = 0; diff --git a/Classes/Library/Neon/Decoder.php b/Classes/Library/Neon/Decoder.php new file mode 100644 index 00000000..b726b285 --- /dev/null +++ b/Classes/Library/Neon/Decoder.php @@ -0,0 +1,326 @@ +<?php + +/** + * This file is part of the Nette Framework (http://nette.org) + * Copyright (c) 2004 David Grudl (http://davidgrudl.com) + */ + +namespace Nette\Neon; + +use Nette; + + +/** + * Parser for Nette Object Notation. + * + * @author David Grudl + * @internal + */ +class Decoder +{ + /** @var array */ + public static $patterns = array( + ' + \'[^\'\n]*\' | + "(?: \\\\. | [^"\\\\\n] )*" + ', // string + ' + (?: [^#"\',:=[\]{}()\x00-\x20!`-] | [:-][^"\',\]})\s] ) + (?: + [^,:=\]})(\x00-\x20]+ | + :(?! [\s,\]})] | $ ) | + [\ \t]+ [^#,:=\]})(\x00-\x20] + )* + ', // literal / boolean / integer / float + ' + [,:=[\]{}()-] + ', // symbol + '?:\#.*', // comment + '\n[\t\ ]*', // new line + indent + '?:[\t\ ]+', // whitespace + ); + + private static $brackets = array( + '[' => ']', + '{' => '}', + '(' => ')', + ); + + /** @var string */ + private $input; + + /** @var array */ + private $tokens; + + /** @var int */ + private $pos; + + + + /** + * Decodes a NEON string. + * @param string + * @return mixed + */ + public function decode($input) + { + if (!is_string($input)) { + throw new \InvalidArgumentException(sprintf('Argument must be a string, %s given.', gettype($input))); + + } elseif (substr($input, 0, 3) === "\xEF\xBB\xBF") { // BOM + $input = substr($input, 3); + } + $this->input = "\n" . str_replace("\r", '', $input); // \n forces indent detection + + $pattern = '~(' . implode(')|(', self::$patterns) . ')~Amix'; + $this->tokens = preg_split($pattern, $this->input, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_DELIM_CAPTURE); + + $last = end($this->tokens); + if ($this->tokens && !preg_match($pattern, $last[0])) { + $this->pos = count($this->tokens) - 1; + $this->error(); + } + + $this->pos = 0; + $res = $this->parse(NULL); + + while (isset($this->tokens[$this->pos])) { + if ($this->tokens[$this->pos][0][0] === "\n") { + $this->pos++; + } else { + $this->error(); + } + } + return $res; + } + + + /** + * @param string indentation (for block-parser) + * @param mixed + * @return array + */ + private function parse($indent, $result = NULL, $key = NULL, $hasKey = FALSE) + { + $inlineParser = $indent === FALSE; + $value = NULL; + $hasValue = FALSE; + $tokens = $this->tokens; + $n = & $this->pos; + $count = count($tokens); + $mainResult = & $result; + + for (; $n < $count; $n++) { + $t = $tokens[$n][0]; + + if ($t === ',') { // ArrayEntry separator + if ((!$hasKey && !$hasValue) || !$inlineParser) { + $this->error(); + } + $this->addValue($result, $hasKey, $key, $hasValue ? $value : NULL); + $hasKey = $hasValue = FALSE; + + } elseif ($t === ':' || $t === '=') { // KeyValuePair separator + if ($hasValue && (is_array($value) || is_object($value))) { + $this->error('Unacceptable key'); + + } elseif ($hasKey && $key === NULL && $hasValue && !$inlineParser) { + $n++; + $result[] = $this->parse($indent . ' ', array(), $value, TRUE); + $newIndent = isset($tokens[$n], $tokens[$n+1]) ? (string) substr($tokens[$n][0], 1) : ''; // not last + if (strlen($newIndent) > strlen($indent)) { + $n++; + $this->error('Bad indentation'); + } elseif (strlen($newIndent) < strlen($indent)) { + return $mainResult; // block parser exit point + } + $hasKey = $hasValue = FALSE; + + } elseif ($hasKey || !$hasValue) { + $this->error(); + + } else { + $key = (string) $value; + $hasKey = TRUE; + $hasValue = FALSE; + $result = & $mainResult; + } + + } elseif ($t === '-') { // BlockArray bullet + if ($hasKey || $hasValue || $inlineParser) { + $this->error(); + } + $key = NULL; + $hasKey = TRUE; + + } elseif (isset(self::$brackets[$t])) { // Opening bracket [ ( { + if ($hasValue) { + if ($t !== '(') { + $this->error(); + } + $n++; + $value = new Entity($value, $this->parse(FALSE, array())); + } else { + $n++; + $value = $this->parse(FALSE, array()); + } + $hasValue = TRUE; + if (!isset($tokens[$n]) || $tokens[$n][0] !== self::$brackets[$t]) { // unexpected type of bracket or block-parser + $this->error(); + } + + } elseif ($t === ']' || $t === '}' || $t === ')') { // Closing bracket ] ) } + if (!$inlineParser) { + $this->error(); + } + break; + + } elseif ($t[0] === "\n") { // Indent + if ($inlineParser) { + if ($hasKey || $hasValue) { + $this->addValue($result, $hasKey, $key, $hasValue ? $value : NULL); + $hasKey = $hasValue = FALSE; + } + + } else { + while (isset($tokens[$n+1]) && $tokens[$n+1][0][0] === "\n") { + $n++; // skip to last indent + } + if (!isset($tokens[$n+1])) { + break; + } + + $newIndent = (string) substr($tokens[$n][0], 1); + if ($indent === NULL) { // first iteration + $indent = $newIndent; + } + $minlen = min(strlen($newIndent), strlen($indent)); + if ($minlen && (string) substr($newIndent, 0, $minlen) !== (string) substr($indent, 0, $minlen)) { + $n++; + $this->error('Invalid combination of tabs and spaces'); + } + + if (strlen($newIndent) > strlen($indent)) { // open new block-array or hash + if ($hasValue || !$hasKey) { + $n++; + $this->error('Bad indentation'); + } + $this->addValue($result, $key !== NULL, $key, $this->parse($newIndent)); + $newIndent = isset($tokens[$n], $tokens[$n+1]) ? (string) substr($tokens[$n][0], 1) : ''; // not last + if (strlen($newIndent) > strlen($indent)) { + $n++; + $this->error('Bad indentation'); + } + $hasKey = FALSE; + + } else { + if ($hasValue && !$hasKey) { // block items must have "key"; NULL key means list item + break; + + } elseif ($hasKey) { + $this->addValue($result, $key !== NULL, $key, $hasValue ? $value : NULL); + if ($key !== NULL && !$hasValue && $newIndent === $indent && isset($tokens[$n + 1]) && $tokens[$n + 1][0] === '-') { + $result = & $result[$key]; + } + $hasKey = $hasValue = FALSE; + } + } + + if (strlen($newIndent) < strlen($indent)) { // close block + return $mainResult; // block parser exit point + } + } + + } else { // Value + if ($hasValue) { + $this->error(); + } + static $consts = array( + 'true' => TRUE, 'True' => TRUE, 'TRUE' => TRUE, 'yes' => TRUE, 'Yes' => TRUE, 'YES' => TRUE, 'on' => TRUE, 'On' => TRUE, 'ON' => TRUE, + 'false' => FALSE, 'False' => FALSE, 'FALSE' => FALSE, 'no' => FALSE, 'No' => FALSE, 'NO' => FALSE, 'off' => FALSE, 'Off' => FALSE, 'OFF' => FALSE, + ); + if ($t[0] === '"') { + $value = preg_replace_callback('#\\\\(?:ud[89ab][0-9a-f]{2}\\\\ud[c-f][0-9a-f]{2}|u[0-9a-f]{4}|x[0-9a-f]{2}|.)#i', array($this, 'cbString'), substr($t, 1, -1)); + } elseif ($t[0] === "'") { + $value = substr($t, 1, -1); + } elseif (isset($consts[$t]) && (!isset($tokens[$n+1][0]) || ($tokens[$n+1][0] !== ':' && $tokens[$n+1][0] !== '='))) { + $value = $consts[$t]; + } elseif ($t === 'null' || $t === 'Null' || $t === 'NULL') { + $value = NULL; + } elseif (is_numeric($t)) { + $value = $t * 1; + } elseif (preg_match('#\d\d\d\d-\d\d?-\d\d?(?:(?:[Tt]| +)\d\d?:\d\d:\d\d(?:\.\d*)? *(?:Z|[-+]\d\d?(?::\d\d)?)?)?\z#A', $t)) { + $value = new \DateTime($t); + } else { // literal + $value = $t; + } + $hasValue = TRUE; + } + } + + if ($inlineParser) { + if ($hasKey || $hasValue) { + $this->addValue($result, $hasKey, $key, $hasValue ? $value : NULL); + } + } else { + if ($hasValue && !$hasKey) { // block items must have "key" + if ($result === NULL) { + $result = $value; // simple value parser + } else { + $this->error(); + } + } elseif ($hasKey) { + $this->addValue($result, $key !== NULL, $key, $hasValue ? $value : NULL); + } + } + return $mainResult; + } + + + private function addValue(& $result, $hasKey, $key, $value) + { + if ($hasKey) { + if ($result && array_key_exists($key, $result)) { + $this->error("Duplicated key '$key'"); + } + $result[$key] = $value; + } else { + $result[] = $value; + } + } + + + private function cbString($m) + { + static $mapping = array('t' => "\t", 'n' => "\n", 'r' => "\r", 'f' => "\x0C", 'b' => "\x08", '"' => '"', '\\' => '\\', '/' => '/', '_' => "\xc2\xa0"); + $sq = $m[0]; + if (isset($mapping[$sq[1]])) { + return $mapping[$sq[1]]; + } elseif ($sq[1] === 'u' && strlen($sq) >= 6) { + $lead = hexdec(substr($sq, 2, 4)); + $tail = hexdec(substr($sq, 8, 4)); + $code = $tail ? (0x2400 + (($lead - 0xD800) << 10) + $tail) : $lead; + if ($code >= 0xD800 && $code <= 0xDFFF) { + $this->error("Invalid UTF-8 (lone surrogate) $sq"); + } + return iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code)); + } elseif ($sq[1] === 'x' && strlen($sq) === 4) { + return chr(hexdec(substr($sq, 2))); + } else { + $this->error("Invalid escaping sequence $sq"); + } + } + + + private function error($message = "Unexpected '%s'") + { + $last = isset($this->tokens[$this->pos]) ? $this->tokens[$this->pos] : NULL; + $offset = $last ? $last[1] : strlen($this->input); + $text = substr($this->input, 0, $offset); + $line = substr_count($text, "\n"); + $col = $offset - strrpos("\n" . $text, "\n") + 1; + $token = $last ? str_replace("\n", '<new line>', substr($last[0], 0, 40)) : 'end'; + throw new Exception(str_replace('%s', $token, $message) . " on line $line, column $col."); + } + +} diff --git a/Classes/Library/Neon/Encoder.php b/Classes/Library/Neon/Encoder.php new file mode 100644 index 00000000..c47e918f --- /dev/null +++ b/Classes/Library/Neon/Encoder.php @@ -0,0 +1,84 @@ +<?php + +/** + * This file is part of the Nette Framework (http://nette.org) + * Copyright (c) 2004 David Grudl (http://davidgrudl.com) + */ + +namespace Nette\Neon; + +use Nette; + + +/** + * Simple generator for Nette Object Notation. + * + * @author David Grudl + */ +class Encoder +{ + const BLOCK = 1; + + + /** + * Returns the NEON representation of a value. + * @param mixed + * @param int + * @return string + */ + public function encode($var, $options = NULL) + { + if ($var instanceof \DateTime) { + return $var->format('Y-m-d H:i:s O'); + + } elseif ($var instanceof Entity) { + return $this->encode($var->value) . '(' + . (is_array($var->attributes) ? substr($this->encode($var->attributes), 1, -1) : '') . ')'; + } + + if (is_object($var)) { + $obj = $var; $var = array(); + foreach ($obj as $k => $v) { + $var[$k] = $v; + } + } + + if (is_array($var)) { + $isList = !$var || array_keys($var) === range(0, count($var) - 1); + $s = ''; + if ($options & self::BLOCK) { + if (count($var) === 0) { + return '[]'; + } + foreach ($var as $k => $v) { + $v = $this->encode($v, self::BLOCK); + $s .= ($isList ? '-' : $this->encode($k) . ':') + . (strpos($v, "\n") === FALSE ? ' ' . $v : "\n\t" . str_replace("\n", "\n\t", $v)) + . "\n"; + continue; + } + return $s; + + } else { + foreach ($var as $k => $v) { + $s .= ($isList ? '' : $this->encode($k) . ': ') . $this->encode($v) . ', '; + } + return ($isList ? '[' : '{') . substr($s, 0, -2) . ($isList ? ']' : '}'); + } + + } elseif (is_string($var) && !is_numeric($var) + && !preg_match('~[\x00-\x1F]|^\d{4}|^(true|false|yes|no|on|off|null)\z~i', $var) + && preg_match('~^' . Decoder::$patterns[1] . '\z~x', $var) // 1 = literals + ) { + return $var; + + } elseif (is_float($var)) { + $var = json_encode($var); + return strpos($var, '.') === FALSE ? $var . '.0' : $var; + + } else { + return json_encode($var, PHP_VERSION_ID >= 50400 ? JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES : 0); + } + } + +} diff --git a/Classes/Library/Neon/Entity.php b/Classes/Library/Neon/Entity.php new file mode 100644 index 00000000..a1dd040e --- /dev/null +++ b/Classes/Library/Neon/Entity.php @@ -0,0 +1,31 @@ +<?php + +/** + * This file is part of the Nette Framework (http://nette.org) + * Copyright (c) 2004 David Grudl (http://davidgrudl.com) + */ + +namespace Nette\Neon; + +use Nette; + + +/** + * Representation of 'foo(bar=1)' literal + */ +class Entity extends \stdClass +{ + /** @var mixed */ + public $value; + + /** @var array */ + public $attributes; + + + public function __construct($value = NULL, array $attrs = NULL) + { + $this->value = $value; + $this->attributes = (array) $attrs; + } + +} diff --git a/Classes/Library/Neon/Exception.php b/Classes/Library/Neon/Exception.php new file mode 100644 index 00000000..4ab52ce8 --- /dev/null +++ b/Classes/Library/Neon/Exception.php @@ -0,0 +1,18 @@ +<?php + +/** + * This file is part of the Nette Framework (http://nette.org) + * Copyright (c) 2004 David Grudl (http://davidgrudl.com) + */ + +namespace Nette\Neon; + +use Nette; + + +/** + * The exception that indicates error of NEON processing. + */ +class Exception extends \Exception +{ +} diff --git a/Classes/Library/Neon/Neon.php b/Classes/Library/Neon/Neon.php new file mode 100644 index 00000000..c2fa231a --- /dev/null +++ b/Classes/Library/Neon/Neon.php @@ -0,0 +1,47 @@ +<?php + +/** + * This file is part of the Nette Framework (http://nette.org) + * Copyright (c) 2004 David Grudl (http://davidgrudl.com) + */ + +namespace Nette\Neon; + +use Nette; + + +/** + * Simple parser & generator for Nette Object Notation. + * + * @author David Grudl + */ +class Neon +{ + const BLOCK = Encoder::BLOCK; + + + /** + * Returns the NEON representation of a value. + * @param mixed + * @param int + * @return string + */ + public static function encode($var, $options = NULL) + { + $encoder = new Encoder; + return $encoder->encode($var, $options); + } + + + /** + * Decodes a NEON string. + * @param string + * @return mixed + */ + public static function decode($input) + { + $decoder = new Decoder; + return $decoder->decode($input); + } + +} diff --git a/Classes/Library/Neon/license.md b/Classes/Library/Neon/license.md new file mode 100644 index 00000000..8906f177 --- /dev/null +++ b/Classes/Library/Neon/license.md @@ -0,0 +1,60 @@ +Licenses +======== + +Good news! You may use Nette Framework under the terms of either +the New BSD License or the GNU General Public License (GPL) version 2 or 3. + +The BSD License is recommended for most projects. It is easy to understand and it +places almost no restrictions on what you can do with the framework. If the GPL +fits better to your project, you can use the framework under this license. + +You don't have to notify anyone which license you are using. You can freely +use Nette Framework in commercial projects as long as the copyright header +remains intact. + +Please be advised that the name "Nette Framework" is a protected trademark and its +usage has some limitations. So please do not use word "Nette" in the name of your +project or top-level domain, and choose a name that stands on its own merits. +If your stuff is good, it will not take long to establish a reputation for yourselves. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (http://davidgrudl.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +This software is provided by the copyright holders and contributors "as is" and +any express or implied warranties, including, but not limited to, the implied +warranties of merchantability and fitness for a particular purpose are +disclaimed. In no event shall the copyright owner or contributors be liable for +any direct, indirect, incidental, special, exemplary, or consequential damages +(including, but not limited to, procurement of substitute goods or services; +loss of use, data, or profits; or business interruption) however caused and on +any theory of liability, whether in contract, strict liability, or tort +(including negligence or otherwise) arising in any way out of the use of this +software, even if advised of the possibility of such damage. + + +GNU General Public License +-------------------------- + +GPL licenses are very very long, so instead of including them here we offer +you URLs with full text: + +- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html) +- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html) diff --git a/README.md b/README.md index 0aa1538a..6bdfdbbe 100644 --- a/README.md +++ b/README.md @@ -47,9 +47,9 @@ Packages that need to be installed are php-pear and php5-curl ### Config -Rename the configuration file and then edit it, to suit you. +Copy the example configuration file and edit it to suit you. It uses the [Neon](http://ne-on.org/) syntax (borrowed from [Nette Framework](http://nette.org/en/)). It is similar to yaml but less strict and much faster to parse. - cp config.example.php config.php + cp config.example.neon config.neon ### Run diff --git a/config.example.neon b/config.example.neon new file mode 100644 index 00000000..192f25b5 --- /dev/null +++ b/config.example.neon @@ -0,0 +1,56 @@ +# This is the bot configuration file. +# It uses neon (http://ne-on.org/) syntax. +# You can use tabs or spaces for indentation. +# Strings with special characters need to be in single (') or double (") quotes. + +# IRC server connection details +server: irc.freenode.org +port: 6667 +password: '' +nick: phpbot +name: phpbot +nickserv: NickServ + +# General bot settings +timezone: America/New_York +max_reconnects: 1 +prefix: '!' + +channels: + - '#wildphp' + +log: + file: log + dir: /logs + filter: + - '#wildphp' + +# Load and configure the bot commands +commands: + Command\Say: + Command\Weather: + yahooKey: a + Command\Joke: + Command\Ip: + Command\Imdb: + Command\Poke: + Command\Topic: + Command\Join: + Command\Part: + Command\Timeout: + Command\Quit: + Command\Restart: + Command\Help: + Command\Update: + Command\Version: + Command\Raw: + +# Load and configure listeners +listeners: + Listener\Joins: + Listener\Youtube: + +# Set up the 'masters' of the bot here using 'ident@hostmask' syntax +hosts: + - ~notident@example.com + - idented@example.net diff --git a/config.example.php b/config.example.php deleted file mode 100644 index 240b91bd..00000000 --- a/config.example.php +++ /dev/null @@ -1,52 +0,0 @@ -<?php -return array( - 'server' => 'irc.freenode.org', - 'port' => 6667, - 'name' => 'phpbot', - 'password' => '', - 'nick' => 'phpbot', - 'nickserv' => 'NickServ', // What is the nickname registration service on this server? - 'channels' => array( - '#wildphp', - ), - 'timezone' => 'America/New_York', - 'max_reconnects' => 1, - 'prefix' => '!', - 'log' => array( - 'file' => 'log', // No file extension! - 'dir' => ROOT_DIR . '/logs', - - // Set this if you want only output from specific channel(s) to show up. - // This will not log any other output, so this is not useful for debugging. - // This is particularly useful if you use public logs. - // Can contain multiple channels. - 'filter' => array('#wildphp'), - ), - 'commands' => array( - 'Command\Say' => array(), - 'Command\Weather' => array( - 'yahooKey' => 'a', - ), - 'Command\Joke' => array(), - 'Command\Ip' => array(), - 'Command\Imdb' => array(), - 'Command\Poke' => array(), - 'Command\Topic' => array(), - 'Command\Join' => array(), - 'Command\Part' => array(), - 'Command\Timeout' => array(), - 'Command\Quit' => array(), - 'Command\Restart' => array(), - 'Command\Help' => array(), - 'Command\Update' => array(), - 'Command\Version' => array(), - 'Command\Raw' => array(), - ), - 'listeners' => array( - 'Listener\Joins' => array(), - 'Listener\Youtube' => array(), - ), - 'hosts' => array( - 'Add trusted hosts in here. Example: example@example.com if no ident server ~example@example.com', - ), -); diff --git a/phpbot404.php b/phpbot404.php index 722eead7..5a4903e1 100644 --- a/phpbot404.php +++ b/phpbot404.php @@ -16,6 +16,7 @@ */ define('ROOT_DIR', __DIR__); + define('CONFIG_FILE', '/config.neon') // Configure PHP //ini_set( 'display_errors', 'on' ); @@ -23,12 +24,12 @@ // Make autoload working require 'Classes/Autoloader.php'; - if (file_exists(ROOT_DIR . '/config.php')) { - $config = include_once(ROOT_DIR . '/config.php'); - } else { - die("Please Look at the Installaion Documentation.\n"); + if ( !file_exists(ROOT_DIR . CONFIG_FILE) || ($config = file_get_contents(ROOT_DIR . CONFIG_FILE)) === false ) { + die('Could not read config file. Please Look at the Installaion Documentation.' . PHP_EOL); } + $config = Nette\Neon\Neon::decode($config); + $timezone = ini_get('date.timezone'); if (empty($timezone)) { if (empty($config['timezone'])) From 093ff37ce10df9fec5f0115921202ad35112f9c9 Mon Sep 17 00:00:00 2001 From: Amunak <git@amunak.net> Date: Sat, 17 Jan 2015 01:07:05 +0100 Subject: [PATCH 11/20] fixes syntax error --- phpbot404.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpbot404.php b/phpbot404.php index 5a4903e1..521994c5 100644 --- a/phpbot404.php +++ b/phpbot404.php @@ -16,7 +16,7 @@ */ define('ROOT_DIR', __DIR__); - define('CONFIG_FILE', '/config.neon') + define('CONFIG_FILE', '/config.neon'); // Configure PHP //ini_set( 'display_errors', 'on' ); From 12b7e788166c56df582851b8b0267f806b70c90a Mon Sep 17 00:00:00 2001 From: Amunak <git@amunak.net> Date: Sat, 17 Jan 2015 01:07:18 +0100 Subject: [PATCH 12/20] fixes default log directory --- config.example.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.example.neon b/config.example.neon index 192f25b5..1523f4e3 100644 --- a/config.example.neon +++ b/config.example.neon @@ -21,7 +21,7 @@ channels: log: file: log - dir: /logs + dir: ./logs filter: - '#wildphp' From f41fe856a6c2495a5c6ca0d033fb5214ecca28d3 Mon Sep 17 00:00:00 2001 From: Amunak <git@amunak.net> Date: Sat, 17 Jan 2015 01:09:19 +0100 Subject: [PATCH 13/20] Makes the class loading available sooner --- phpbot404.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/phpbot404.php b/phpbot404.php index 521994c5..2ad9b08c 100644 --- a/phpbot404.php +++ b/phpbot404.php @@ -23,6 +23,7 @@ // Make autoload working require 'Classes/Autoloader.php'; + spl_autoload_register( 'Autoloader::load' ); if ( !file_exists(ROOT_DIR . CONFIG_FILE) || ($config = file_get_contents(ROOT_DIR . CONFIG_FILE)) === false ) { die('Could not read config file. Please Look at the Installaion Documentation.' . PHP_EOL); @@ -38,8 +39,6 @@ date_default_timezone_set($config['timezone']); } - spl_autoload_register( 'Autoloader::load' ); - // Initialise the LogManager. $log = new Library\IRC\Log($config['log']); From a4336f00c9688533aeb002a897efcf04b590dfb5 Mon Sep 17 00:00:00 2001 From: Amunak <git@amunak.net> Date: Sat, 17 Jan 2015 01:09:58 +0100 Subject: [PATCH 14/20] Fixes missing parameter in Log.php --- Classes/Library/IRC/Log.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/Library/IRC/Log.php b/Classes/Library/IRC/Log.php index b56e1da3..ec3e6930 100644 --- a/Classes/Library/IRC/Log.php +++ b/Classes/Library/IRC/Log.php @@ -73,7 +73,7 @@ public function __construct($config) trigger_error('LogManager: A log file needs to be set to use logging. Aborting.', E_USER_ERROR); // Also can't log to a directory we can't write to. - if (!is_writable()) + if (!is_writable($logDir)) trigger_error('LogManager: A log file cannot be created in the set directory (' . $logDir . '). Please make it writable. Aborting.', E_USER_ERROR); // Start off with the base path to the file. From c414d3e1a2ca6d91cbb57daa8ac549684127467c Mon Sep 17 00:00:00 2001 From: Amunak <git@amunak.net> Date: Sat, 17 Jan 2015 01:17:11 +0100 Subject: [PATCH 15/20] Fixes errorneous ROOT_DIR concatenation --- Classes/Library/IRC/Log.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/Library/IRC/Log.php b/Classes/Library/IRC/Log.php index ec3e6930..d3bafc69 100644 --- a/Classes/Library/IRC/Log.php +++ b/Classes/Library/IRC/Log.php @@ -66,7 +66,7 @@ public function __construct($config) { $logDir = $config['dir']; if(substr($logDir, 0, 2) === './') - $logDir = ROOT_DIR . substr($logDir, 2); + $logDir = ROOT_DIR . substr($logDir, 1); // Can't log to a file not set. if (empty($config['file'])) From f256bb3bb4eceb4fa1acdb60bba97e848b4a3ecf Mon Sep 17 00:00:00 2001 From: Amunak <git@amunak.net> Date: Sat, 17 Jan 2015 01:32:35 +0100 Subject: [PATCH 16/20] Command/Listener loader refactor. Empty command/listener args will no longer cause errors. --- phpbot404.php | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/phpbot404.php b/phpbot404.php index 2ad9b08c..b559facc 100644 --- a/phpbot404.php +++ b/phpbot404.php @@ -48,21 +48,20 @@ // Register the shutdown function. register_shutdown_function(array($bot, 'onShutdown')); - // Add commands to the bot. - foreach ($config['commands'] as $commandName => $args) { - $reflector = new ReflectionClass($commandName); - - $command = $reflector->newInstanceArgs($args); - - $bot->commandManager->addCommand($command); - } - - foreach ($config['listeners'] as $listenerName => $args) { - $reflector = new ReflectionClass($listenerName); - - $listener = $reflector->newInstanceArgs($args); - - $bot->listenerManager->addListener($listener); + // Add commands and listeners to the bot. + foreach (array_merge($config['commands'], $config['listeners']) as $className => $args) { + $reflector = new ReflectionClass($className); + if(!isset($args)) + $args = array(); + + $instance = $reflector->newInstanceArgs($args); + if(array_key_exists($className, $config['commands'])) { + $bot->commandManager->addCommand($instance); + } else if(array_key_exists($className, $config['listeners'])) { + $bot->listenerManager->addListener($instance); + } else { + $bot->log('Command/Listener loader found invalid class ( ' . $className . ' ). Skipping.', 'STARTUP'); + } } if (function_exists('setproctitle')) { From 17874af7498e8b6c9892db1a603db2e6757c72ca Mon Sep 17 00:00:00 2001 From: Amunak <git@amunak.net> Date: Sat, 17 Jan 2015 01:45:31 +0100 Subject: [PATCH 17/20] Adds exception handling to config decoding --- phpbot404.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/phpbot404.php b/phpbot404.php index b559facc..e10a1e23 100644 --- a/phpbot404.php +++ b/phpbot404.php @@ -29,7 +29,11 @@ die('Could not read config file. Please Look at the Installaion Documentation.' . PHP_EOL); } - $config = Nette\Neon\Neon::decode($config); + try { + $config = Nette\Neon\Neon::decode($config); + } catch (Nette\Neon\Exception $e) { + die('Configuration syntax error: ' . $e->getMessage()); + } $timezone = ini_get('date.timezone'); if (empty($timezone)) { From 70df71d9452250618107f3a32d9a4405d6401e01 Mon Sep 17 00:00:00 2001 From: Amunak <git@amunak.net> Date: Sat, 17 Jan 2015 01:46:36 +0100 Subject: [PATCH 18/20] Improves exception format (inserts EOL) --- phpbot404.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpbot404.php b/phpbot404.php index e10a1e23..85ca169a 100644 --- a/phpbot404.php +++ b/phpbot404.php @@ -32,7 +32,7 @@ try { $config = Nette\Neon\Neon::decode($config); } catch (Nette\Neon\Exception $e) { - die('Configuration syntax error: ' . $e->getMessage()); + die('Configuration syntax error: ' . $e->getMessage() . PHP_EOL); } $timezone = ini_get('date.timezone'); From 9feb52e19531e5f62fa4276b0cfe8e4abd5d3ccd Mon Sep 17 00:00:00 2001 From: Tim Vos <timtimss@outlook.com> Date: Sun, 18 Jan 2015 17:22:52 +0000 Subject: [PATCH 19/20] Updated Version --- Classes/Library/IRC/Bot.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/Library/IRC/Bot.php b/Classes/Library/IRC/Bot.php index 30c91609..3395f809 100644 --- a/Classes/Library/IRC/Bot.php +++ b/Classes/Library/IRC/Bot.php @@ -109,7 +109,7 @@ class Bot { * Version of the Bot * @var string */ - public $botVersion = '1.1.0.0'; + public $botVersion = '1.2.0.1'; /** * The listener manager. From b2fbd279088c612985422b7e261e5572a0c415d8 Mon Sep 17 00:00:00 2001 From: Tim Vos <TimTims@users.noreply.github.com> Date: Sun, 18 Jan 2015 17:24:49 +0000 Subject: [PATCH 20/20] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 995e81b7..48f7c79c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,6 @@ log/* .buildpath .project .settings/ -config.php +config.neon .idea