public function write_data($sqlite3) { /** * Write data to database table "fqdns". */ if ($this->fqdn !== '') { if (($fid = $sqlite3->querySingle('SELECT fid FROM fqdns WHERE fqdn = \'' . $this->fqdn . '\'')) === false) { output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); } if (is_null($fid)) { $sqlite3->exec('INSERT INTO fqdns (fid, fqdn, tld) VALUES (NULL, \'' . $this->fqdn . '\', \'' . $this->tld . '\')') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); $fid = $sqlite3->lastInsertRowID(); } } /** * Write data to database tables "urls" and "uid_urls". */ if (($lid = $sqlite3->querySingle('SELECT lid FROM urls WHERE url = \'' . $sqlite3->escapeString($this->url) . '\'')) === false) { output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); } if (is_null($lid)) { $sqlite3->exec('INSERT INTO urls (lid, url' . ($this->fqdn !== '' ? ', fid' : '') . ') VALUES (NULL, \'' . $sqlite3->escapeString($this->url) . '\'' . ($this->fqdn !== '' ? ', ' . $fid : '') . ')') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); $lid = $sqlite3->lastInsertRowID(); } foreach ($this->uses as $key => $values) { $sqlite3->exec('INSERT INTO uid_urls (uid, lid, datetime) VALUES ((SELECT uid FROM uid_details WHERE csnick = \'' . $values[1] . '\'), ' . $lid . ', DATETIME(\'' . $values[0] . '\'))') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); } }
public function write_data($sqlite3) { /** * Write data to database table "words". */ $sqlite3->exec('INSERT OR IGNORE INTO words (word, length, total) VALUES (\'' . $sqlite3->escapeString($this->word) . '\', ' . $this->length . ', ' . $this->total . ')') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); $sqlite3->exec('UPDATE words SET total = total + ' . $this->total . ' WHERE CHANGES() = 0 AND word = \'' . $sqlite3->escapeString($this->word) . '\'') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); }
/** * Write data to database tables "topics" and "uid_topics". */ public function write_data($sqlite3) { if (($tid = $sqlite3->querySingle('SELECT tid FROM topics WHERE topic = \'' . $sqlite3->escapeString($this->topic) . '\'')) === false) { output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); } if (is_null($tid)) { $sqlite3->exec('INSERT INTO topics (tid, topic) VALUES (NULL, \'' . $sqlite3->escapeString($this->topic) . '\')') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); $tid = $sqlite3->lastInsertRowID(); } foreach ($this->uses as $key => $values) { $sqlite3->exec('INSERT INTO uid_topics (uid, tid, datetime) VALUES ((SELECT uid FROM uid_details WHERE csnick = \'' . $values[1] . '\'), ' . $tid . ', DATETIME(\'' . $values[0] . '\'))') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); } }
/** * Parse a line for various chat data. */ protected function parse_line($line) { /** * "Normal" lines. */ if (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] (?<nick>\\S+): (?<line>.+)$/', $line, $matches)) { $this->set_normal($matches['time'], $matches['nick'], $matches['line']); /** * "Join" lines. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] (?<nick>\\S+) has joined the channel$/', $line, $matches)) { $this->set_join($matches['time'], $matches['nick']); /** * "Part" lines. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] (?<nick>\\S+) has left the channel$/', $line, $matches)) { $this->set_part($matches['time'], $matches['nick']); /** * Skip everything else. */ } elseif ($line !== '') { output::output('debug', __METHOD__ . '(): skipping line ' . $this->linenum . ': \'' . $line . '\''); } }
private function make_table_people_timeofday($sqlite3) { /** * Only create the table if there is activity from users other than bots and * excluded users. */ if (($total = $sqlite3->querySingle('SELECT SUM(l_total) FROM ruid_lines JOIN uid_details ON ruid_lines.ruid = uid_details.uid WHERE status NOT IN (3,4)')) === false) { output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); } if (empty($total)) { return null; } $high_value = 0; $times = ['night', 'morning', 'afternoon', 'evening']; foreach ($times as $time) { $query = $sqlite3->query('SELECT csnick, l_' . $time . ' FROM ruid_lines JOIN uid_details ON ruid_lines.ruid = uid_details.uid WHERE status NOT IN (3,4) AND l_' . $time . ' != 0 ORDER BY l_' . $time . ' DESC, ruid_lines.ruid ASC LIMIT ' . $this->maxrows_people_timeofday) or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); $i = 0; while ($result = $query->fetchArray(SQLITE3_ASSOC)) { $i++; ${$time}[$i]['lines'] = $result['l_' . $time]; ${$time}[$i]['user'] = $result['csnick']; if (${$time}[$i]['lines'] > $high_value) { $high_value = ${$time}[$i]['lines']; } } } $tr0 = '<colgroup><col class="pos"><col class="c"><col class="c"><col class="c"><col class="c">'; $tr1 = '<tr><th colspan="5">Most Talkative People by Time of Day'; $tr2 = '<tr><td class="pos"><td class="k">Night<br>0h - 5h<td class="k">Morning<br>6h - 11h<td class="k">Afternoon<br>12h - 17h<td class="k">Evening<br>18h - 23h'; $trx = ''; for ($i = 1; $i <= $this->maxrows_people_timeofday; $i++) { if (!isset($night[$i]['lines']) && !isset($morning[$i]['lines']) && !isset($afternoon[$i]['lines']) && !isset($evening[$i]['lines'])) { break; } $trx .= '<tr><td class="pos">' . $i; foreach ($times as $time) { if (!isset(${$time}[$i]['lines'])) { $trx .= '<td class="v">'; } else { $width = round(${$time}[$i]['lines'] / $high_value * 190); if ($width !== (double) 0) { $trx .= '<td class="v">' . htmlspecialchars(${$time}[$i]['user']) . ' - ' . number_format(${$time}[$i]['lines']) . '<br><div class="' . $this->color[$time] . '" style="width:' . $width . 'px"></div>'; } else { $trx .= '<td class="v">' . htmlspecialchars(${$time}[$i]['user']) . ' - ' . number_format(${$time}[$i]['lines']); } } } } return '<table class="ppl-tod">' . $tr0 . $tr1 . $tr2 . $trx . '</table>' . "\n"; }
/** * Read the settings from the configuration file and put them into $settings[] * so they can be passed along to other classes. */ private function read_config($file) { if (($rp = realpath($file)) === false) { output::output('critical', __METHOD__ . '(): no such file: \'' . $file . '\''); } if (($fp = fopen($rp, 'rb')) === false) { output::output('critical', __METHOD__ . '(): failed to open file: \'' . $rp . '\''); } while (!feof($fp)) { $line = trim(fgets($fp)); if (preg_match('/^\\s*(?<setting>\\w+)\\s*=\\s*"\\s*(?<value>([^\\s"]+(\\s+[^\\s"]+)*))\\s*"/', $line, $matches)) { $this->settings[$matches['setting']] = $matches['value']; } } fclose($fp); /** * Exit if any crucial setting is missing. */ foreach ($this->settings_list_required as $key) { if (!array_key_exists($key, $this->settings)) { output::output('critical', __METHOD__ . '(): missing required setting: \'' . $key . '\''); } } /** * If set, override variables listed in $settings_list[]. */ foreach ($this->settings_list as $key => $type) { if (!array_key_exists($key, $this->settings)) { continue; } /** * Do some explicit type casting because everything is initially a string. */ if ($type === 'string') { $this->{$key} = $this->settings[$key]; } elseif ($type === 'int' && preg_match('/^\\d+$/', $this->settings[$key])) { $this->{$key} = (int) $this->settings[$key]; } elseif ($type === 'bool') { if (strtolower($this->settings[$key]) === 'true') { $this->{$key} = true; } elseif (strtolower($this->settings[$key]) === 'false') { $this->{$key} = false; } } } }
/** * Parse a line for various chat data. */ protected function parse_line($line) { /** * "Normal" lines. */ if (preg_match('/^\\S{3} \\d{2} (?<time>\\d{2}:\\d{2}(:\\d{2})?) <(?<nick>\\S+)> (?<line>.+)$/', $line, $matches)) { $this->set_normal($matches['time'], $matches['nick'], $matches['line']); /** * "Join" lines. */ } elseif (preg_match('/^\\S{3} \\d{2} (?<time>\\d{2}:\\d{2}(:\\d{2})?) \\* (?<nick>\\S+) \\(\\S+\\) has joined [#&!+]\\S+$/', $line, $matches)) { $this->set_join($matches['time'], $matches['nick']); /** * "Quit" lines. */ } elseif (preg_match('/^\\S{3} \\d{2} (?<time>\\d{2}:\\d{2}(:\\d{2})?) \\* (?<nick>\\S+) has quit \\(.*\\)$/', $line, $matches)) { $this->set_quit($matches['time'], $matches['nick']); /** * "Mode" lines. */ } elseif (preg_match('/^\\S{3} \\d{2} (?<time>\\d{2}:\\d{2}(:\\d{2})?) \\* (?<nick_performing>\\S+) (?<modesign>gives|removes) (?<mode>channel operator status|voice) (to|from) (?<nicks_undergoing>\\S+( \\S+)*)$/', $line, $matches)) { $nicks_undergoing = explode(' ', $matches['nicks_undergoing']); if ($matches['modesign'] === 'gives') { $modesign = '+'; } else { $modesign = '-'; } if ($matches['mode'] === 'channel operator status') { $mode = 'o'; } else { $mode = 'v'; } foreach ($nicks_undergoing as $nick_undergoing) { $this->set_mode($matches['time'], $matches['nick_performing'], $nick_undergoing, $modesign . $mode); } /** * "Nickchange" lines. */ } elseif (preg_match('/^\\S{3} \\d{2} (?<time>\\d{2}:\\d{2}(:\\d{2})?) \\* (?<nick_performing>\\S+) is now known as (?<nick_undergoing>\\S+)$/', $line, $matches)) { $this->set_nickchange($matches['time'], $matches['nick_performing'], $matches['nick_undergoing']); /** * "Part" lines. */ } elseif (preg_match('/^\\S{3} \\d{2} (?<time>\\d{2}:\\d{2}(:\\d{2})?) \\* (?<nick>\\S+) \\(\\S+\\) has left [#&!+]\\S+( \\(.*\\))?$/', $line, $matches)) { $this->set_part($matches['time'], $matches['nick']); /** * "Topic" lines. */ } elseif (preg_match('/^\\S{3} \\d{2} (?<time>\\d{2}:\\d{2}(:\\d{2})?) \\* (?<nick>\\S+) has changed the topic to: (?<line>.+)$/', $line, $matches)) { $this->set_topic($matches['time'], $matches['nick'], $matches['line']); /** * "Kick" lines. */ } elseif (preg_match('/^\\S{3} \\d{2} (?<time>\\d{2}:\\d{2}(:\\d{2})?) \\* (?<line>(?<nick_performing>\\S+) has kicked (?<nick_undergoing>\\S+) from [#&!+]\\S+ \\(.*\\))$/', $line, $matches)) { $this->set_kick($matches['time'], $matches['nick_performing'], $matches['nick_undergoing'], $matches['line']); /** * Skip everything else. */ } elseif ($line !== '') { output::output('debug', __METHOD__ . '(): skipping line ' . $this->linenum . ': \'' . $line . '\''); } }
public function write_data($sqlite3) { /** * Write data to database table "uid_details". */ if (($result = $sqlite3->querySingle('SELECT uid, firstseen FROM uid_details WHERE csnick = \'' . $sqlite3->escapeString($this->csnick) . '\'', true)) === false) { output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); } if (empty($result)) { $sqlite3->exec('INSERT INTO uid_details (uid, csnick' . ($this->firstseen !== '' ? ', firstseen, lastseen' : '') . ') VALUES (NULL, \'' . $sqlite3->escapeString($this->csnick) . '\'' . ($this->firstseen !== '' ? ', DATETIME(\'' . $this->firstseen . '\'), DATETIME(\'' . $this->lastseen . '\')' : '') . ')') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); $uid = $sqlite3->lastInsertRowID(); } else { $uid = $result['uid']; /** * Only update $firstseen if the value stored in the database is zero. We're * parsing logs in chronological order so the stored value of $firstseen can * never be lower and the value of $lastseen can never be higher than the parsed * values. (We are not going out of our way to deal with possible DST nonsense.) * Secondly, only update $csnick if the nick was seen. We want to avoid it from * being overwritten by a lowercase $prevnick (streak code) or weirdly cased * nick due to a slap. */ if ($this->firstseen !== '') { $sqlite3->exec('UPDATE uid_details SET csnick = \'' . $sqlite3->escapeString($this->csnick) . '\'' . ($result['firstseen'] === '0000-00-00 00:00:00' ? ', firstseen = DATETIME(\'' . $this->firstseen . '\')' : '') . ', lastseen = DATETIME(\'' . $this->lastseen . '\') WHERE uid = ' . $uid) or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); } } /** * Write data to database table "uid_activity". */ if ($this->l_total !== 0) { $queryparts = $this->get_queryparts($sqlite3, ['l_night', 'l_morning', 'l_afternoon', 'l_evening', 'l_total']); $sqlite3->exec('INSERT OR IGNORE INTO uid_activity (uid, date, ' . implode(', ', $queryparts['columns']) . ') VALUES (' . $uid . ', \'' . substr($this->firstseen, 0, 10) . '\', ' . implode(', ', $queryparts['values']) . ')') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); $sqlite3->exec('UPDATE uid_activity SET ' . implode(', ', $queryparts['update-assignments']) . ' WHERE CHANGES() = 0 AND uid = ' . $uid . ' AND date = \'' . substr($this->firstseen, 0, 10) . '\'') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); } /** * Write data to database table "uid_events". */ $queryparts = $this->get_queryparts($sqlite3, ['m_op', 'm_opped', 'm_voice', 'm_voiced', 'm_deop', 'm_deopped', 'm_devoice', 'm_devoiced', 'joins', 'parts', 'quits', 'kicks', 'kicked', 'nickchanges', 'topics', 'ex_kicks', 'ex_kicked']); if (!empty($queryparts)) { $sqlite3->exec('INSERT OR IGNORE INTO uid_events (uid, ' . implode(', ', $queryparts['columns']) . ') VALUES (' . $uid . ', ' . implode(', ', $queryparts['values']) . ')') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); $sqlite3->exec('UPDATE uid_events SET ' . implode(', ', $queryparts['update-assignments']) . ' WHERE CHANGES() = 0 AND uid = ' . $uid) or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); } /** * Try to pick the longest unique line from each of the quote stacks. */ $types = ['ex_actions', 'ex_uppercased', 'ex_exclamations', 'ex_questions', 'quote']; foreach ($types as $type) { if (empty($this->{$type . '_stack'})) { continue; } /** * rsort() sorts a multidimensional array on the first value of each contained * array, highest to lowest. */ rsort($this->{$type . '_stack'}); $this->{$type} = $this->{$type . '_stack'}[0]['line']; if (($type === 'ex_questions' || $type === 'ex_exclamations') && $this->{$type} === $this->ex_uppercased && count($this->{$type . '_stack'}) > 1) { for ($i = 1, $j = count($this->{$type . '_stack'}); $i < $j; $i++) { if ($this->{$type . '_stack'}[$i]['line'] !== $this->ex_uppercased) { $this->{$type} = $this->{$type . '_stack'}[$i]['line']; break; } } } elseif ($type === 'quote' && ($this->quote === $this->ex_uppercased || $this->quote === $this->ex_exclamations || $this->quote === $this->ex_questions) && count($this->quote_stack) > 1) { for ($i = 1, $j = count($this->quote_stack); $i < $j; $i++) { if ($this->quote_stack[$i]['line'] !== $this->ex_uppercased && $this->quote_stack[$i]['line'] !== $this->ex_exclamations && $this->quote_stack[$i]['line'] !== $this->ex_questions) { $this->quote = $this->quote_stack[$i]['line']; break; } } } } /** * Write data to database table "uid_lines". */ $queryparts = $this->get_queryparts($sqlite3, ['l_00', 'l_01', 'l_02', 'l_03', 'l_04', 'l_05', 'l_06', 'l_07', 'l_08', 'l_09', 'l_10', 'l_11', 'l_12', 'l_13', 'l_14', 'l_15', 'l_16', 'l_17', 'l_18', 'l_19', 'l_20', 'l_21', 'l_22', 'l_23', 'l_night', 'l_morning', 'l_afternoon', 'l_evening', 'l_total', 'l_mon_night', 'l_mon_morning', 'l_mon_afternoon', 'l_mon_evening', 'l_tue_night', 'l_tue_morning', 'l_tue_afternoon', 'l_tue_evening', 'l_wed_night', 'l_wed_morning', 'l_wed_afternoon', 'l_wed_evening', 'l_thu_night', 'l_thu_morning', 'l_thu_afternoon', 'l_thu_evening', 'l_fri_night', 'l_fri_morning', 'l_fri_afternoon', 'l_fri_evening', 'l_sat_night', 'l_sat_morning', 'l_sat_afternoon', 'l_sat_evening', 'l_sun_night', 'l_sun_morning', 'l_sun_afternoon', 'l_sun_evening', 'urls', 'words', 'characters', 'monologues', 'slaps', 'slapped', 'exclamations', 'questions', 'actions', 'uppercased', 'quote', 'ex_exclamations', 'ex_questions', 'ex_actions', 'ex_uppercased']); if (!empty($queryparts)) { $sqlite3->exec('INSERT OR IGNORE INTO uid_lines (uid, ' . implode(', ', $queryparts['columns']) . ($this->lasttalked !== '' ? ', lasttalked' : '') . ') VALUES (' . $uid . ', ' . implode(', ', $queryparts['values']) . ($this->lasttalked !== '' ? ', DATETIME(\'' . $this->lasttalked . '\')' : '') . ')') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); $sqlite3->exec('UPDATE uid_lines SET ' . implode(', ', $queryparts['update-assignments']) . ($this->lasttalked !== '' ? ', lasttalked = DATETIME(\'' . $this->lasttalked . '\')' : '') . ' WHERE CHANGES() = 0 AND uid = ' . $uid) or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); /** * Update $topmonologue separately as we want to keep the highest value instead * of the sum. */ if ($this->topmonologue !== 0) { if (($topmonologue = $sqlite3->querySingle('SELECT topmonologue FROM uid_lines WHERE uid = ' . $uid)) === false) { output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); } if ($this->topmonologue > $topmonologue) { $sqlite3->exec('UPDATE uid_lines SET topmonologue = ' . $this->topmonologue . ' WHERE uid = ' . $uid) or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); } } } /** * Write data to database table "uid_smileys". */ $queryparts = $this->get_queryparts($sqlite3, ['s_01', 's_02', 's_03', 's_04', 's_05', 's_06', 's_07', 's_08', 's_09', 's_10', 's_11', 's_12', 's_13', 's_14', 's_15', 's_16', 's_17', 's_18', 's_19', 's_20', 's_21', 's_22', 's_23', 's_24', 's_25', 's_26', 's_27', 's_28', 's_29', 's_30', 's_31', 's_32', 's_33', 's_34', 's_35', 's_36', 's_37', 's_38', 's_39', 's_40', 's_41', 's_42', 's_43', 's_44', 's_45', 's_46', 's_47', 's_48', 's_49', 's_50']); if (!empty($queryparts)) { $sqlite3->exec('INSERT OR IGNORE INTO uid_smileys (uid, ' . implode(', ', $queryparts['columns']) . ') VALUES (' . $uid . ', ' . implode(', ', $queryparts['values']) . ')') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); $sqlite3->exec('UPDATE uid_smileys SET ' . implode(', ', $queryparts['update-assignments']) . ' WHERE CHANGES() = 0 AND uid = ' . $uid) or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); } }
/** * Parse a line for various chat data. */ protected function parse_line($line) { /** * "Normal" lines. */ if (preg_match('/^\\d{4}-\\d{2}-\\d{2}T(?<time>\\d{2}:\\d{2}:\\d{2}) <(?<nick>\\S+)> (?<line>.+)$/', $line, $matches)) { $this->set_normal($matches['time'], $matches['nick'], $matches['line']); /** * "Join" lines. */ } elseif (preg_match('/^\\d{4}-\\d{2}-\\d{2}T(?<time>\\d{2}:\\d{2}:\\d{2}) \\*\\*\\* (?<nick>\\S+) has joined [#&!+]\\S+$/', $line, $matches)) { $this->set_join($matches['time'], $matches['nick']); /** * "Quit" lines. */ } elseif (preg_match('/^\\d{4}-\\d{2}-\\d{2}T(?<time>\\d{2}:\\d{2}:\\d{2}) \\*\\*\\* (?<nick>\\S+) has quit IRC$/', $line, $matches)) { $this->set_quit($matches['time'], $matches['nick']); /** * "Mode" lines. */ } elseif (preg_match('/^\\d{4}-\\d{2}-\\d{2}T(?<time>\\d{2}:\\d{2}:\\d{2}) \\*\\*\\* (?<nick_performing>\\S+) sets mode: (?<modes>[-+][ov]+([-+][ov]+)?) (?<nicks_undergoing>\\S+( \\S+)*)$/', $line, $matches)) { $modenum = 0; $nicks_undergoing = explode(' ', $matches['nicks_undergoing']); for ($i = 0, $j = strlen($matches['modes']); $i < $j; $i++) { $mode = substr($matches['modes'], $i, 1); if ($mode === '-' || $mode === '+') { $modesign = $mode; } else { $this->set_mode($matches['time'], $matches['nick_performing'], $nicks_undergoing[$modenum], $modesign . $mode); $modenum++; } } /** * "Action" and "slap" lines. */ } elseif (preg_match('/^\\d{4}-\\d{2}-\\d{2}T(?<time>\\d{2}:\\d{2}:\\d{2}) \\* (?<line>(?<nick_performing>\\S+) ((?<slap>[sS][lL][aA][pP][sS]( (?<nick_undergoing>\\S+)( .+)?)?)|(.+)))$/', $line, $matches)) { if (!empty($matches['slap'])) { $this->set_slap($matches['time'], $matches['nick_performing'], !empty($matches['nick_undergoing']) ? $matches['nick_undergoing'] : null); } $this->set_action($matches['time'], $matches['nick_performing'], $matches['line']); /** * "Nickchange" lines. */ } elseif (preg_match('/^\\d{4}-\\d{2}-\\d{2}T(?<time>\\d{2}:\\d{2}:\\d{2}) \\*\\*\\* (?<nick_performing>\\S+) is now known as (?<nick_undergoing>\\S+)$/', $line, $matches)) { $this->set_nickchange($matches['time'], $matches['nick_performing'], $matches['nick_undergoing']); /** * "Part" lines. */ } elseif (preg_match('/^\\d{4}-\\d{2}-\\d{2}T(?<time>\\d{2}:\\d{2}:\\d{2}) \\*\\*\\* (?<nick>\\S+) has left [#&!+]\\S+$/', $line, $matches)) { $this->set_part($matches['time'], $matches['nick']); /** * "Topic" lines. */ } elseif (preg_match('/^\\d{4}-\\d{2}-\\d{2}T(?<time>\\d{2}:\\d{2}:\\d{2}) \\*\\*\\* (?<nick>\\S+) changes topic to "(?<line>.+)"$/', $line, $matches)) { if ($matches['line'] !== ' ') { $this->set_topic($matches['time'], $matches['nick'], $matches['line']); } /** * "Kick" lines. */ } elseif (preg_match('/^\\d{4}-\\d{2}-\\d{2}T(?<time>\\d{2}:\\d{2}:\\d{2}) \\*\\*\\* (?<line>(?<nick_undergoing>\\S+) was kicked by (?<nick_performing>\\S+) \\(.*\\))$/', $line, $matches)) { $this->set_kick($matches['time'], $matches['nick_performing'], $matches['nick_undergoing'], $matches['line']); /** * Skip everything else. */ } elseif ($line !== '') { output::output('debug', __METHOD__ . '(): skipping line ' . $this->linenum . ': \'' . $line . '\''); } }
/** * Normalize and validate a URL and return an array with its elements. */ public static function get_elements($url) { /** * Assemble the regular expression if not already done so. */ if (self::$regexp_complete === '') { $domain = '(?<domain>[a-z0-9]([a-z0-9-]{0,61}?[a-z0-9]|[a-z0-9]{0,62})?(\\.[a-z0-9]([a-z0-9-]{0,61}?[a-z0-9]|[a-z0-9]{0,62})?)*)'; $tld = '(?<tld>\\.[a-z0-9]([a-z0-9-]{0,61}?[a-z0-9]|[a-z0-9]{0,62})?)'; $fqdn = '(?<fqdn>' . $domain . $tld . ')\\.?'; $ipv4address = '(?<ipv4address>(25[0-5]|(2[0-4]|1[0-9]|[1-9])?[0-9])(\\.(25[0-5]|(2[0-4]|1[0-9]|[1-9])?[0-9])){3})'; $port = '(?<port>(6553[0-5]|(655[0-2]|(65[0-4]|(6[0-4]|[1-5][0-9]|[1-9])[0-9]|[1-9])[0-9]|[1-9])?[0-9]))'; $authority = '(?<authority>(' . $ipv4address . '|' . $fqdn . ')(:' . $port . ')?)'; $unreserved = '[a-z0-9_.~-]'; $pct_encoded = '%[0-9a-f]{2}'; $sub_delims = '[!$&\'()*+,;=]'; $pchar = '(' . $unreserved . '|' . $pct_encoded . '|' . $sub_delims . '|[:@])'; $fragment = '(?<fragment>(#(' . $pchar . '|[\\/?])*)?)'; $path = '(?<path>(\\/\\/?(' . $pchar . '+\\/?)*)?)'; $query = '(?<query>(\\?(' . $pchar . '|[\\/?])*)?)'; $scheme = '(?<scheme>https?:\\/\\/)'; self::$regexp_callback = '/^' . $scheme . '?' . $authority . '/i'; self::$regexp_complete = '/^(?<url>' . $scheme . '?' . $authority . $path . $query . $fragment . ')$/i'; /** * Read "tlds-alpha-by-domain.txt" and put all TLDs in an array against which we * can validate found URLs. If the aforementioned file does not exist or fails * to be read, the TLD check will not be done. This would be an unexpected and * undesired exception though. */ if (($tlds = file(__DIR__ . '/tlds-alpha-by-domain.txt')) === false) { output::output('notice', __METHOD__ . '(): failed to open file: \'tlds-alpha-by-domain.txt\', tld validation disabled'); } else { foreach ($tlds as $tld) { $tld = trim($tld); if ($tld !== '' && strpos($tld, '#') === false) { self::$valid_tlds[] = '.' . strtolower($tld); } } } } /** * Convert scheme and authority to lower case. */ $url = preg_replace_callback(self::$regexp_callback, function ($matches) { return strtolower($matches[0]); }, $url); /** * Validate and further process the URL. */ if (!preg_match(self::$regexp_complete, $url, $matches)) { return false; } /** * Verify if the TLD is valid. If the validation array is empty we skip this * step. */ if (!empty(self::$valid_tlds) && !empty($matches['tld']) && !in_array($matches['tld'], self::$valid_tlds)) { return false; } /** * The maximum allowed length of the FQDN (root domain excluded) is 254 * characters. */ if (strlen($matches['fqdn']) > 254) { return false; } /** * If the URL has no scheme, http:// is assumed. Update the elements. */ if (empty($matches['scheme'])) { $matches['scheme'] = 'http://'; $matches['url'] = 'http://' . $matches['url']; } /** * Create and return an array with all the elements of this URL. */ $elements = ['url', 'scheme', 'authority', 'ipv4address', 'fqdn', 'domain', 'tld', 'path', 'query', 'fragment']; foreach ($elements as $element) { if (empty($matches[$element])) { /** * Always pass along an empty string for nonexistent elements. */ $urldata[$element] = ''; } else { $urldata[$element] = $matches[$element]; } } /** * Make sure the only numeric element isn't passed along as a string. */ if (empty($matches['port'])) { $urldata['port'] = 0; } else { $urldata['port'] = (int) $matches['port']; } return $urldata; }
/** * Parse a line for various chat data. */ protected function parse_line($line) { /** * "Normal" lines. */ if (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] <(?<nick>\\S+)> (?<line>.+)$/', $line, $matches)) { $this->set_normal($matches['time'], $matches['nick'], $matches['line']); /** * "Join" lines. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] (?<nick>\\S+) \\(\\S+\\) joined [#&!+]\\S+\\.$/', $line, $matches)) { $this->set_join($matches['time'], $matches['nick']); /** * "Quit" lines. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] (?<nick>\\S+) \\(\\S+\\) left irc:( .+)?$/', $line, $matches)) { $this->set_quit($matches['time'], $matches['nick']); /** * "Mode" lines. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] [#&!+]\\S+: mode change \'(?<modes>[-+][ov]+([-+][ov]+)?) (?<nicks_undergoing>\\S+( \\S+)*)\' by (?<nick_performing>\\S+?)(!(\\S+)?)?$/', $line, $matches)) { $modenum = 0; $nicks_undergoing = explode(' ', $matches['nicks_undergoing']); for ($i = 0, $j = strlen($matches['modes']); $i < $j; $i++) { $mode = substr($matches['modes'], $i, 1); if ($mode === '-' || $mode === '+') { $modesign = $mode; } else { $this->set_mode($matches['time'], $matches['nick_performing'], $nicks_undergoing[$modenum], $modesign . $mode); $modenum++; } } /** * "Action" and "slap" lines. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] Action: (?<line>(?<nick_performing>\\S+) ((?<slap>[sS][lL][aA][pP][sS]( (?<nick_undergoing>\\S+)( .+)?)?)|(.+)))$/', $line, $matches)) { if (!empty($matches['slap'])) { $this->set_slap($matches['time'], $matches['nick_performing'], !empty($matches['nick_undergoing']) ? $matches['nick_undergoing'] : null); } $this->set_action($matches['time'], $matches['nick_performing'], $matches['line']); /** * "Nickchange" lines. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] Nick change: (?<nick_performing>\\S+) -> (?<nick_undergoing>\\S+)$/', $line, $matches)) { $this->set_nickchange($matches['time'], $matches['nick_performing'], $matches['nick_undergoing']); /** * "Part" lines. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] (?<nick>\\S+) \\(\\S+\\) left [#&!+]\\S+( \\(.*\\))?\\.$/', $line, $matches)) { $this->set_part($matches['time'], $matches['nick']); /** * "Topic" lines. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] Topic changed on [#&!+]\\S+ by (?<nick>\\S+?)(!(\\S+)?)?: (?<line>.+)$/', $line, $matches)) { $this->set_topic($matches['time'], $matches['nick'], $matches['line']); /** * "Kick" lines. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] (?<line>(?<nick_undergoing>\\S+) kicked from [#&!+]\\S+ by (?<nick_performing>\\S+):( .+)?)$/', $line, $matches)) { $this->set_kick($matches['time'], $matches['nick_performing'], $matches['nick_undergoing'], $matches['line']); /** * Eggdrop logs repeated lines (case insensitive matches) in the format: "Last message repeated NUM * time(s).". We process the previous line NUM times. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] Last message repeated (?<num>\\d+) time\\(s\\)\\.$/', $line, $matches)) { /** * Prevent the parser from repeating a preceding repeat line. Also, skip processing if we find a * repeat line on the first line of the logfile. We can't look back across files. */ if ($this->linenum === 1 || $this->repeatlock) { return null; } $this->linenum--; $this->repeatlock = true; output::output('debug', __METHOD__ . '(): repeating line ' . $this->linenum . ': ' . $matches['num'] . ' time' . ($matches['num'] !== '1' ? 's' : '')); for ($i = 1, $j = (int) $matches['num']; $i <= $j; $i++) { $this->parse_line($this->prevline); } $this->linenum++; $this->repeatlock = false; /** * Skip everything else. */ } elseif ($line !== '') { output::output('debug', __METHOD__ . '(): skipping line ' . $this->linenum . ': \'' . $line . '\''); } }
/** * Make the alias with the most lines the new registered nick for the user or * bot it is linked to. */ private function register_most_active_alias($sqlite3) { $query = $sqlite3->query('SELECT status, csnick, ruid, (SELECT uid_details.uid AS uid FROM uid_details JOIN uid_lines ON uid_details.uid = uid_lines.uid WHERE ruid = t1.ruid ORDER BY l_total DESC, uid ASC LIMIT 1) AS newruid FROM uid_details AS t1 WHERE status IN (1,3,4) AND newruid IS NOT NULL AND ruid != newruid') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); if (($result = $query->fetchArray(SQLITE3_ASSOC)) === false) { return null; } $query->reset(); while ($result = $query->fetchArray(SQLITE3_ASSOC)) { $registered = $result['csnick']; if (($alias = $sqlite3->querySingle('SELECT csnick FROM uid_details WHERE uid = ' . $result['newruid'])) === false) { output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); } $sqlite3->exec('UPDATE uid_details SET ruid = ' . $result['newruid'] . ', status = ' . $result['status'] . ' WHERE uid = ' . $result['newruid']) or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); $sqlite3->exec('UPDATE uid_details SET ruid = ' . $result['newruid'] . ', status = 2 WHERE ruid = ' . $result['ruid']) or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); output::output('debug', __METHOD__ . '(): \'' . $alias . '\' set to new registered for \'' . $registered . '\''); } }
public function make_table($sqlite3) { /** * Detect which class to use. Class medium should be set explicitly by setting * $medium to true. */ if ($this->medium) { $class = 'medium'; } elseif (array_key_exists('v3', $this->keys)) { $class = 'large'; } else { $class = 'small'; } /** * Run the "total" query if present. */ if (!empty($this->queries['total'])) { if (($this->total = $sqlite3->querySingle($this->queries['total'])) === false) { output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); } } /** * Create the table head. */ if ($class === 'small') { $tr0 = '<colgroup><col class="c1"><col class="pos"><col class="c2">'; $tr1 = '<tr><th colspan="3">' . (!empty($this->total) ? '<span class="title">' . $this->head . '</span><span class="title-right">' . number_format($this->total) . ' Total</span>' : $this->head); $tr2 = '<tr><td class="k1">' . $this->keys['k1'] . '<td class="pos"><td class="k2">' . $this->keys['k2']; $trx = ''; } else { $tr0 = '<colgroup><col class="c1"><col class="pos"><col class="c2"><col class="c3">'; $tr1 = '<tr><th colspan="4">' . (!empty($this->total) ? '<span class="title">' . $this->head . '</span><span class="title-right">' . number_format($this->total) . ' Total</span>' : $this->head); $tr2 = '<tr><td class="k1">' . $this->keys['k1'] . '<td class="pos"><td class="k2">' . $this->keys['k2'] . '<td class="k3">' . $this->keys['k3']; $trx = ''; } /** * Run the "main" query and structure the table contents. */ $i = 0; $query = $sqlite3->query($this->queries['main']) or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); while ($result = $query->fetchArray(SQLITE3_ASSOC)) { $i++; foreach ($this->keys as $key => $type) { /** * Skip irrelevant keys. */ if (strpos($key, 'v') === false) { continue; } switch ($type) { case 'string': ${$key} = htmlspecialchars($result[$key]); break; case 'int': ${$key} = number_format($result[$key]); break; case 'float': ${$key} = number_format($result[$key], $this->decimals) . ($this->percentage ? '%' : ''); break; case 'date': ${$key} = date('j M \'y', strtotime($result[$key])); break; case 'date-norepeat': ${$key} = date('j M \'y', strtotime($result[$key])); if (!empty($prevdate) && ${$key} === $prevdate) { ${$key} = ''; } else { $prevdate = ${$key}; } break; case 'url': ${$key} = '<a href="' . htmlspecialchars($result[$key]) . '">' . htmlspecialchars($result[$key]) . '</a>'; break; case 'string-url': ${$key} = $this->find_urls($result[$key]); break; case 'userstats': ${$key} = '<a href="user.php?cid=' . urlencode($this->cid) . '&nick=' . urlencode($result[$key]) . '">' . htmlspecialchars($result[$key]) . '</a>'; break; } } if ($class === 'small') { $trx .= '<tr><td class="v1">' . $v1 . '<td class="pos">' . $i . '<td class="v2">' . $v2; } else { /** * Class v3a doesn't use ellipsis. */ $trx .= '<tr><td class="v1">' . $v1 . '<td class="pos">' . $i . '<td class="v2">' . $v2 . '<td class="' . ($this->v3a ? 'v3a' : 'v3') . '">' . $v3; } } if ($i < $this->minrows) { return null; } for ($i; $i < $this->maxrows; $i++) { if ($class === 'small') { $trx .= '<tr><td class="v1"><td class="pos"> <td class="v2">'; } else { $trx .= '<tr><td class="v1"><td class="pos"> <td class="v2"><td class="v3">'; } } return '<table class="' . $class . '">' . $tr0 . $tr1 . $tr2 . $trx . '</table>' . "\n"; }
/** * Parse a line for various chat data. */ protected function parse_line($line) { /** * "Normal" lines. */ if (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] (?<nick>\\S+): (?<line>.+)$/', $line, $matches)) { $this->set_normal($matches['time'], $matches['nick'], $matches['line']); /** * "Join" lines. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] (?<nick>\\S+) \\(\\S+\\) joined the channel\\.$/', $line, $matches)) { $this->set_join($matches['time'], $matches['nick']); /** * "Quit" lines. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] (?<nick>\\S+) \\(\\S+\\) left IRC\\. \\(.*\\)$/', $line, $matches)) { $this->set_quit($matches['time'], $matches['nick']); /** * "Mode" lines. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] (?<nick_performing>\\S+) sets mode (?<modes>[-+][ov]+([-+][ov]+)?) (?<nicks_undergoing>\\S+( \\S+)*)$/', $line, $matches)) { $modenum = 0; $nicks_undergoing = explode(' ', $matches['nicks_undergoing']); for ($i = 0, $j = strlen($matches['modes']); $i < $j; $i++) { $mode = substr($matches['modes'], $i, 1); if ($mode === '-' || $mode === '+') { $modesign = $mode; } else { $this->set_mode($matches['time'], $matches['nick_performing'], $nicks_undergoing[$modenum], $modesign . $mode); $modenum++; } } /** * "Nickchange" lines. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] (?<nick_performing>\\S+) is now known as (?<nick_undergoing>\\S+)$/', $line, $matches)) { $this->set_nickchange($matches['time'], $matches['nick_performing'], $matches['nick_undergoing']); /** * "Part" lines. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] (?<nick>\\S+) \\(\\S+\\) left the channel\\.( \\(.*\\))?$/', $line, $matches)) { $this->set_part($matches['time'], $matches['nick']); /** * "Topic" lines. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] (?<nick>\\S+) changed the topic to (?<line>.+)$/', $line, $matches)) { $this->set_topic($matches['time'], $matches['nick'], $matches['line']); /** * "Kick" lines. */ } elseif (preg_match('/^\\[(?<time>\\d{2}:\\d{2}(:\\d{2})?)\\] (?<line>(?<nick_performing>\\S+) kicked (?<nick_undergoing>\\S+) from the channel\\. \\(.*\\))$/', $line, $matches)) { $this->set_kick($matches['time'], $matches['nick_performing'], $matches['nick_undergoing'], $matches['line']); /** * Skip everything else. */ } elseif ($line !== '') { output::output('debug', __METHOD__ . '(): skipping line ' . $this->linenum . ': \'' . $line . '\''); } }
public function write_data($sqlite3) { /** * If there are no nicks there is no data. */ if (empty($this->nick_objs)) { return false; } output::output('notice', __METHOD__ . '(): writing data to database'); $sqlite3->exec('BEGIN TRANSACTION') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); /** * Write channel totals to database. */ if ($this->l_total !== 0) { $queryparts = $this->get_queryparts($sqlite3, ['l_00', 'l_01', 'l_02', 'l_03', 'l_04', 'l_05', 'l_06', 'l_07', 'l_08', 'l_09', 'l_10', 'l_11', 'l_12', 'l_13', 'l_14', 'l_15', 'l_16', 'l_17', 'l_18', 'l_19', 'l_20', 'l_21', 'l_22', 'l_23', 'l_night', 'l_morning', 'l_afternoon', 'l_evening', 'l_total']); $sqlite3->exec('INSERT OR IGNORE INTO channel_activity (date, ' . implode(', ', $queryparts['columns']) . ') VALUES (\'' . $this->date . '\', ' . implode(', ', $queryparts['values']) . ')') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); $sqlite3->exec('UPDATE channel_activity SET ' . implode(', ', $queryparts['update-assignments']) . ' WHERE CHANGES() = 0 AND date = \'' . $this->date . '\'') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); } /** * Write user data to database. User data should be written prior to topic and * URL data. */ foreach ($this->nick_objs as $nick) { $nick->write_data($sqlite3); } /** * Write topic data to database. */ foreach ($this->topic_objs as $topic) { $topic->write_data($sqlite3); } /** * Write URL data to database. */ foreach ($this->url_objs as $url) { $url->write_data($sqlite3); } /** * Write word data to database. */ foreach ($this->word_objs as $word) { $word->write_data($sqlite3); } /** * Write streak data (history) to database. */ if ($this->l_total !== 0) { $sqlite3->exec('DELETE FROM streak_history') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); $sqlite3->exec('INSERT INTO streak_history (prevnick, streak) VALUES (\'' . $sqlite3->escapeString($this->prevnick) . '\', ' . $this->streak . ')') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); } $sqlite3->exec('COMMIT') or output::output('critical', basename(__FILE__) . ':' . __LINE__ . ', sqlite3 says: ' . $sqlite3->lastErrorMsg()); return true; }