# HG changeset patch # User Dan # Date 1226780912 18000 # Node ID 51560f5414a602be2f2978480f586638db1c8515 # Parent e5e4ba531f415016e3a06c905c9ae1d4815e9148# Parent 07be890ef627b383150c63b8ad2ef1596ae76903 Re-merge snippets fix diff -r 07be890ef627 -r 51560f5414a6 database.sql --- a/database.sql Sat Oct 25 20:24:31 2008 +0000 +++ b/database.sql Sat Nov 15 15:28:32 2008 -0500 @@ -17,3 +17,47 @@ PRIMARY KEY (id) ); +-- +-- NEW - Late October '08 modifications +-- + +CREATE TABLE stats_messages ( + message_id int(21) NOT NULL auto_increment, + channel varchar(30) NOT NULL DEFAULT '', + nick varchar(40) NOT NULL DEFAULT '', + time int(11) NOT NULL DEFAULT 0, + PRIMARY KEY ( message_id ) +); + +CREATE TABLE stats_anon ( + nick varchar(40) NOT NULL DEFAULT '', + PRIMARY KEY ( nick ) +); + +CREATE TABLE stats_count_cache ( + cache_id int(21) NOT NULL auto_increment, + channel varchar(30) NOT NULL DEFAULT '', + time_min int(11) NOT NULL DEFAULT 0, + time_max int(11) NOT NULL DEFAULT 0, + message_count int(11) NOT NULL DEFAULT 0, + PRIMARY KEY ( cache_id ) +); + +-- +-- ADDED November 15 2008 +-- + +CREATE TABLE ip_log ( + entry_id int(21) NOT NULL auto_increment, + nick varchar(40) NOT NULL, + basenick varchar(40) NOT NULL, + ip varchar(39) NOT NULL, + hostname varchar(80) NOT NULL, + channel varchar(20) NOT NULL, + time int(12) unsigned NOT NULL DEFAULT 0, + PRIMARY KEY ( entry_id ) +); + +-- Also added Nov. 15 (this DRAMATICALLY speeds things up) +CREATE INDEX stats_time_idx USING BTREE ON stats_messages (time); + diff -r 07be890ef627 -r 51560f5414a6 enanobot.php --- a/enanobot.php Sat Oct 25 20:24:31 2008 +0000 +++ b/enanobot.php Sat Nov 15 15:28:32 2008 -0500 @@ -74,6 +74,7 @@ require('libirc.php'); require('hooks.php'); require('config.php'); +require('database.php'); @ini_set('display_errors', 'on'); error_reporting(E_ALL); @@ -88,62 +89,9 @@ } } -$mysql_conn = false; -$doctor = array(); +mysql_reconnect(); -function mysql_reconnect() -{ - global $mysql_conn, $mysql_host, $mysql_user, $mysql_pass, $mysql_dbname; - if ( $mysql_conn ) - { - @mysql_close($mysql_conn); - if ( defined('LIBIRC_DEBUG') ) - { - echo "< > Reconnecting to MySQL\n"; - } - } - // connect to MySQL - $mysql_conn = @mysql_connect($mysql_host, $mysql_user, $mysql_pass); - if ( !$mysql_conn ) - { - $m_e = mysql_error(); - echo "Error connecting to MySQL: $m_e\n"; - exit(1); - } - $q = @mysql_query("USE `$mysql_dbname`;", $mysql_conn); - if ( !$q ) - { - $m_e = mysql_error(); - echo "Error selecting database: $m_e\n"; - exit(1); - } -} - -function eb_mysql_query($sql, $conn = false) -{ - global $mysql_conn, $irc; - $m_et = false; - while ( true ) - { - $q = mysql_query($sql, $mysql_conn); - if ( !$q ) - { - $m_e = mysql_error(); - if ( strpos($m_e, 'gone away') && !$m_et ) - { - mysql_reconnect(); - continue; - } - $m_et = true; - $irc->close("MySQL query error: $m_e"); - exit(1); - } - break; - } - return $q; -} - -mysql_reconnect(); +eval(eb_fetch_hook('startup_early')); $libirc_channels = array(); @@ -226,10 +174,11 @@ } $part_cache = array(); } - else if ( in_array($message['nick'], $privileged_list) && $message['message'] == 'Shutdown' && $message['action'] == 'PRIVMSG' ) + else if ( in_array($message['nick'], $privileged_list) && preg_match('/^Shutdown(?: (.+))$/i', $message['message'], $match) && $message['action'] == 'PRIVMSG' ) { $GLOBALS['_shutdown'] = true; - $irc->close("Remote bot shutdown ordered by {$message['nick']}", true); + $quitmessage = empty($match[1]) ? "Remote bot shutdown ordered by {$message['nick']}" : $match[1]; + $irc->close($quitmessage, true); return 'BREAK'; } else if ( $message['action'] == 'PRIVMSG' ) diff -r 07be890ef627 -r 51560f5414a6 graphs.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graphs.php Sat Nov 15 15:28:32 2008 -0500 @@ -0,0 +1,1051 @@ + 7, 'b' => 3, 'c' => 6, 'd' => 0, 'e' => 2); + /** + * GraphMaker::bar_padding + * Padding of bars + */ + var $bar_padding = 5; + /** + * GraphMaker::bar_bordercolor + * Border color of bars + */ + var $bar_bordercolor = array(39, 78, 120); + /** + * GraphMaker::bar_bgcolor + * Background color of bars + */ + var $bar_bgcolor = array(69, 129, 194); + //--------------------------------------------- + /** + * GraphMaker::graph_areaheight + * Height of graphic area + */ + var $graph_areaheight = 100; + /** + * GraphMaker::graph_padding + * Paddings of graph + */ + var $graph_padding = array('left' => 50, 'top' => 20, 'right' => 20, 'bottom' => 20); + /** + * GraphMaker::graph_title + * Title text of graph + */ + var $graph_title = ""; + /** + * GraphMaker::graph_bgcolor + * Background color of graph + */ + var $graph_bgcolor = array(255, 255, 255); + /** + * GraphMaker::graph_bgtransparent + * Boolean for background transparency + */ + var $graph_bgtransparent = 0; + /** + * GraphMaker::graph_transparencylevel + * Transparency level (0=opaque, 127=transparent) + */ + var $graph_transparencylevel = 0; + /** + * GraphMaker::graph_borderwidth + * Width of graph border + */ + var $graph_borderwidth = 1; + /** + * GraphMaker::graph_bordercolor + * Border color of graph + */ + var $graph_bordercolor = array(218, 218, 239); + /** + * GraphMaker::graph_titlecolor + * Color of title text of graph + */ + var $graph_titlecolor = array(99, 88, 78); + //--------------------------------------------- + /** + * GraphMaker::axis_step + * Scale step of axis + */ + var $axis_step = 2; + /** + * GraphMaker::axis_bordercolor + * Border color of axis + */ + var $axis_bordercolor = array(99, 88, 78); + /** + * GraphMaker::axis_bgcolor + * Background color of axis + */ + var $axis_bgcolor = array(152, 137, 124); + + /**************************************************************** + GRAPH + ****************************************************************/ + + /** + * GraphMaker::SetGraphAreaHeight() + * Sets graph height (not counting top and bottom margins) + **/ + function SetGraphAreaHeight($height) { + if ($height > 0) $this->graph_areaheight = $height; + } + + /** + * GraphMaker::SetGraphPadding() + * Sets graph padding (margins) + **/ + function SetGraphPadding($left, $top, $right, $bottom) { + $this->graph_padding = array('left' => (int) $left, + 'top' => (int) $top, + 'right' => (int) $right, + 'bottom' => (int) $bottom); + } + + /** + * GraphMaker::SetGraphTitle() + * Set title text + **/ + function SetGraphTitle($title) { + $this->graph_title = $title; + } + + /** + * GraphMaker::SetGraphBorderColor() + * Sets border color for graph + **/ + function SetGraphBorderColor($red, $green, $blue) { + $this->graph_bordercolor = array($red, $green, $blue); + } + + /** + * GraphMaker::SetGraphBorderWidth() + * Set width of border. 0 disables border + **/ + function SetGraphBorderWidth($width = 0) { + $this->graph_borderwidth = $width; + } + + /** + * GraphMaker::SetGraphBackgroundColor() + * Sets background color for graph + **/ + function SetGraphBackgroundColor($red, $green, $blue) { + $this->graph_bgcolor = array($red, $green, $blue); + } + + /** + * GraphMaker::SetGraphBackgroundTransparent() + * Sets background color for graph (and set it transparent) + **/ + function SetGraphBackgroundTransparent($red, $green, $blue, $addtransparency = 1) { + $this->graph_bgcolor = array($red, $green, $blue); + $this->graph_bgtransparent = ($addtransparency ? 1 : 0); + } + + /** + * GraphMaker::SetGraphTitleColor() + * Sets title color for graph + **/ + function SetGraphTitleColor($red, $green, $blue) { + $this->graph_titlecolor = array($red, $green, $blue); + } + + /** + * GraphMaker::SetGraphTransparency() + * Sets transparency for graph + **/ + function SetGraphTransparency($percent) { + if ($percent < 0) $percent = 0; + elseif ($percent > 100) $percent = 127; + else $percent = $percent * 1.27; + $this->graph_transparencylevel = $percent; + } + + /**************************************************************** + BAR + ****************************************************************/ + + /** + * GraphMaker::SetBarBorderColor() + * Sets border color for bars + **/ + function SetBarBorderColor($red, $green, $blue) { + $this->bar_bordercolor = array($red, $green, $blue); + } + + /** + * GraphMaker::SetBarBackgroundColor() + * Sets background color for bars + **/ + function SetBarBackgroundColor($red, $green, $blue) { + $this->bar_bgcolor = array($red, $green, $blue); + } + + /** + * GraphMaker::SetBarData() + * Sets data of graph (parameter should be an array with key + * being the name of the bar and the value the value of the bar. + **/ + function SetBarData($data) { + if (is_array($data)) $this->bar_data = $data; + } + + /** + * GraphMaker::SetBarDimensions() + * Sets with and height of each bar + **/ + function SetBarDimensions($width, $height) { + if ($width > 0) $this->bar_width = $width; + if ($height > 0) $this->bar_height = $height; + } + + /** + * GraphMaker::SetBarPadding() + * Sets padding (border) around each bar + **/ + function SetBarPadding($padding) { + if ($padding > 0) $this->bar_padding = $padding; + } + + /**************************************************************** + AXIS + ****************************************************************/ + + /** + * GraphMaker::SetAxisBorderColor() + * Sets border color for axis + **/ + function SetAxisBorderColor($red, $green, $blue) { + $this->axis_bordercolor = array($red, $green, $blue); + } + + /** + * GraphMaker::SetAxisBackgroundColor() + * Sets background color for axis + **/ + function SetAxisBackgroundColor($red, $green, $blue) { + $this->axis_bgcolor = array($red, $green, $blue); + } + + /** + * GraphMaker::SetAxisStep() + * Sets axis scale step + **/ + function SetAxisStep($step) { + if ($step > 0) $this->axis_step = $step; + } + + /** + * GraphMaker::GetFinalGraphDimensions() + * From the values already setted, it calculates image + * width and height + **/ + function GetFinalGraphDimensions() { + $w = $this->graph_padding['left'] + + (count($this->bar_data) * ($this->bar_width + ($this->bar_padding * 2))) + + $this->graph_padding['right']; + $h = $this->graph_padding['top'] + + $this->graph_areaheight + + $this->graph_padding['bottom']; + return array($w, $h); + } + + /** + * GraphMaker::LoadGraph() + * Loads definitions from a file + **/ + function LoadGraph($path) { + if (($fp = @fopen($path, "r")) !== false) { + $content = ""; + while (!feof($fp)) { // I do not use filesize() here + $content .= fread($fp, 4096); // because of remote files. If + } // there is no problem with them + fclose($fp); // please let me know + $this->__LoadGraphDefinitions($content); + return true; + } else return false; + } + + /** + * GraphMaker::DrawGraph() + * Draw all the graph: bg, axis, bars, text.. and output it + * Optional file parameter turns output to file, and bool on success + **/ + function DrawGraph($file = "") { + list($w, $h) = $this->GetFinalGraphDimensions(); + $this->graph_width = $w; + $this->graph_height = $h; + + $this->im = imagecreatetruecolor($w, $h); + if ($this->graph_transparencylevel) { + imagealphablending($this->im, true); + } + + $this->__PaintBackground(); + $this->__DrawAxis(); + + $p = 0; + foreach ($this->bar_data as $name => $value) { + $p++; + $this->__DrawBarText($p, $name); + $this->__DrawBar($p, $value); + } + + if (strlen($this->graph_title)) { + $this->__AllocateColor("im_graph_titlecolor", + $this->graph_titlecolor, + $this->graph_transparencylevel); + $this->__DrawText($this->graph_title, + floor($this->graph_width / 2), + $this->graph_borderwidth + 2, + $this->im_graph_titlecolor, + 2, + 1); + } + + if (strlen($file)) { + $ret = imagepng($this->im, $file); + } else { + header('Content-Type: image/png'); + imagepng($this->im); + $ret = true; + } + imagedestroy($this->im); + return $ret; + } + + /** + * GraphMaker::PaintBackground() + * Draw all the graph: bg, axis, bars, text.. and output it + * Optional file parameter turns output to file, and bool on success + **/ + function __PaintBackground() { + $this->__AllocateColor("im_graph_bgcolor", + $this->graph_bgcolor, + 0); + imagefilledrectangle($this->im, + 0, + 0, + $this->graph_width, + $this->graph_height, + $this->im_graph_bgcolor); + if ($this->graph_bgtransparent) { + imagecolortransparent($this->im, $this->im_graph_bgcolor); + } + if ($this->graph_borderwidth) { + $this->__AllocateColor("im_graph_bordercolor", + $this->graph_bordercolor, + $this->graph_transparencylevel); + for ($i = 0; $i < $this->graph_borderwidth; $i++) { + imagerectangle($this->im, + $i, + $i, + $this->graph_width - 1 - $i, + $this->graph_height - 1 - $i, + $this->im_graph_bordercolor); + } + } + } + + /** + * GraphMaker::__DrawAxis() + * Draws all the axis stuff (and scale steps) + **/ + function __DrawAxis() { + $this->__AllocateColor("im_axis_bordercolor", + $this->axis_bordercolor, + $this->graph_transparencylevel); + $this->__AllocateColor("im_axis_bgcolor", + $this->axis_bgcolor, + $this->graph_transparencylevel); + $this->__DrawPolygon($this->graph_padding['left'], $this->graph_height - $this->graph_padding['bottom'], + $this->graph_padding['left'], $this->graph_padding['top'], + $this->graph_padding['left'] + $this->bar_height - 1, $this->graph_padding['top'] - $this->bar_height + 1, + $this->graph_padding['left'] + $this->bar_height - 1, $this->graph_height - $this->graph_padding['bottom'] - $this->bar_height + 1, + $this->im_axis_bgcolor, true); + $this->__DrawPolygon($this->graph_padding['left'], $this->graph_height - $this->graph_padding['bottom'], + $this->graph_padding['left'], $this->graph_padding['top'], + $this->graph_padding['left'] + $this->bar_height - 1, $this->graph_padding['top'] - $this->bar_height + 1, + $this->graph_padding['left'] + $this->bar_height - 1, $this->graph_height - $this->graph_padding['bottom'] - $this->bar_height + 1, + $this->im_axis_bordercolor); + + $this->__DrawPolygon($this->graph_padding['left'], $this->graph_height - $this->graph_padding['bottom'], + $this->graph_padding['left'] + $this->bar_height - 1, $this->graph_height - $this->graph_padding['bottom'] - $this->bar_height + 1, + $this->graph_width - $this->graph_padding['right'] + $this->bar_height - 1, $this->graph_height - $this->graph_padding['bottom'] - $this->bar_height + 1, + $this->graph_width - $this->graph_padding['right'], $this->graph_height - $this->graph_padding['bottom'], + $this->im_axis_bgcolor, true); + $this->__DrawPolygon($this->graph_padding['left'], $this->graph_height - $this->graph_padding['bottom'], + $this->graph_padding['left'] + $this->bar_height - 1, $this->graph_height - $this->graph_padding['bottom'] - $this->bar_height + 1, + $this->graph_width - $this->graph_padding['right'] + $this->bar_height - 1, $this->graph_height - $this->graph_padding['bottom'] - $this->bar_height + 1, + $this->graph_width - $this->graph_padding['right'], $this->graph_height - $this->graph_padding['bottom'], + $this->im_axis_bordercolor); + + // draw lines that separate bars + $total_bars = count($this->bar_data); + for ($i = 1; $i < $total_bars; $i++) { + $offset = $this->graph_padding['left'] + + (($this->bar_width + ($this->bar_padding * 2)) * $i); + imageline($this->im, + $offset, + $this->graph_height - $this->graph_padding['bottom'], + $offset + $this->bar_height - 1, + $this->graph_height - $this->graph_padding['bottom'] - $this->bar_height + 1, + $this->im_axis_bordercolor); + } + + // draw scale steps + $max_value = $this->__GetMaxGraphValue(); + if (($max_value % 10) > 0) { + $max_value = $max_value + (10 - ($max_value % 10)); + } + $this->axis_max = $max_value; + $y = 0; + $style = array($this->im_axis_bordercolor, $this->im_graph_bgcolor); + imagesetstyle($this->im, $style); + while ($y <= $max_value) { + if ($max_value == 0) { $max_value=1; } // corrected by Marcelo Trenkenchu + $offset = floor($this->graph_height - $this->graph_padding['bottom'] - + ($y * $this->graph_areaheight / $max_value)); + imageline($this->im, + $this->graph_padding['left'], + $offset, + $this->graph_padding['left'] + $this->bar_height - 1, + $offset - $this->bar_height + 1, + $this->im_axis_bordercolor); + $this->__DrawText($y, + $this->graph_padding['left'], + $offset, + $this->im_axis_bordercolor, + 1, + 2, + 1); + // gridline + if ($y > 0) { + imageline($this->im, + $this->graph_padding['left'] + $this->bar_height, + $offset - $this->bar_height + 1, + $this->graph_width - $this->graph_padding['right'] + $this->bar_height - 1, + $offset - $this->bar_height + 1, + IMG_COLOR_STYLED); + } + $y += $this->axis_step; + } + + imageline($this->im, + $this->graph_width - $this->graph_padding['right'] + $this->bar_height - 1, + $this->graph_padding['top'] - $this->bar_height + 1, + $this->graph_width - $this->graph_padding['right'] + $this->bar_height - 1, + $this->graph_height - $this->graph_padding['bottom'] - $this->bar_height, + IMG_COLOR_STYLED); + } + + /** + * GraphMaker::__DrawText() + * Draws text on image with color, size and alignment options + **/ + function __DrawText($text, $x, $y, $color, $size = 1, $align = 0, $valign = 0) { + /* + * Align: 0=left | 1=center | 2=right + */ + if ($align == 1) $x -= floor(strlen($text) * imagefontwidth($size) / 2); + elseif ($align == 2) $x -= (strlen($text) * imagefontwidth($size)); + if ($valign == 1) $y -= floor(imagefontheight($size) / 2); + elseif ($valign == 2) $y -= imagefontheight($size); + imagestring($this->im, + $size, + $x, + $y, + $text, + $color); + } + + /** + * GraphMaker::__GetMaxGraphValue() + * Returns max bar value + **/ + function __GetMaxGraphValue() { + $max_value = 0; + foreach ($this->bar_data as $name => $value) { + if ($value > $max_value) $max_value = $value; + } + return $max_value; + } + + /** + * GraphMaker::__DrawBarText() + * Determines top and left to draw text to a choosen bar + **/ + function __DrawBarText($bar, $text) { + $this->__DrawText($text, + $this->graph_padding['left'] + (($this->bar_width + ($this->bar_padding * 2)) * ($bar - 0.5)), + $this->graph_height - $this->graph_padding['bottom'] + 1, + $this->axis_bordercolor, + 1, + 1); + } + + /** + * GraphMaker::__DrawBar() + * Draws a choosen bar with it's value + **/ + function __DrawBar($bar, $value) { + $x = $this->graph_padding['left'] + + (($this->bar_width + ($this->bar_padding * 2)) * ($bar - 1)) + + $this->bar_padding; + if ($this->axis_max == 0) { $this->axis_max = 1; } // corrected by Marcelo Trenkenchu + $y = $value * $this->graph_areaheight / $this->axis_max; + $this->____DrawBar($x, + $this->graph_height - $this->graph_padding['bottom'] - $y, + $x + $this->bar_width, + $this->graph_height - $this->graph_padding['bottom']); + } + + /** + * GraphMaker::____DrawBar() + * Draws the actual rectangles that form a bar + **/ + function ____DrawBar($x1, $y1, $x2, $y2) { + $this->__AllocateColor("im_bar_bordercolor", + $this->bar_bordercolor, + $this->graph_transparencylevel); + $this->__AllocateColor("im_bar_bgcolor", + $this->bar_bgcolor, + $this->graph_transparencylevel); + $this->__DrawPolygon($x1, $y1, + $x2, $y1, + $x2, $y2, + $x1, $y2, + $this->im_bar_bgcolor, true); + $this->__DrawPolygon($x1, $y1, + $x2, $y1, + $x2, $y2, + $x1, $y2, + $this->im_bar_bordercolor); + $this->__DrawPolygon($x1, $y1, + $x2, $y1, + $x2 + $this->bar_height - 1, $y1 - $this->bar_height + 1, + $x1 + $this->bar_height - 1, $y1 - $this->bar_height + 1, + $this->im_bar_bgcolor, true); + $this->__DrawPolygon($x1, $y1, + $x2, $y1, + $x2 + $this->bar_height - 1, $y1 - $this->bar_height + 1, + $x1 + $this->bar_height - 1, $y1 - $this->bar_height + 1, + $this->im_bar_bordercolor); + $this->__DrawPolygon($x2, $y2, + $x2, $y1, + $x2 + $this->bar_height - 1, $y1 - $this->bar_height + 1, + $x2 + $this->bar_height - 1, $y2 - $this->bar_height + 1, + $this->im_bar_bgcolor, true); + $this->__DrawPolygon($x2, $y2, + $x2, $y1, + $x2 + $this->bar_height - 1, $y1 - $this->bar_height + 1, + $x2 + $this->bar_height - 1, $y2 - $this->bar_height + 1, + $this->im_bar_bordercolor); + } + + /** + * GraphMaker::__DrawPolygon() + * Draws a (filled) (ir)regular polygon + **/ + function __DrawPolygon($x1, $y1, $x2, $y2, $x3, $y3, $x4, $y4, $color, $filled = false) { + if ($filled) { + imagefilledpolygon($this->im, array($x1, $y1, $x2, $y2, $x3, $y3, $x4, $y4), 4, $color); + } else { + imagepolygon($this->im, array($x1, $y1, $x2, $y2, $x3, $y3, $x4, $y4), 4, $color); + } + } + + /** + * GraphMaker::__LoadGraphDefinitions() + * Loads definitions to a graph from text lines (normaly + * they come from a file). This function is called by + * GraphMaker::LoadGraph() + **/ + function __LoadGraphDefinitions($text) { + $text = preg_split("/\r?\n/", $text); + $data = array(); + $section = ''; + for ($i = 0; $i < count($text); $i++) { + if (preg_match("/^\s*#/", $text[$i])) { + //ignore.. it's just a comment + } elseif (preg_match("/^\s*\}\s*/", $text[$i])) { + $section = ''; + } elseif (preg_match("/^\s*(\w+)\s*\{\s*$/", $text[$i], $r)) { + $section = $r[1]; + } else { + $p = strpos($text[$i], "="); + if ($p !== false) { + $data[$section][trim(substr($text[$i], 0, $p))] = trim(substr($text[$i], $p + 1)); + } + } + } + if (is_array($data['graph'])) { + $this->__LoadGraphValues($data['graph']); + } + if (is_array($data['bar'])) { + $this->__LoadBarValues($data['bar']); + } + if (is_array($data['axis'])) { + $this->__LoadAxisValues($data['axis']); + } + if (is_array($data['data'])) { + $this->bar_data = $data['data']; + } + } + + /** + * GraphMaker::__LoadGraphValues() + * Loads definitions to main graph settings + **/ + function __LoadGraphValues($data) { + foreach ($data as $name => $value) { + $name = strtolower($name); + switch ($name) { + case 'background-color': + $this->__SetColorToValue("graph_bgcolor", $value); + break; + case 'border-color': + $this->__SetColorToValue("graph_bordercolor", $value); + break; + case 'title-color': + $this->__SetColorToValue("graph_titlecolor", $value); + break; + case 'background-transparent': + $this->graph_bgtransparent = ($value == 1 || $value == 'yes' ? 1 : 0); + break; + case 'transparency': + $this->SetGraphTransparency(str_replace('%', '', $value)); + break; + case 'title': + $this->graph_title = $value; + break; + case 'border-width': + $this->graph_borderwidth = (int) $value; + break; + case 'area-height': + $this->graph_areaheight = (int) $value; + break; + default: + if (substr($name, 0, 8) == 'padding-' && strlen($name) > 8) { + $this->graph_padding[substr($name, 8)] = $value; + } + } + } + } + + /** + * GraphMaker::__LoadBarValues() + * Loads definitions to bar settings + **/ + function __LoadBarValues($data) { + foreach ($data as $name => $value) { + $name = strtolower($name); + switch ($name) { + case 'background-color': + $this->__SetColorToValue("bar_bgcolor", $value); + break; + case 'border-color': + $this->__SetColorToValue("bar_bordercolor", $value); + break; + case 'padding': + $this->bar_padding = $value; + break; + case 'width': + $this->bar_width = (int) $value; + break; + case 'height': + $this->bar_height = (int) $value; + break; + } + } + } + + /** + * GraphMaker::__LoadAxisValues() + * Loads definitions to axis settings + **/ + function __LoadAxisValues($data) { + foreach ($data as $name => $value) { + switch (strtolower($name)) { + case 'step': + $this->SetAxisStep($value); + break; + case 'background-color': + $this->__SetColorToValue("axis_bgcolor", $value); + break; + case 'border-color': + $this->__SetColorToValue("axis_bordercolor", $value); + } + } + } + + /** + * GraphMaker::__SetColorToValue() + * Sets a color (rgb or in html format) to a variable + **/ + function __SetColorToValue($varname, $color) { + if ($color[0] == "#") { // if it's hex (html format), change to rgb array + if (strlen($color) == 4) { + // if only 3 hex values (I assume it's a shade of grey: #ddd) + $color .= substr($color, -3); + } + $color = array(hexdec($color[1].$color[2]), + hexdec($color[3].$color[4]), + hexdec($color[5].$color[6])); + } + $this->$varname = $color; + } + + function __AllocateColor($varname, $color, $alpha) { + $this->$varname = imagecolorallocatealpha($this->im, + $color[0], + $color[1], + $color[2], + $alpha); + } +} + +// Graph Generator for PHP +// Originally located at http://szewo.com/php/graph, but link was broken, so this file was retrieved from: +// http://web.archive.org/web/20030130065944/szewo.com/php/graph/graph.class.php3.txt +// License unknown, however sources on the web have shown this to be either GPL or public domain. + +// At this point this class has been very nearly rewritten for Enano. + +class GraphMaker_compat { + var $_values; + var $_ShowLabels; + var $_ShowCounts; + var $_ShowCountsMode; + + var $_BarWidth; + var $_GraphWidth; + var $_BarImg; + var $_BarBorderWidth; + var $_BarBorderColor; + var $_BarBackgroundColor; + var $_RowSortMode; + var $_TDClassHead; + var $_TDClassLabel; + var $_TDClassCount; + var $_GraphTitle; + + function __construct() { + $this->_values = array(); + $this->_ShowLabels = true; + $this->_BarWidth = 32; + $this->_GraphWidth = 360; + $this->_BarImg = scriptPath . "/images/graphbit.png"; + $this->_BarBorderWidth = 0; + $this->_BarBorderColor = "red"; + $this->_ShowCountsMode = 2; + $this->_RowSortMode = 1; + $this->_TDClassHead = "graph-title"; + $this->_TDClassLabel = "graph-label"; + $this->_TDClassCount = "graph-count"; + $this->_GraphTitle="Graph title"; + $this->_BarBackgroundColor = "#456798"; + } + + function GraphMaker_compat() { + $this->__construct(); + } + + function SetBarBorderWidth($width) { + $this->_BarBorderWidth = $width; + } + function SetBorderColor($color) { + $this->_BarBorderColor = $color; + } + + function SetBarBackgroundColor($color) + { + $this->_BarBackgroundColor = $color; + } + +// mode = 1 labels asc, 2 label desc + function SetSortMode($mode) { + switch ($mode) { + case 1: + asort($this->_values); + break; + case 2: + arsort($this->_values); + break; + default: + break; + } + + } + + function AddValue($labelName, $theValue) { + array_push($this->_values, array("label" => $labelName, "value" => $theValue)); + } + + function SetBarData($data) + { + foreach ( $data as $name => $value ) + { + $this->AddValue($name, $value); + } + } + function DrawGraph() + { + $this->BarGraphVert(); + } + function SetBarWidth($width) + { + $this->_BarWidth = $width; + } + function SetBarImg($img) + { + $this->_BarImg = $img; + } + function SetShowLabels($lables) + { + $this->_ShowLabels = $labels; + } + function SetGraphWidth($width) + { + $this->_GraphWidth = $width; + } + function SetGraphTitle($title) + { + $this->_GraphTitle = $title; + } + //mode = percentage or counts + function SetShowCountsMode($mode) + { + $this->_ShowCountsMode = $mode; + } + //mode = none(0) label(1) or count(2) + function SetRowSortMode($sortmode) + { + $this->_RowSortMode = $sortmode; + } + + function SetTDClassHead($class) + { + $this->_TDClassHead = $class; + } + function SetTDClassLabel($class) + { + $this->_TDClassLabel = $class; + } + function SetTDClassCount($class) + { + $this->_TDClassCount = $class; + } + function GetMaxVal() + { + $maxval = 0; + foreach ( $this->_values as $value ) + { + if ( $maxval < $value["value"] ) + { + $maxval = $value["value"]; + } + } + return $maxval; + } + function BarGraphVert() + { + $maxval = $this->GetMaxVal(); + foreach($this->_values as $value) + { + $sumval += $value["value"]; + } + + $this->SetSortMode($this->_RowSortMode); + + echo "\n\n
\n\n "; + + if ( strlen($this->_GraphTitle) > 0 ) + { + echo "\n \n \n "; + } + + echo "\n "; + $css_class = 'row1'; + + foreach($this->_values as $value) + { + $css_class = ( $css_class == 'row1' ) ? 'row3' : 'row1'; + echo " \n "; + } + echo "\n "; + if ( $this->_ShowCountsMode > 0 ) + { + $css_class = 'row1'; + echo "\n "; + foreach($this->_values as $value) + { + $css_class = ( $css_class == 'row1' ) ? 'row3' : 'row1'; + switch ($this->_ShowCountsMode) + { + case 1: + $count = round ( 100 * $value["value"] / $sumval ) . "%"; + break; + case 2: + $count = $value["value"]; + break; + default: + break; + } + echo " \n "; + } + echo "\n"; + } + + if ($this->_ShowLabels) + { + $css_class = 'row1'; + echo " \n "; + foreach($this->_values as $value) + { + $css_class = ( $css_class == 'row1' ) ? 'row3' : 'row1'; + echo " \n "; + } + echo "\n"; + } + + echo "
_values)."\" class=\"".$this->_TDClassHead."\">".$this->_GraphTitle."
\n "; + $width = $this->_BarWidth; + $height = ceil( $value["value"] * $this->_GraphWidth / $maxval ); + + echo "
_BarBackgroundColor}; border: ".$this->_BarBorderWidth."px solid ".$this->_BarBorderColor."\">\n "; + echo "
\n "; + + // echo "_BarImg."\" height=\"$width\" width=\"$height\" "; + // echo " style=\"border: ".$this->_BarBorderWidth."px solid ".$this->_BarBorderColor."\""; + // echo ">"; + + echo "
_TDClassCount."\">$count
_TDClassLabel."\""; + echo ">".$value["label"]."
"; + } + + function BarGraphHoriz() + { + $maxval = $this->GetMaxVal(); + + foreach($this->_values as $value) + { + $sumval += $value["value"]; + } + + $this->SetSortMode($this->_RowSortMode); + + echo ""; + + if ( strlen($this->_GraphTitle) > 0 ) + { + echo ""; + } + foreach($this->_values as $value) + { + if ($this->_ShowLabels) + { + echo ""; + echo ""; + } + echo ""; + if ( $this->_ShowCountsMode > 0 ) + { + switch ($this->_ShowCountsMode) + { + case 1: + $count = round(100 * $value["value"] / $sumval )."%"; + break; + case 2: + $count = $value["value"]; + break; /* Exit the switch and the while. */ + default: + break; + } + echo ""; + } + echo ""; + } + echo "
_ShowCountsMode > 0 ) + { + echo " colspan=\"2\""; + } + echo " class=\"".$this->_TDClassHead."\">".$this->_GraphTitle."
_TDClassLabel."\""; + if ( $this->_ShowCountsMode > 0 ) + { + echo " colspan=\"2\""; + } + echo ">".$value["label"]."
_TDClassCount."\">$count"; + $height = $this->_BarWidth; + $width = ceil( $value["value"] * $this->_GraphWidth / $maxval ); + echo "
_BarBorderWidth."px solid ".$this->_BarBorderColor."\">\n "; + echo "
\n "; + //echo "_BarImg."\" height=$height width=$width "; + //echo " style=\"border: ".$this->_BarBorderWidth."px solid ".$this->_BarBorderColor."\""; + //echo ">"; + echo "
"; + } + /** + * Dummy functions for compatibility with the GD version of the class + */ + + function SetGraphPadding($a, $b, $c, $d) + { + return true; + } + function SetBarPadding($a) + { + return true; + } + function SetAxisStep($a) + { + return true; + } + function SetGraphBackgroundTransparent($r, $g, $b, $a) + { + return true; + } + function SetGraphTransparency($a) + { + return true; + } + function SetGraphAreaHeight($a) + { + return true; + } +} + + diff -r 07be890ef627 -r 51560f5414a6 htdocs/24hours.php --- a/htdocs/24hours.php Sat Oct 25 20:24:31 2008 +0000 +++ b/htdocs/24hours.php Sat Nov 15 15:28:32 2008 -0500 @@ -4,9 +4,9 @@ require('../graphs.php'); require('../timezone.php'); -$first_channel = array_keys($stats_data['messages']); -$first_channel = $first_channel[0]; -$channel = ( isset($_REQUEST['channel']) && isset($stats_data['messages'][$_REQUEST['channel']]) ) ? $_REQUEST['channel'] : $first_channel; +$channel_list = stats_channel_list(); +$first_channel = $channel_list[0]; +$channel = ( isset($_REQUEST['channel']) && in_array($_REQUEST['channel'], $channel_list) ) ? $_REQUEST['channel'] : $first_channel; // generate the data // we're doing this by absolute hours, not by strictly "24 hours ago", e.g. on-the-hour stats diff -r 07be890ef627 -r 51560f5414a6 htdocs/datafile.php --- a/htdocs/datafile.php Sat Oct 25 20:24:31 2008 +0000 +++ b/htdocs/datafile.php Sat Nov 15 15:28:32 2008 -0500 @@ -1,6 +1,16 @@ @@ -26,7 +25,7 @@ change]
'; - echo 'The time now is ' . date('H:i:s') . '.
Statistics last written to disk at ' . date('H:i:s', stats_last_updated()) . '.
'; + echo 'The time now is ' . date('H:i:s') . '.
Statistics now updated constantly (see news)
'; ?>

