PageRenderTime 64ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/log.php

https://bitbucket.org/youknowone/langdev
PHP | 453 lines | 398 code | 47 blank | 8 comment | 54 complexity | d789ad6091d21b6ba637407d3f11bf26 MD5 | raw file
  1. <?php
  2. ini_set('display_errors', 'Off');
  3. require 'auth.php';
  4. class Log
  5. {
  6. var $date; // YYMMDD format
  7. function Log($date) {
  8. $this->date = $date;
  9. }
  10. function path() {
  11. if ($this->is_today())
  12. return "logs/langdev.log";
  13. else
  14. return "logs/langdev.log.$this->date";
  15. }
  16. function available() {
  17. return file_exists($this->path());
  18. }
  19. /*static*/ function today() {
  20. return new Log(date('Ymd'));
  21. }
  22. /*static*/ function random() {
  23. $rand = rand(30, 600);
  24. return new Log(date('Ymd', strtotime("$rand days ago")));
  25. }
  26. function is_today() {
  27. return $this->date == date('Ymd');
  28. }
  29. function yesterday() {
  30. return $this->days_before(1);
  31. }
  32. function days_before($days) {
  33. list($y, $m, $d) = $this->parsed_date();
  34. $timestamp = mktime(0, 0, 0, $m, $d, $y);
  35. $yesterday = strtotime("-$days day", $timestamp);
  36. return new Log(date('Ymd', $yesterday));
  37. }
  38. function parsed_date() {
  39. $y = substr($this->date, 0, 4);
  40. $m = substr($this->date, 4, 2);
  41. $d = substr($this->date, 6, 2);
  42. return array($y, $m, $d);
  43. }
  44. function title() {
  45. list($y, $m, $d) = $this->parsed_date();
  46. return "$y-$m-$d";
  47. }
  48. function messages($from = 0) {
  49. $messages = array();
  50. $fp = @fopen($this->path(), 'r');
  51. if (!$fp)
  52. return $messages;
  53. $no = 0;
  54. while ($line = fgets($fp)) {
  55. if (preg_match('/PRIVMSG/', $line)) {
  56. $no++;
  57. if ($no < $from) continue;
  58. if (!preg_match('/^.*?\[(.+?)(?: #.*?)?\].*? (<<<|>>>) (?::(.+?)!.+? )?PRIVMSG #.+? :(.+)$/', $line, $parts))
  59. continue;
  60. $is_bot = !$parts[3];
  61. if ($is_bot && preg_match('/^<(.+?)> (.*)$/', $parts[4], $tmp)) {
  62. $parts[3] = $tmp[1];
  63. $parts[4] = $tmp[2];
  64. $is_bot = false;
  65. }
  66. $messages[] = array(
  67. 'no' => $no,
  68. 'time' => strtotime($parts[1]),
  69. 'nick' => $parts[3],
  70. 'text' => $parts[4],
  71. 'bot?' => $is_bot,
  72. );
  73. }
  74. }
  75. fclose($fp);
  76. return $messages;
  77. }
  78. function uri() {
  79. return "/log/" . $this->title();
  80. }
  81. }
  82. define('GROUP_THRES', 900 /*seconds*/);
  83. function group_messages($messages) {
  84. $groups = array();
  85. $group = array();
  86. $prev_time = $messages[0]['time'];
  87. foreach ($messages as $msg) {
  88. if (($msg['time'] - $prev_time) > GROUP_THRES) {
  89. // 그룹이 갈린다. 이 메시지 앞까지 한 그룹이 된다.
  90. $groups[] = $group;
  91. $group = array();
  92. }
  93. $group[] = $msg;
  94. $prev_time = $msg['time'];
  95. }
  96. if (!empty($group))
  97. $groups['ungrouped'] = $group;
  98. return $groups;
  99. }
  100. class Synonym
  101. {
  102. var $dict;
  103. function Synonym() {
  104. $dict = array();
  105. $fp = fopen("synonyms", 'r');
  106. while ($line = fgets($fp)) {
  107. $line = trim($line);
  108. $words = explode(' ', $line);
  109. foreach ($words as $word) {
  110. $dict[$word] = $words;
  111. }
  112. }
  113. $this->dict = $dict;
  114. }
  115. function get($word) {
  116. $word = strtolower($word);
  117. $words = $this->dict[$word];
  118. if (!isset($words)) $words = array($word);
  119. return $words;
  120. }
  121. }
  122. class SearchQuery
  123. {
  124. var $keyword;
  125. var $words;
  126. var $days = 7;
  127. var $offset = 0;
  128. function SearchQuery($keyword) {
  129. $synonym = new Synonym();
  130. $this->keyword = $keyword;
  131. $this->words = $synonym->get($this->keyword);
  132. }
  133. function perform() {
  134. $log = Log::today();
  135. if ($this->offset > 0)
  136. $log = $log->days_before($this->offset * $this->days);
  137. $results = array();
  138. for ($days = 0; $days < $this->days; $days++) {
  139. $result = $this->filter($log->messages());
  140. if (!empty($result))
  141. // descending order
  142. $results[$log->date] = array_reverse($result);
  143. $log = $log->yesterday();
  144. }
  145. return $results;
  146. }
  147. function filter($messages) {
  148. $result = array();
  149. foreach ($messages as $msg) {
  150. foreach ($this->words as $word) {
  151. if (stripos($msg['text'], $word) !== FALSE) {
  152. $result[] = $msg;
  153. break;
  154. }
  155. }
  156. }
  157. return $result;
  158. }
  159. }
  160. function autolink($string) {
  161. return preg_replace("#(https?)://([-0-9a-z_.@:~\\#%=+?!/$;,&]+)#i", '<a href="http://fw.mearie.org/$2" rel="noreferrer">$0</a>', $string);
  162. }
  163. function h($string) { return htmlspecialchars($string); }
  164. function print_lines($log, $lines) {
  165. foreach ($lines as $msg): ?>
  166. <tr id="line<?=$msg['no']?>">
  167. <td class="time" title="<?=date('c', $msg['time'])?>"><a href="<?=$log->uri()?>#line<?=$msg['no']?>"><?=date('H:i:s', $msg['time'])?></a></td>
  168. <td class="nickname"><i>&lt;</i><?=!$msg['bot?'] ? h($msg['nick']) : '<strong>낚지</strong>'?><i>&gt;</i></td>
  169. <td class="message"><?=autolink(h($msg['text']))?></td>
  170. </tr>
  171. <?php
  172. endforeach;
  173. }
  174. function print_header($title) {
  175. ?>
  176. <!DOCTYPE html>
  177. <html>
  178. <head>
  179. <meta charset="UTF-8" />
  180. <title>#langdev log: <?=$title?></title>
  181. <meta name="viewport" content="width=device-width" />
  182. <link rel="stylesheet" href="/style.css" type="text/css">
  183. <style type="text/css">
  184. #nav { font-size: 0.6em; margin-left: 2em; color: #999; vertical-align: middle; }
  185. #nav a { color: #999; text-decoration: underline; }
  186. #nav a:hover { background-color: transparent; }
  187. form { border: 1px solid #ccc; padding: 1em; clear: both; }
  188. form#tagline { clear: right; border: none; padding: 0; }
  189. form p { margin: 0; text-indent: 0; }
  190. table { border-collapse: collapse; margin-bottom: 2em; border-top: 2px solid #999; }
  191. td { padding: 0.5em 1em; border-top: 1px solid #ddd; line-height: 1.4; }
  192. .time { font-size: 0.85em; color: #999; }
  193. .time a { color: #999; text-decoration: none; }
  194. .time a:hover { background-color: #ccc; color: #fff; }
  195. .nickname { font-size: 0.9em; text-align: right; }
  196. .link { border: 1px solid #ddd; background-color: #f8f8f8; padding: 0.5em; margin-bottom: 2em; }
  197. .new { font-size: xx-small; vertical-align: super; color: #000; }
  198. .highlight { background-color: #ff0; }
  199. #update a { font-size: 1.2em; text-align: center; border: 1px solid #ccc; background-color: #f4f4f4; display: block; padding: 0.5em; }
  200. i { font-size: 0; color: transparent; }
  201. @media only screen and (max-device-width: 480px) {
  202. body { margin: 0; }
  203. h2 { text-align: center; }
  204. #nav { display: block; margin-left: 0; font-size: 0.8em; }
  205. .time { display: none; }
  206. }
  207. </style>
  208. <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
  209. </head>
  210. <body>
  211. <?php
  212. }
  213. /////////////////////////////
  214. $path = trim(@$_SERVER['PATH_INFO'], '/');
  215. if (empty($path)) {
  216. $log = Log::today();
  217. header("Location: " . $log->uri() . "?recent=30");
  218. exit;
  219. }
  220. if ($path == 'random') {
  221. $log = Log::random();
  222. header("Location: " . $log->uri());
  223. exit;
  224. } else if ($path == 'atom'):
  225. header("Content-Type: application/atom+xml");
  226. echo '<?xml version="1.0" encoding="utf-8"?>';
  227. $log = Log::today();
  228. $data = array_reverse(group_messages($log->messages()));
  229. unset($data['ungrouped']);
  230. $no = count($data);
  231. ?>
  232. <feed xmlns="http://www.w3.org/2005/Atom">
  233. <title>Log of #langdev</title>
  234. <link href="http://log.langdev.org/" />
  235. <updated><?=date('c', $data[0][count($data[0])-1]['time'])?></updated>
  236. <?php foreach ($data as $group): ?>
  237. <entry>
  238. <title><?=$log->title()?>#<?=$no?></title>
  239. <link href="http://log.langdev.org<?=$log->uri()?>#line<?=$group[0]['no']?>" />
  240. <updated><?php echo date('c', $group[count($group)-1]['time']); ?></updated>
  241. <content type="xhtml">
  242. <table xmlns="http://www.w3.org/1999/xhtml">
  243. <?php print_lines($log, $group); ?>
  244. </table>
  245. </content>
  246. </entry>
  247. <?php $no--; endforeach; ?>
  248. </feed>
  249. <?php
  250. elseif ($path == 'search'):
  251. if (isset($_GET['q'])) {
  252. $query = new SearchQuery(trim($_GET['q']));
  253. $query->offset = !empty($_GET['offset']) ? (int)$_GET['offset'] : 0;
  254. $result = $query->perform();
  255. } else
  256. $query = null;
  257. ?>
  258. <?php print_header("search" . ($query ? ": $query->keyword" : '')); ?>
  259. <h1>Log Search</h1>
  260. <form method="get" action="">
  261. <p>최근 7 간의 기록을 검색합니다.</p>
  262. <p>검색어: <input type="text" name="q" value="<?=h($query->keyword)?>" /> <input type="submit" value="찾기" /></p>
  263. </form>
  264. <?php if ($query): ?>
  265. <?php if (!empty($result)): ?>
  266. <?php foreach ($result as $date => $messages): $log = new Log($date); ?>
  267. <div class="day">
  268. <h2><a href="<?=$log->uri()?>"><?=$log->title()?></a></h2>
  269. <table>
  270. <?php print_lines($log, $messages); ?>
  271. </table>
  272. </div>
  273. <?php endforeach; ?>
  274. <?php else: ?>
  275. <p>검색 결과가 없습니다.</p>
  276. <?php endif; ?>
  277. <p><a href="?q=<?=urlencode(h($query->keyword))?>&amp;offset=<?=$query->offset + 1?>">이전 7 &rarr;</a></p>
  278. <script type="text/javascript">
  279. var re = /<?=h(implode('|', array_map('preg_quote', $query->words)))?>/gi
  280. var repl = "<span class=\"highlight\">$&</span>"
  281. var cells = document.getElementsByTagName('td')
  282. for (var i = 0; i < cells.length; i++) {
  283. var cell = cells[i]
  284. if (cell.className == 'message' && cell.innerHTML.match(re))
  285. {
  286. var content = []
  287. for (var j = 0; j < cell.childNodes.length; j++)
  288. {
  289. var node = cell.childNodes[j]
  290. if (node.nodeType == 3)
  291. content.push(node.nodeValue.replace(re, repl))
  292. else if (node.nodeType == 1 && node.tagName == 'A') {
  293. content.push('<a href="' + node.href + '">')
  294. content.push(node.innerHTML.replace(re, repl))
  295. content.push('</a>')
  296. }
  297. }
  298. cell.innerHTML = content.join('')
  299. }
  300. }
  301. </script>
  302. <?php endif; ?>
  303. </body>
  304. </html>
  305. <?php
  306. elseif ($path == 'say'):
  307. $ctx = stream_context_create(array(
  308. 'http' => array(
  309. 'method' => 'POST',
  310. 'content' => "nick=$_SERVER[PHP_AUTH_USER]&msg=" . rawurlencode($_POST['msg']),
  311. )
  312. ));
  313. file_get_contents("http://localhost:6667/", false, $ctx);
  314. else:
  315. $log = new Log(preg_replace('/^(\d{4})-(\d{2})-(\d{2})$/', '$1$2$3', $path));
  316. if (!$log->available()) {
  317. header("HTTP/1.1 404 Not Found");
  318. echo "Not found";
  319. exit;
  320. }
  321. if (empty($_GET['from'])):
  322. $messages = $log->messages();
  323. $count = count($messages);
  324. $lines = $messages[$count-1]['no'];
  325. if ($only_recent = $log->is_today() && isset($_GET['recent'])) {
  326. $now = time();
  327. $from = 0;
  328. foreach ($messages as $i => $msg) {
  329. if (($now - $msg['time']) <= 60*$_GET['recent']) {
  330. $from = $i;
  331. break;
  332. }
  333. }
  334. if ($count - $from < 50)
  335. $from = $count - 50;
  336. if ($from == 0)
  337. $only_recent = false;
  338. else
  339. $messages = array_slice($messages, $from);
  340. }
  341. ?>
  342. <?php print_header($log->title()); ?>
  343. <h1>Log of #langdev</h1>
  344. <form method="get" action="search" id="tagline">
  345. <p><input type="text" name="q" value="<?=h(@$query->keyword)?>" /> <input type="submit" value="찾기" /> / <a href="/log/atom">Atom 피드</a></p>
  346. </form>
  347. <h2><?=$log->title()?>
  348. <span id="nav">
  349. <a href="/log/<?=date('Y-m-d', strtotime('yesterday'))?>">어제</a> 또는 <a href="/log/">오늘</a> 날아가기 / <a href="/log/random"> 좋은 예감</a> / <a href="#updates"> 아래로 &darr;</a>
  350. </span></h2>
  351. <?php if ($only_recent): ?>
  352. <p>최근 대화만 표시하고 있습니다. [<a href="<?=$log->uri()?>">전체 보기</a>] [<a href="<?=$log->uri()?>?recent=<?=$_GET['recent']+30?>">30 보기</a>]</p>
  353. <?php endif; ?>
  354. <?php foreach (group_messages($messages) as $group): ?>
  355. <table>
  356. <?php print_lines($log, $group); ?>
  357. </table>
  358. <?php endforeach; ?>
  359. <?php if ($log->is_today()): ?>
  360. <table id="updates"></table>
  361. <form method="post" action="say" id="say">
  362. <p>&lt;<?=htmlspecialchars($_SERVER['PHP_AUTH_USER'])?>&gt; <input type="text" name="msg" id="msg" size="50" /> <input type="submit" value="Say!" /> 갱신 주기: <span id="period">3000</span>ms</p>
  363. </form>
  364. <script type="text/javascript">
  365. var from = <?=$lines + 1?>;
  366. var interval = 3000;
  367. var seq = 0;
  368. function _update_log() {
  369. var _from = from;
  370. $.get('?from=' + _from, function (data) {
  371. if (from > _from) return;
  372. var willScroll = $(document).height() <= $(window).scrollTop()+$(window).height() + 20;
  373. $('#updates').append(data)
  374. if (willScroll)
  375. $(window).scrollTop($(document).height() + 100000)
  376. $('#period').text(interval)
  377. })
  378. }
  379. function update_log() {
  380. _update_log()
  381. window.setTimeout(update_log, interval)
  382. }
  383. window.setTimeout(update_log, interval)
  384. $('#say').submit(function(event) {
  385. $.post($(this).attr('action'), $(this).serialize(), function() {
  386. _update_log()
  387. })
  388. $('#msg').attr('value', '')
  389. event.preventDefault()
  390. })
  391. </script>
  392. <?php endif; ?>
  393. <p>낚지가 기록합니다.</a></p>
  394. </body>
  395. </html>
  396. <?php
  397. else:
  398. $messages = $log->messages((int)$_GET['from']);
  399. if (!empty($messages)) {
  400. $lines = $messages[count($messages)-1]['no'] + 1;
  401. print_lines($log, $messages);
  402. $js = "from=$lines;interval=3000;";
  403. } else $js = "interval=Math.min(10000, interval+100)";
  404. echo "<script type='text/javascript'>$js</script>";
  405. endif;
  406. endif;