db = new PDO('sqlite:' . $this->dir . 'drink.db'); if (!$this->db) { return; } // Populate the database if necessary if ($this->needTable('beer')) { $this->debug('Retrieving data for: Beer'); $contents = @file_get_contents('http://beerme.com/beerlist.php'); if ($contents !== false) { $this->debug('Parsing data for: Beer'); preg_match_all('/brewery\.php\?[0-9]+#[0-9]+\'>([^<]+)/', $contents, $matches); $names = array(); foreach($matches[1] as $key => $name) { $name = $this->decodeTranslit($name); if ($this->hasBadChars($name) || strpos($name, '(discontinued)') !== false) { continue; } $name = explode('/', preg_replace('/\([^)]+\)/', '', $name)); $name = trim(array_shift($name)); if (!empty($name)) { $name = html_entity_decode($name); $names[] = $name; } } $this->populateTable('beer', $names); unset($names); } } if ($this->needTable('cocktail')) { $limit = 2; $names = array(); $this->debug('Retrieving data for: Cocktail'); for ($i = 1; $i <= $limit; $i += 150) { $contents = @file_get_contents('http://www.webtender.com/db/browse?level=2&dir=drinks&char=%2A&start=' . $i); if ($contents === false) { break; } $this->debug('Parsing data for: Cocktail (' . $i . ' - ' . ($i + 150) . ')'); if ($i == 1) { preg_match('/>([0-9]+) found\\.([^<]+)/', $contents, $matches); foreach($matches[1] as $name) { $name = $this->decodeTranslit($name); if ($this->hasBadChars($name)) { continue; } $name = html_entity_decode(preg_replace('/ The$|^The |\s*\([^)]+\)\s*| #[0-9]+$/', '', $name)); $names[] = $name; } } if ($contents) { $this->populateTable('cocktail', $names); } unset($names); } if ($this->needTable('coke')) { $this->debug('Retrieving data for: Coke'); $contents = @file_get_contents('http://www.energyfiend.com/huge-caffeine-database/'); if ($contents) { $this->debug('Parsing data for: Coke'); // List of drinks to filter out $filter = array( 'tea', 'coffee', 'starbucks' ); $start = stripos($contents, 'id="caffeinedb"'); $end = stripos($contents, '', $start); $contents = substr($contents, $start, $end-$start); preg_match_all('/]*>(<[^>]+>)?([^<]+)/is', $contents, $matches); $names = array(); foreach($matches[2] as $name) { $name = $this->decodeTranslit($name); if ($this->hasBadChars($name)) { continue; } $name = html_entity_decode(trim(preg_replace('/ \\([^)]+\\)| - .*$/', '', $name))); if (!preg_match('/(?:^|\s+)(?:' . implode('|', $filter) . ')(?:\s+|$)/i', $name)) { $names[] = $name; } } $this->populateTable('coke', $names); unset($names); } } if ($this->needTable('tea')) { $this->debug('Retrieving data for: Tea'); $names = @file('http://www.midnight-labs.org/tea.txt'); if ($names) { $this->debug('Parsing data for: Tea'); foreach($names as $key => $value) { $value = $this->decodeTranslit($value); if ($this->hasBadChars($value)) { continue; } $names[$key] = ucwords(trim($value)); } $this->populateTable('tea', $names); unset($names); } } } catch (PDOException $e) { } unset($this->filter); } /** * Returns whether or not the plugin's dependencies are met. * * @param Phergie_Driver_Abstract $client Client instance * @param array $plugins List of short names for plugins that the * bootstrap file intends to instantiate * @see Phergie_Plugin_Abstract_Base::checkDependencies() * @return bool TRUE if dependencies are met, FALSE otherwise */ public static function checkDependencies(Phergie_Driver_Abstract $client, array $plugins) { $errors = array(); if (!extension_loaded('PDO')) { $errors[] = 'PDO php extension is required'; } if (!extension_loaded('pdo_sqlite')) { $errors[] = 'pdo_sqlite php extension is required'; } return empty($errors) ? true : $errors; } /** * Determines if a table does not exist or is empty. * * @param string $name Table name * @return bool TRUE if the table does not exist or is empty, FALSE * otherwise */ private function needTable($name) { $table = $this->db->query('SELECT COUNT(*) FROM sqlite_master WHERE name = ' . $this->db->quote($name))->fetchColumn(); if (!$table) { return true; } return !$this->db->query('SELECT COUNT(*) FROM ' . $name)->fetchColumn(); } /** * Populates a source database table with a given set of data. * * @param string $table Name of the table * @param array $names List of drink names * @return void */ private function populateTable($table, $names) { $this->debug('Creating the database schema for: ' . ucfirst($table)); $this->db->exec('CREATE TABLE ' . $table . ' (name VARCHAR(255))'); $this->db->exec('CREATE UNIQUE INDEX ' . $table . '_name ON ' . $table . ' (name)'); $stmt = $this->db->prepare('INSERT INTO ' . $table . ' (name) VALUES (:name)'); $this->db->beginTransaction(); foreach(array_unique($names) as $name) { if (preg_match('/(?:^|[^a-z])(' . implode('|', $this->filter) . ')(?:[^a-z]|$)/i', $name, $match)) { $this->debug('Filtered out ' . $name . ' because it contains ' . $match[1]); continue; } $this->debug('Inserted ' . ucfirst($table) . ': ' . $name); $stmt->execute(array('name' => $name)); } $this->db->commit(); } /** * Returns a random record value from a given table. * * @string $table Name of the table * @return string Value of the name column for the selected record */ private function getRandomRecord($table) { return $this->db->query('SELECT name FROM ' . $table . ' ORDER BY RANDOM() LIMIT 1')->fetchColumn(); } /** * Returns a random record value from a given search pattern. * * @string $table Name of the table * @return string Value of the name column for the selected record */ private function getSearchRecord($table, $search) { $search = $this->db->quote('%' . str_replace(array('\\', '%'), array('\\\\', '\\%'), $search) .'%'); return $this->db->query('SELECT name FROM ' . $table . ' WHERE name LIKE ' . $search . ' ESCAPE "\\" ORDER BY RANDOM() LIMIT 1')->fetchColumn(); } /** * Returns whether or not a given value has characters that may not be * displayed correctly. * * @param string $name Value to check */ private function hasBadChars($name) { return (max(array_map('ord', str_split($name))) > 126); } /** * Resolves a target to the appropriate nick or pronoun and returns the * result. * * @param string $target Original specified target * @return string Resolved target */ protected function resolveTarget($target) { $target = rtrim(trim($target), '.?!'); switch (trim(strtolower($target))) { case 'me': $target = $this->event->getNick(); break; case 'you': case 'your self': case 'yourself': case strtolower($this->getIni('nick')): $gender = $this->getIni('gender'); if (!$gender || $gender == 'F') { $target = 'herself'; } else { $target = 'himself'; } break; } return $target; } /** * Responds to a message requesting that the bot perform an action to * serve the source with a random drink of a specific type. * * @param string $type Type of drink * @param string $message Drink reuest message * @return void */ protected function handleDrink($type, $message) { if (!$this->db) { return; } $message = preg_replace('/\s+/', ' ', trim($message)); preg_match('/^(.+?)(?:\s+an?(?:\s+(?:cuppa|cup\s+of))?\s+(.+?))?(\s+(?:for|because)\s+.+)?$/', $message, $m); list(, $target, $drink, $action) = array_pad($m, 4, null); $drink = trim($drink); if ($type == 'tea' && strtolower(substr($drink, -3)) == 'tea') { $drink = trim(substr($drink, 0, -3)); } $action = trim($action); $target = $this->resolveTarget($target); if (!Phergie_Plugin_ServerInfo::isIn($target, $this->event->getSource())) { return; } if (!empty($drink)) { $drink = implode(' ', array_map('ucfirst', explode(' ', $drink))); if ($search = $this->getSearchRecord($type, $drink)) { $drink = $search; } } else { $drink = $this->getRandomRecord($type); } if ($drink) { if ($type != 'tea') { $text = 'throws ' . $target . ' a'; if (preg_match('/^[aeoiu]/i', $drink)) { $text .= 'n'; } $text .= ' ' . $drink . ($action ? ' ' . $action : ''); } else { // One must be gentle with tea $text = 'pours ' . $target . ' a cup of ' . $drink . ' tea' . ($action ? ' ' . $action : ''); } $this->doAction($this->event->getSource(), $text . '.'); } } /** * Handles beer requests. * * @param string $target Target for the request * @return void */ public function onDoBeer($message) { $this->handleDrink('beer', $message); } /** * Alias to Beer * * @param string $target Target for the request * @return void */ public function onDoBooze($message) { $this->handleDrink('beer', $message); } /** * Handles cocktail requests. * * @param string $target Target for the request * @return void */ public function onDoCocktail($message) { $this->handleDrink('cocktail', $message); } /** * Handles coke requests. * * @param string $target Source of the request * @return void */ public function onDoCoke($message) { $this->handleDrink('coke', $message); } /** * Provides a soda alias for coke requests. * * @param string $target Target for the request * @return void */ public function onDoSoda($message) { $this->handleDrink('coke', $message); } /** * Handles tea requests. * * @param string $target Target for the request * @return void */ public function onDoTea($message) { $this->handleDrink('tea', $message); } /** * Handles pop requests. * * @param string $target Target for the request * @return void */ public function onDoPop($target) { $target = $this->resolveTarget($target); $this->doAction($this->event->getSource(), 'lays ' . $target . ' out flat.'); } }