diff -r 07be890ef627 -r 51560f5414a6 htdocs/news.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/htdocs/news.php Sat Nov 15 15:28:32 2008 -0500 @@ -0,0 +1,24 @@ + + + <?php echo $nick; ?> - updates + + + +

Updates and changes

+

I've been updating recently with some really cool enhancements to the back-end. This is more technical stuff + so you might want to read on only if you're a geek.

+

only stores info with MySQL now. All the stats go into a MySQL table that had an initial size of over 360,000 + records after the import of the existing flat file database. This means that while querying can be slower (things aren't split up + like they were with the flatfile DB) it's a more portable programming technique and it means it can be easily expanded in the + future to include more data. The table's indexed so it should be decently fast.

+

In addition, smarter functionality is being included, plus a few bugs here and there have been fixed.

+ + diff -r 07be890ef627 -r 51560f5414a6 htdocs/privacy.php --- a/htdocs/privacy.php Sat Oct 25 20:24:31 2008 +0000 +++ b/htdocs/privacy.php Sat Nov 15 15:28:32 2008 -0500 @@ -13,20 +13,24 @@

Privacy information

-

is designed to collect IRC statistics. It does this by recording raw data and then letting the frontend (index.php and a - few backend functions in stats-fe.php) look at the data and draw graphs and measurements based on it.

+

is designed to collect IRC statistics. It does this by recording raw data and then letting the frontend (index.php and the + backend access abstraction in stats-fe.php) look at the data and draw graphs and measurements based on it.

The only information collects is

+

In addition, knows whether users currently have permissions such as operator and voice, but this information isn't logged (it's used to determine who can do what). This means that the web interface never knows for sure who is in the channel.

also gives you the ability to disable recording statistics about you. To clear all your past statistics, type in any channel:

!deluser

-

You can also prevent yourself from being logged in the future with:

+

(Moderators can also type:

+

!deluser | SomeNick

+

to remove statistics for a flooder or spammer)

+

You can prevent yourself from being logged in the future with:

/msg anonymize

+

You'll be asked if you want to anonymize your past statistics as well.

Remove yourself from the anonymization list with:

/msg denonymize

-

Want to know more about the numbers collects? Download 's data file yourself (in JSON format).

+

Want to know more about the numbers collects? Download a dump of 's database yourself.

diff -r 07be890ef627 -r 51560f5414a6 libirc.php --- a/libirc.php Sat Oct 25 20:24:31 2008 +0000 +++ b/libirc.php Sat Nov 15 15:28:32 2008 -0500 @@ -249,6 +249,7 @@ private function handle_privmsg($message) { $message = self::parse_message($message); + $message['message'] = preg_replace('/^(.+?):/', '', $message['message']); $ph = $this->privmsg_handler; if ( @function_exists($ph) ) return @call_user_func($ph, $message); diff -r 07be890ef627 -r 51560f5414a6 libprogress.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libprogress.php Sat Nov 15 15:28:32 2008 -0500 @@ -0,0 +1,311 @@ +bar_left = $bar_left; + $this->bar_right = $bar_right; + $this->color_bar = $this->color_to_code($color_bar); + $this->color_empty = $this->color_to_code($color_empty); + $this->color_text = $this->color_to_code($color_text); + $this->color_emptytext = $this->color_to_code($color_emptytext); + + if ( isset($_SERVER['COLUMNS']) ) + { + $this->term_width = intval($_SERVER['COLUMNS']); + } + $this->bar_width = $this->term_width - strlen($this->bar_left) - strlen($this->bar_right); + + $this->update_text_quiet($bar_text); + } + + /** + * Updates the text on the progress bar and recalculates the position without redrawing. + * @param string Text in the bar. If omitted, blanked. + */ + + public function update_text_quiet($bar_text = '') + { + $this->bar_text = strval($bar_text); + + if ( !empty($this->bar_text) ) + { + $this->text_pos = round(( $this->bar_width / 2 ) - ( strlen($this->bar_text) / 2 )); + } + } + + /** + * Updates the text on the progress bar, recalculates the position, and redraws. + * @param string Text in the bar. If omitted, blanked. + */ + + function update_text($bar_text = '') + { + $this->update_text_quiet($bar_text); + $this->set($this->bar_pos); + } + + /** + * Starts output of the bar. + */ + + function start() + { + echo self::CARRIAGE_RETURN; + echo $this->bar_left; + } + + /** + * Closes the bar. + */ + + function end() + { + $this->set($this->bar_pos, $this->bar_width); + echo "\n"; + } + + /** + * Sets the position of the bar. + * @param int Position in %. If a second parameter is set, this is treated as a numerator with the second parameter being the denominator and that is used to calculate position. + * @param int Optional. Total number of units to allow fraction usage instead of percentage. + */ + + function set($pos, $max = 100) + { + // if our pos is higher than 100%, reduce it + if ( $pos > $max ) + $pos = $max; + + // arithmetic one-liner + // this is where we should stop showing the "full" color and instead use "empty" + $bar_pos = round($this->bar_width * ( $pos / $max )); + $this->bar_pos = 100 * ( $pos / $max ); + + // reset the cursor + echo self::CARRIAGE_RETURN . $this->bar_left; + + // print everything out + for ( $i = 0; $i < $this->bar_width; $i++ ) + { + $char = ' '; + $hide = true; + if ( !empty($this->bar_text) ) + { + // we have some text to display in the middle; see where we are. + $show_text = ( $i >= $this->text_pos && $i < ( $this->text_pos + strlen($this->bar_text) ) ); + if ( $show_text ) + { + $char = substr($this->bar_text, $i - $this->text_pos, 1); + if ( strlen($char) < 1 ) + $char = ' '; + else + $hide = false; + } + } + // determine color + if ( $i > $bar_pos ) + { + $hide ? $this->set_color_empty_hide() : $this->set_color_empty_show(); + } + else + { + $hide ? $this->set_color_full_hide() : $this->set_color_full_show(); + } + echo $char; + } + $this->set_color_reset(); + echo $this->bar_right; + } + + # + # PRIVATE METHODS + # + + function set_color_full_hide() + { + if ( $this->color_state == self::COLOR_STATE_FULL_HIDE ) + return; + $this->color_state = self::COLOR_STATE_FULL_HIDE; + + $fgcolor = 30 + $this->color_bar; + $bgcolor = $fgcolor + 10; + echo self::SHELL_ESCAPE . "[0;{$fgcolor};{$bgcolor};8m"; + } + + function set_color_full_show() + { + if ( $this->color_state == self::COLOR_STATE_FULL_SHOW ) + return; + $this->color_state = self::COLOR_STATE_FULL_SHOW; + + $fgcolor = 30 + $this->color_text; + $bgcolor = 40 + $this->color_bar; + echo self::SHELL_ESCAPE . "[0;1;{$fgcolor};{$bgcolor}m"; + } + + function set_color_empty_hide() + { + if ( $this->color_state == self::COLOR_STATE_EMPTY_HIDE ) + return; + $this->color_state = self::COLOR_STATE_EMPTY_HIDE; + + $fgcolor = 30 + $this->color_empty; + $bgcolor = $fgcolor + 10; + echo self::SHELL_ESCAPE . "[0;{$fgcolor};{$bgcolor};8m"; + } + + function set_color_empty_show() + { + if ( $this->color_state == self::COLOR_STATE_EMPTY_SHOW ) + return; + $this->color_state = self::COLOR_STATE_EMPTY_SHOW; + + $fgcolor = 30 + $this->color_emptytext; + $bgcolor = 40 + $this->color_empty; + echo self::SHELL_ESCAPE . "[0;1;{$fgcolor};{$bgcolor}m"; + } + + function set_color_reset() + { + if ( $this->color_state == self::COLOR_STATE_RESET ) + return; + $this->color_state = self::COLOR_STATE_RESET; + + echo self::SHELL_ESCAPE . "[0m"; + } + + /** + * Converts a color name to an ASCII color code. Valid color names are black, red, green, yellow, blue, magenta, cyan, and white. + * @param string Color name + * @return int + */ + + private function color_to_code($color) + { + static $colors = array( + 'black' => 0, + 'red' => 1, + 'green' => 2, + 'yellow' => 3, + 'blue' => 4, + 'magenta' => 5, + 'cyan' => 6, + 'white' => 7 + ); + return ( isset($colors[$color]) ) ? $colors[$color] : $colors['white']; + } +} diff -r 07be890ef627 -r 51560f5414a6 modules/doctor.php --- a/modules/doctor.php Sat Oct 25 20:24:31 2008 +0000 +++ b/modules/doctor.php Sat Nov 15 15:28:32 2008 -0500 @@ -1,4 +1,6 @@ + $basenick = basenick('enanobot|debug'); + // $basenick = 'enanobot' + + * @param string Nickname + * @return string + */ + +function basenick($nick) +{ + if ( preg_match('/^`/', $nick) ) + { + $nick = substr($nick, 1); + } + return preg_replace('/(`|\|)(.+?)$/', '', $nick); +} + +/** + * Resolve an IP address. First goes by checking if it's a mibbit or CGI-IRC IP/user, then performs lookups accordingly. + * @param string Hostname + * @param string Username + * @return string IP address + */ + +function resolve_ip($host, $user) +{ + if ( $host == 'webchat.mibbit.com' ) + { + return hex2ipv4($user); + } + return gethostbyname($host); +} + +function hex2ipv4($ip) +{ + $ip = preg_replace('/^0x/', '', $ip); + $ip = str_split($ip, 2); + foreach ( $ip as &$byte ) + { + $byte = hexdec($byte); + } + return implode('.', $ip); +} diff -r 07be890ef627 -r 51560f5414a6 modules/memberlist.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/memberlist.php Sat Nov 15 15:28:32 2008 -0500 @@ -0,0 +1,123 @@ + '@', + 'v' => '+' +); + +eb_hook('event_self_join', 'mlist_init_channel($this);'); +eb_hook('event_raw_message', 'mlist_process_message($chan, $message);'); +eb_hook('snippet_dynamic', 'if ( $snippet === "memberlist" ) return mlist_list_members($chan, $message);'); +eb_hook('event_other', 'mlist_handle_other_event($message);'); + +function mlist_init_channel(&$chan) +{ + global $eb_memberlist, $userflags; + + $channel_name = $chan->get_channel_name(); + $eb_memberlist[$channel_name] = array(); + $prefixes_regexp = '/^([' . preg_quote(implode('', $userflags)) . '])+/'; + $prefixes_flipped = array_flip($userflags); + $prefixes_regexp_notlist = '/[^' . preg_quote(implode('', $prefixes_flipped)) . ']/'; + + // read list of members from channel + @stream_set_timeout($chan->parent->sock, 3); + while ( $msg = $chan->parent->get() ) + { + if ( $ml = strstr($msg, ' 353 ') ) + { + $memberlist = trim(substr(strstr($ml, ':'), 1)); + $eb_memberlist[$channel_name] = explode(' ', $memberlist); + $eb_memberlist[$channel_name] = array_flip($eb_memberlist[$channel_name]); + foreach ( $eb_memberlist[$channel_name] as $nick => $_ ) + { + $eb_memberlist[$channel_name][$nick] = ''; + while ( preg_match($prefixes_regexp, $nick) ) + { + $prefix = substr($nick, 0, 1); + $add = preg_replace($prefixes_regexp_notlist, '', strval($eb_memberlist[$channel_name][$nick])); + unset($eb_memberlist[$channel_name][$nick]); + $nick = substr($nick, 1); + $eb_memberlist[$channel_name][$nick] = $prefixes_flipped[$prefix] . $add; + } + } + break; + } + } +} + +function mlist_process_message(&$chan, $message) +{ + global $eb_memberlist; + $channel_name = $chan->get_channel_name(); + if ( !isset($eb_memberlist[$channel_name]) ) + { + return false; + } + + $ml =& $eb_memberlist[$channel_name]; + + // we need to change statistics accordingly depending on the event + if ( $message['action'] == 'JOIN' ) + { + // member joined - init their flags and up the member count by one + $ml[$message['nick']] = ''; + } + else if ( $message['action'] == 'PART' ) + { + // member left - clear flags and decrement the total member count + unset($ml[$message['nick']]); + } + else if ( $message['action'] == 'MODE' ) + { + // update member list (not sure why this would be useful, but export it anyway - display scripts might find it useful) + list($mode, $target) = explode(' ', $message['message']); + $action = substr($mode, 0, 1); + + global $userflags; + $ml[$target] = str_replace(substr($mode, 1), '', $ml[$target]); + if ( $action == '+' ) + { + $ml[$target] .= substr($mode, 1); + } + } +} + +function mlist_list_members(&$chan, &$message) +{ + global $eb_memberlist; + $channel_name = $chan->get_channel_name(); + if ( !isset($eb_memberlist[$channel_name]) ) + { + return false; + } + + $ml =& $eb_memberlist[$channel_name]; + + $mlt = implode("\n", str_split(str_replace("\n", ' ', print_r($ml, true)), 400)); + $chan->parent->privmsg($message['nick'], "memberlist:\n" . $mlt); + + return true; +} + +function mlist_handle_other_event(&$message) +{ + global $eb_memberlist; + + if ( $message['action'] == 'NICK' ) + { + // we have a nick change; go through all channels and replace the old nick with the new + foreach ( $eb_memberlist as &$ml ) + { + if ( isset($ml[$message['nick']]) ) + { + $ml[$message['message']] = $ml[$message['nick']]; + unset($ml[$message['nick']]); + } + } + } +} + diff -r 07be890ef627 -r 51560f5414a6 modules/stats.php --- a/modules/stats.php Sat Oct 25 20:24:31 2008 +0000 +++ b/modules/stats.php Sat Nov 15 15:28:32 2008 -0500 @@ -1,272 +1,6 @@ '@', - 'v' => '+' -); -$stats_data = array('anonymous' => array(), 'messages' => array()); -$stats_day = gmdate('Ymd'); -@include("./stats/stats-data-$stats_day.php"); -unset($stats_data['members']); -$stats_data['members'] =& $stats_memberlist; - -eb_hook('event_self_join', 'stats_init_channel($this);'); -eb_hook('event_raw_message', 'stats_process_message($chan, $message);'); -eb_hook('snippet_dynamic', 'if ( $snippet === "memberlist" ) return stats_list_members($chan, $message); if ( $snippet === "deluser" ) return stats_del_user($chan, $message);'); -eb_hook('event_other', 'stats_handle_other_event($message);'); -eb_hook('event_privmsg', 'stats_handle_privmsg($message);'); - -function stats_init_channel(&$chan) -{ - global $stats_memberlist, $stats_prefixes, $stats_data; - - $channel_name = $chan->get_channel_name(); - $stats_memberlist[$channel_name] = array(); - $prefixes_regexp = '/^([' . preg_quote(implode('', $stats_prefixes)) . '])+/'; - $prefixes_flipped = array_flip($stats_prefixes); - $prefixes_regexp_notlist = '/[^' . preg_quote(implode('', $prefixes_flipped)) . ']/'; - - if ( !isset($stats_data['messages'][$channel_name]) ) - { - $stats_data['messages'][$channel_name] = array(); - } - - // read list of members from channel - @stream_set_timeout($chan->parent->sock, 3); - while ( $msg = $chan->parent->get() ) - { - if ( $ml = strstr($msg, ' 353 ') ) - { - $memberlist = trim(substr(strstr($ml, ':'), 1)); - $stats_memberlist[$channel_name] = explode(' ', $memberlist); - $stats_memberlist[$channel_name] = array_flip($stats_memberlist[$channel_name]); - foreach ( $stats_memberlist[$channel_name] as $nick => $_ ) - { - $stats_memberlist[$channel_name][$nick] = ''; - while ( preg_match($prefixes_regexp, $nick) ) - { - $prefix = substr($nick, 0, 1); - $add = preg_replace($prefixes_regexp_notlist, '', strval($stats_memberlist[$channel_name][$nick])); - unset($stats_memberlist[$channel_name][$nick]); - $nick = substr($nick, 1); - $stats_memberlist[$channel_name][$nick] = $prefixes_flipped[$prefix] . $add; - } - } - break; - } - } -} - -function stats_process_message(&$chan, $message) -{ - global $stats_memberlist, $stats_data; - $channel_name = $chan->get_channel_name(); - if ( !isset($stats_memberlist[$channel_name]) ) - { - return false; - } - - $ml =& $stats_memberlist[$channel_name]; - - // we need to change statistics accordingly depending on the event - if ( $message['action'] == 'JOIN' ) - { - // member joined - init their flags and up the member count by one - $ml[$message['nick']] = ''; - } - else if ( $message['action'] == 'PART' ) - { - // member left - clear flags and decrement the total member count - unset($ml[$message['nick']]); - $ml = array_values($ml); - } - else if ( $message['action'] == 'MODE' ) - { - // update member list (not sure why this would be useful, but export it anyway - display scripts might find it useful) - list($mode, $target) = explode(' ', $message['message']); - $action = substr($mode, 0, 1); - - global $stats_prefixes; - $ml[$target] = str_replace(substr($mode, 1), '', $ml[$target]); - if ( $action == '+' ) - { - $ml[$target] .= substr($mode, 1); - } - } - else if ( $message['action'] == 'PRIVMSG' ) - { - // private message into $channel_name - mark the user active and log the message time - if ( isset($stats_data['anonymous'][$message['nick']]) ) - $message['nick'] = 'Anonymous'; - - $messages =& $stats_data['messages'][$channel_name]; - - $messages[] = array( - 'time' => time(), - 'nick' => $message['nick'] - ); - } - - stats_cron(); -} - -function stats_list_members(&$chan, &$message) -{ - global $stats_memberlist; - $channel_name = $chan->get_channel_name(); - if ( !isset($stats_memberlist[$channel_name]) ) - { - return false; - } - - $ml =& $stats_memberlist[$channel_name]; - - $chan->parent->privmsg($message['nick'], "memberlist:\n" . str_replace("\n", ' ', print_r($ml, true))); - - return true; -} +require('statsincludes/stats_core.php'); +require('statsincludes/stats_logger.php'); +require('statsincludes/stats_frontend.php'); -function stats_del_user(&$chan, &$message) -{ - global $stats_memberlist, $privileged_list, $irc, $stats_data; - - // remove a user from the DB - $targetuser = trim(substr(strstr($message['message'], '|'), 1)); - if ( empty($targetuser) ) - $targetuser = $message['nick']; - - if ( $targetuser != $message['nick'] && !in_array($message['nick'], $privileged_list) ) - { - $irc->privmsg($message['nick'], "Sorry, you need to be a moderator to delete statistics for users other than yourself."); - return true; - } - - // we should be good - delete the user - foreach ( $stats_data['messages'] as $channel => &$messages ) - { - foreach ( $messages as $i => &$currentmessage ) - { - if ( $currentmessage['nick'] == $targetuser ) - { - unset($messages[$i]); - } - } - $messages = array_values($messages); - } - unset($users, $currentmessage, $messages); - - global $nick; - $greeting = ( $targetuser == $message['nick'] ) ? "All of your statistics data" : "All of {$targetuser}'s statistic data"; - $irc->privmsg($message['nick'], "$greeting has been removed from the database for all channels. The changes will show up in the next commit to disk, which is usually no more than once every two minutes."); - $irc->privmsg($message['nick'], "Want your stats to be anonymized in the future? Type /msg $nick anonymize to make me keep all your stats anonymous in the future. This only applies to your current nick though - for example if you change your nick to \"{$message['nick']}|sleep\" or similar your information will not be anonymous."); - $irc->privmsg($message['nick'], "You can't clear your logs if you're anonymous. Type /msg $nick denonymize to remove yourself from the anonymization list. Anonymized logs can't be converted back to their original nicks."); - - return true; -} - -function stats_handle_privmsg(&$message) -{ - global $irc, $stats_data, $nick; - static $poll_list = array(); - - $message['message'] = strtolower($message['message']); - - if ( trim($message['message']) === 'anonymize' ) - { - $stats_data['anonymous'][$message['nick']] = true; - $poll_list[$message['nick']] = true; - $irc->privmsg($message['nick'], "Anonymization complete. Any further statistics recorded about you will be anonymous."); - $irc->privmsg($message['nick'], "Do you want to also anonymize any past statistics about you? (type \"yes\" or \"no\")"); - } - else if ( trim($message['message']) === 'denonymize' ) - { - $stats_data['anonymous'][$message['nick']] = false; - unset($stats_data['anonymous'][$message['nick']]); - $irc->privmsg($message['nick'], "Denonymization complete. Any further statistics recorded about you will bear your nick. Remember that you can always change this with /msg $nick anonymize."); - } - else if ( trim($message['message']) === 'yes' && isset($poll_list[$message['nick']]) ) - { - // anonymize logs for this user - // we should be good - delete the user - $targetuser = $message['nick']; - - foreach ( $stats_data['messages'] as $channel => &$messages ) - { - foreach ( $messages as $i => &$currentmessage ) - { - if ( $currentmessage['nick'] == $targetuser ) - { - $currentmessage['nick'] = 'Anonymous'; - } - } - $messages = array_values($messages); - } - unset($users, $currentmessage, $messages); - $irc->privmsg($message['nick'], "Anonymization complete. All past statistics on your nick are now anonymous."); - - unset($poll_list[$message['nick']]); - } - stats_cron(); -} - -function stats_handle_other_event(&$message) -{ - global $stats_memberlist; - - if ( $message['action'] == 'NICK' ) - { - // we have a nick change; go through all channels and replace the old nick with the new - foreach ( $stats_memberlist as &$ml ) - { - if ( isset($ml[$message['nick']]) ) - { - $ml[$message['message']] = $ml[$message['nick']]; - unset($ml[$message['nick']]); - } - } - } - stats_cron(); -} - -function stats_cron() -{ - static $commit_time = 0; - $now = time(); - // commit to disk every 1 minute - if ( $commit_time + 60 < $now ) - { - $commit_time = $now; - stats_commit(); - } -} - -function stats_commit() -{ - global $stats_data, $stats_day; - - ob_start(); - var_export($stats_data); - $stats_data_exported = ob_get_contents(); - ob_end_clean(); - - $fp = @fopen("./stats/stats-data-$stats_day.php", 'w'); - if ( !$fp ) - return false; - fwrite($fp, " */ -$stats_merged_data = array('counts' => array(), 'messages' => array()); -$stats_data =& $stats_merged_data; - define('ENANOBOT_ROOT', dirname(__FILE__)); define('NOW', time()); +require(ENANOBOT_ROOT . '/config.php'); +require(ENANOBOT_ROOT . '/hooks.php'); +require(ENANOBOT_ROOT . '/database.php'); + +mysql_reconnect(); + +/** + * Gets ths list of channels. + * @return array + */ + +function stats_channel_list() +{ + return $GLOBALS['channels']; +} + /** * Gets the number of messages posted in IRC in the last X minutes. * @param string Channel @@ -23,26 +36,32 @@ function stats_message_count($channel, $mins = 10, $base = NOW) { - global $stats_merged_data; - + $channel = db_escape($channel); $time_min = $base - ( $mins * 60 ); - $time_max = $base; - - if ( !isset($stats_merged_data['messages'][$channel]) ) + $time_max =& $base; + if ( $q = eb_mysql_query("SELECT message_count FROM stats_count_cache WHERE time_min = $time_min AND time_max = $time_max AND channel = '$channel';") ) { - return 0; + if ( mysql_num_rows($q) > 0 ) + { + $row = mysql_fetch_assoc($q); + mysql_free_result($q); + return intval($row['message_count']); + } + mysql_free_result($q); } - - $count = 0; - foreach ( $stats_merged_data['messages'][$channel] as $message ) + if ( $q = eb_mysql_query("SELECT COUNT(message_id) FROM stats_messages WHERE channel = '$channel' AND time >= $time_min AND time <= $time_max;") ) { - if ( $message['time'] >= $time_min && $message['time'] <= $time_max ) + $row = mysql_fetch_row($q); + $count = $row[0]; + mysql_free_result($q); + // avoid caching future queries + if ( $base <= NOW ) { - $count++; + eb_mysql_query("INSERT INTO stats_count_cache(channel, time_min, time_max, message_count) VALUES('$channel', $time_min, $time_max, $count);"); } + return $count; } - - return $count; + return false; } /** @@ -55,50 +74,34 @@ function stats_activity_percent($channel, $mins = 10, $base = NOW) { - global $stats_merged_data; - if ( !($total = stats_message_count($channel, $mins, $base)) ) - { - return array(); - } - $results = array(); - $usercounts = array(); + $channel = db_escape($channel); $time_min = $base - ( $mins * 60 ); - $time_max = $base; - foreach ( $stats_merged_data['messages'][$channel] as $message ) + $time_max =& $base; + + if ( $q = eb_mysql_query("SELECT nick FROM stats_messages WHERE channel = '$channel' AND time >= $time_min AND time <= $time_max;") ) { - if ( $message['time'] >= $time_min && $message['time'] <= $time_max ) + $userdata = array(); + while ( $row = @mysql_fetch_assoc($q) ) { - if ( !isset($usercounts[$message['nick']]) ) - $usercounts[$message['nick']] = 0; - $usercounts[$message['nick']]++; + $total++; + if ( isset($userdata[ $row['nick'] ]) ) + { + $userdata[ $row['nick'] ]++; + } + else + { + $userdata[ $row['nick'] ] = 1; + } } - } - foreach ( $usercounts as $nick => $count ) - { - $results[$nick] = $count / $total; + foreach ( $userdata as &$val ) + { + $val = $val / $total; + } + mysql_free_result($q); + arsort($userdata); + return $userdata; } - arsort($results); - return $results; -} - -/** - * Loads X days of statistics, minimum. - * @param int Days to load, default is 1 - */ - -function load_stats_data($days = 1) -{ - $days++; - for ( $i = 0; $i < $days; $i++ ) - { - $day = NOW - ( $i * 86400 ); - $day = gmdate('Ymd', $day); - if ( file_exists(ENANOBOT_ROOT . "/stats/stats-data-$day.php") ) - { - require(ENANOBOT_ROOT . "/stats/stats-data-$day.php"); - stats_merge($stats_data); - } - } + return false; } /** @@ -108,59 +111,8 @@ function stats_last_updated() { - $day = gmdate('Ymd'); - $file = ENANOBOT_ROOT . "/stats/stats-data-$day.php"; - return ( file_exists($file) ) ? filemtime($file) : 0; + // :-D + return NOW; } -/** - * Merges a newly loaded stats array with the current cache in RAM. - * @param array Data to merge - * @access private - */ -function stats_merge($data) -{ - global $stats_merged_data; - if ( isset($data['counts']) ) - { - foreach ( $data['counts'] as $channel => $chaninfo ) - { - if ( isset($stats_merged_data['counts'][$channel]) ) - { - foreach ( $stats_merged_data['counts'][$channel] as $key => &$value ) - { - if ( is_int($value) ) - { - $value = max($value, $chaninfo[$key]); - } - else if ( is_array($value) ) - { - $value = array_merge($value, $chaninfo[$key]); - } - } - } - else - { - $stats_merged_data['counts'][$channel] = $chaninfo; - } - } - } - foreach ( $data['messages'] as $channel => $chandata ) - { - if ( isset($stats_merged_data['messages'][$channel]) ) - { - foreach ( $chandata as $message ) - { - $stats_merged_data['messages'][$channel][] = $message; - } - } - else - { - $stats_merged_data['messages'][$channel] = $chandata; - } - } -} - -load_stats_data(); - diff -r 07be890ef627 -r 51560f5414a6 stats/import-stats.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/stats/import-stats.php Sat Nov 15 15:28:32 2008 -0500 @@ -0,0 +1,53 @@ +start(); +$pbar->set(0); +$i = 0; +foreach ( $filelist as $dh ) +{ + $pbar->update_text_quiet($dh); + $pbar->set(++$i, count($filelist)); + require($dh); + foreach ( $stats_data['messages'] as $channel => &$data ) + { + foreach ( $data as &$message ) + { + stats_log_message($channel, $message['nick'], $message['time']); + } + } + if ( isset($stats_data['anonymous']) ) + { + foreach ( $stats_data['anonymous'] as $user => $_ ) + { + stats_anonymize_user_now($user); + } + } +} + +$pbar->end(); + diff -r 07be890ef627 -r 51560f5414a6 statsincludes/stats_core.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/statsincludes/stats_core.php Sat Nov 15 15:28:32 2008 -0500 @@ -0,0 +1,85 @@ +privmsg($message['nick'], "Sorry, you need to be a moderator to delete statistics for users other than yourself."); + return true; + } + + // we should be good - delete the user + stats_del_user($chan->get_channel_name(), $targetuser); + + global $nick; + $greeting = ( $targetuser == $message['nick'] ) ? "All of your statistics data" : "All of {$targetuser}'s statistic data"; + $irc->privmsg($message['nick'], "$greeting has been removed from the database for all channels. The changes will show up in the next commit to disk, which is usually no more than once every two minutes."); + $irc->privmsg($message['nick'], "Want your stats to be anonymized in the future? Type /msg $nick anonymize to make me keep all your stats anonymous in the future. This only applies to your current nick though - for example if you change your nick to \"{$message['nick']}|sleep\" or similar your information will not be anonymous."); + $irc->privmsg($message['nick'], "You can't clear your logs if you're anonymous. Type /msg $nick denonymize to remove yourself from the anonymization list. Anonymized logs can't be converted back to their original nicks."); + + return true; +} + +## +## Anonymization +## + +eb_hook('event_privmsg', 'stats_handle_privmsg($message);'); + +function stats_handle_privmsg($message) +{ + global $irc, $stats_data, $nick; + static $poll_list = array(); + + $message['message'] = strtolower($message['message']); + + if ( trim($message['message']) === 'anonymize' ) + { + if ( stats_anonymize_user_now($message['nick']) ) + { + $irc->privmsg($message['nick'], "Anonymization complete. Any further statistics recorded about you will be anonymous."); + $irc->privmsg($message['nick'], "Do you want to also anonymize any past statistics about you? (type \"yes\" or \"no\")"); + $poll_list[$message['nick']] = true; + } + else + { + $irc->privmsg($message['nick'], "You're already marked as anonymous."); + } + } + else if ( trim($message['message']) === 'denonymize' ) + { + if ( stats_denonymize_user($message['nick']) ) + { + $irc->privmsg($message['nick'], "Denonymization complete. Any further statistics recorded about you will bear your nick. Remember that you can always change this with /msg $nick anonymize."); + } + else + { + $irc->privmsg($message['nick'], "You're not marked as anonymous."); + } + } + else if ( trim($message['message']) === 'yes' && isset($poll_list[$message['nick']]) ) + { + // anonymize logs for this user + stats_anonymize_user_past($message['nick']); + $irc->privmsg($message['nick'], "Anonymization complete. All past statistics on your nick are now anonymous."); + + unset($poll_list[$message['nick']]); + } + else if ( isset($poll_list[$message['nick']]) ) + { + unset($poll_list[$message['nick']]); + } +} + diff -r 07be890ef627 -r 51560f5414a6 statsincludes/stats_logger.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/statsincludes/stats_logger.php Sat Nov 15 15:28:32 2008 -0500 @@ -0,0 +1,10 @@ +get_channel_name(); + stats_log_message($channel, $message['nick'], time()); +} +