| @@ -0,0 +1,44 @@ | |||||
| export default class | |||||
| CommonModule | |||||
| { | |||||
| static | |||||
| isWide (chr) | |||||
| { | |||||
| return chr.match (/[^\x01-\x7f]/) !== null; | |||||
| } | |||||
| static | |||||
| lenByFull (str) | |||||
| { | |||||
| return str.split ('').map (c => this.isWide (c) ? 1 : .5) | |||||
| .reduce ((a, c) => a + c); | |||||
| } | |||||
| static | |||||
| indexByFToC (str, index) | |||||
| { | |||||
| let i = 0; | |||||
| let work = ''; | |||||
| for (let c of str) | |||||
| { | |||||
| work += c; | |||||
| if (this.lenByFull (work) > index) | |||||
| break; | |||||
| i += 1; | |||||
| } | |||||
| return i; | |||||
| } | |||||
| static | |||||
| midByFull (str, start, length) | |||||
| { | |||||
| const trimmedLeft = str.slice (this.indexByFToC (str, start)); | |||||
| return trimmedLeft.slice (0, this.indexByFToC (trimmedLeft, length)); | |||||
| } | |||||
| } | |||||
| @@ -22,12 +22,18 @@ | |||||
| </head> | </head> | ||||
| <body> | <body> | ||||
| <?php if (!($available)): ?> | |||||
| <div class="alert alert-danger" role="alert"> | |||||
| <strong>【警報】</strong>AIニジカが落ちてるぬ゛〜゛ん゛(泣) | |||||
| </div> | |||||
| <?php endif ?> | |||||
| <div class="container my-5"> | <div class="container my-5"> | ||||
| <div class="accordion mb-5" id="accordion-filter"> | <div class="accordion mb-5" id="accordion-filter"> | ||||
| <div class="accordion-item"> | <div class="accordion-item"> | ||||
| <h2 class="accordion-header"> | <h2 class="accordion-header"> | ||||
| <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-filter" aria-controls="collapse-filter"> | <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-filter" aria-controls="collapse-filter"> | ||||
| 絞り込みてすと | |||||
| 絞り込み | |||||
| </button> | </button> | ||||
| </h2> | </h2> | ||||
| @@ -124,9 +130,9 @@ | |||||
| $length, | $length, | ||||
| true) | true) | ||||
| as $record): ?> | as $record): ?> | ||||
| <div class="mb-4"> | |||||
| <div class="mb-4 message-block"> | |||||
| <div> | <div> | ||||
| <?= $record['date_time'] ?> | |||||
| <span class="message-block-dt"><?= $record['date_time'] ?></span> | |||||
| </div> | </div> | ||||
| <div> | <div> | ||||
| @@ -135,11 +141,11 @@ | |||||
| </div> | </div> | ||||
| <div style="color: blue"> | <div style="color: blue"> | ||||
| > <span style="font-style: italic"><?= $record['chat_message'] ?></span> | |||||
| > <span class="message-block-chat" style="font-style: italic"><?= $record['chat_message'] ?></span> | |||||
| </div> | </div> | ||||
| <div> | <div> | ||||
| <?= $record['answer'] ?> | |||||
| <span class="message-block-answer"><?= $record['answer'] ?></span> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <?php endforeach ?> | <?php endforeach ?> | ||||
| @@ -7,6 +7,11 @@ const LOG_PATH = './log.txt'; | |||||
| $log_data = []; | $log_data = []; | ||||
| exec ("(ps -Af | grep -e '^miteruzo' | grep 'python3 main.py') && (ps -Af | grep -e '^miteruzo' | grep 'obs')", | |||||
| $output, $exit_code); | |||||
| $available = $exit_code === 0; | |||||
| unset ($output, $exit_code); | |||||
| $page = (int) ($_GET['p'] ?? 1); | $page = (int) ($_GET['p'] ?? 1); | ||||
| $length = (int) ($_GET['max'] ?? 20); | $length = (int) ($_GET['max'] ?? 20); | ||||
| $asc = ($_GET['asc'] ?? 0) != 0; | $asc = ($_GET['asc'] ?? 0) != 0; | ||||
| @@ -78,6 +78,15 @@ Script | |||||
| $.cookie ('expand-filter', '0'); | $.cookie ('expand-filter', '0'); | ||||
| }); | }); | ||||
| $ ('.message-block').on ('click', function () | |||||
| { | |||||
| const dt = $ (this).find ('.message-block-dt').text (); | |||||
| const chat = $ (this).find ('.message-block-chat').text (); | |||||
| const answer = $ (this).find ('.message-block-answer').text (); | |||||
| window.open (`./talk.php?dt=${dt}&chat=${chat}&answer=${answer}`); | |||||
| }); | |||||
| if ($.cookie ('expand-filter') === '0') | if ($.cookie ('expand-filter') === '0') | ||||
| { | { | ||||
| $ ('#collapse-filter').removeClass ('show'); | $ ('#collapse-filter').removeClass ('show'); | ||||
| @@ -0,0 +1,32 @@ | |||||
| /* vim:set tabstop=2 softtabstop=2 expandtab :*/ | |||||
| @font-face | |||||
| { | |||||
| font-family: 'Nikumaru'; | |||||
| src: url(./assets/nikumaru.otf); | |||||
| } | |||||
| html, body | |||||
| { | |||||
| overflow: hidden; | |||||
| } | |||||
| body | |||||
| { | |||||
| background-color: black; | |||||
| } | |||||
| #canvas | |||||
| { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| padding: 0; | |||||
| margin: 0; | |||||
| overflow: unset; | |||||
| border: none | |||||
| !important; | |||||
| outline: none | |||||
| !important; | |||||
| display: block; | |||||
| } | |||||
| @@ -0,0 +1,23 @@ | |||||
| <!-- vim:set tabstop=4 softtabstop=4 expandtab :--> | |||||
| <!DOCTYPE html> | |||||
| <html lang="ja"> | |||||
| <head> | |||||
| <meta charset="UTF-8" /> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||||
| <script src="//code.jquery.com/jquery-3.3.1.min.js"></script> | |||||
| <link rel="stylesheet" href="./talk.css?<?= filemtime ('./talk.css') ?>" /> | |||||
| <link rel="preconnect" href="./assets/nikumaru.otf" /> | |||||
| <title>あほ</title> | |||||
| <input type="hidden" id="dt" value="<?= $dt ?>" /> | |||||
| <input type="hidden" id="chat" value="<?= $chat ?>" /> | |||||
| <input type="hidden" id="answer" value="<?= $answer ?>" /> | |||||
| </head> | |||||
| <body> | |||||
| <canvas id="canvas"></canvas> | |||||
| <script src="./talk.js?<?= filemtime ('./talk.js') ?>" type="module"></script> | |||||
| </body> | |||||
| </html> | |||||
| @@ -0,0 +1,136 @@ | |||||
| import CommonModule from './common_module.js'; | |||||
| class | |||||
| Talk | |||||
| { | |||||
| static | |||||
| main () | |||||
| { | |||||
| const canvas = new Canvas; | |||||
| window.onresize = () => canvas.resize (); | |||||
| } | |||||
| } | |||||
| class | |||||
| Canvas | |||||
| { | |||||
| constructor () | |||||
| { | |||||
| this.canvas = $ ('#canvas'); | |||||
| this.ctx = this.canvas[0].getContext ('2d'); | |||||
| (this.bg = new Image ()).src = './assets/bg.jpg'; | |||||
| this.bg.onload = () => this.resize (); | |||||
| (this.nizika = new Image ()).src = './assets/nizika.png'; | |||||
| this.nizika.onload = () => this.resize (); | |||||
| (this.talking = new Image ()).src = './assets/talking.png'; | |||||
| this.talking.onload = () => this.resize (); | |||||
| (new FontFace ('Nikumaru', 'url(./assets/nikumaru.otf)')).load ().then ( | |||||
| () => this.resize ()); | |||||
| } | |||||
| resize () | |||||
| { | |||||
| this.canvas[0].width = $ (window).width (); | |||||
| this.canvas[0].height = $ (window).height (); | |||||
| this.redraw (); | |||||
| } | |||||
| redraw () | |||||
| { | |||||
| this.putBG (); | |||||
| this.putText (0, 0, 15, $ ('#dt').val ()); | |||||
| this.putImage (this.nizika, 370, 260, 1.1); | |||||
| this.putImage (this.talking, 0, 0, 640 / 1024); | |||||
| this.putText (75, 43.75, 20, | |||||
| ('> ' + ((CommonModule.lenByFull ($ ('#chat').val ()) <= 21) | |||||
| ? $ ('#chat').val () | |||||
| : (CommonModule.midByFull ($ ('#chat').val (), 0, 19.5) | |||||
| + '...'))), | |||||
| undefined, undefined, | |||||
| true); | |||||
| this.putText (62.5, 93.75, 31.25, | |||||
| ((CommonModule.lenByFull ($ ('#answer').val ()) <= 16) | |||||
| ? $ ('#answer').val () | |||||
| : CommonModule.midByFull ($ ('#answer').val (), 0, 16)), | |||||
| 'Nikumaru', '#c00000'); | |||||
| if (CommonModule.lenByFull ($ ('#answer').val ()) > 16) | |||||
| { | |||||
| this.putText (62.5, 125, 31.25, | |||||
| ((CommonModule.lenByFull ($ ('#answer').val ()) <= 32) | |||||
| ? CommonModule.midByFull ($ ('#answer').val (), 16, 16) | |||||
| : (CommonModule.midByFull ($ ('#answer').val (), 16, 14.5) | |||||
| + '...')), | |||||
| 'Nikumaru', '#c00000'); | |||||
| } | |||||
| } | |||||
| putBG () | |||||
| { | |||||
| const [x, y, zoom] = this.convertPosition (0, 0); | |||||
| const width = this.bg.height * 4 / 3; | |||||
| const height = this.bg.height; | |||||
| this.ctx.drawImage (this.bg, | |||||
| (852 - 640) / 2, 0, width, height, | |||||
| x, y, width * zoom, height * zoom); | |||||
| } | |||||
| putImage (image, x, y, zoom = 1) | |||||
| { | |||||
| let zoom2 | |||||
| [x, y, zoom2] = this.convertPosition (x, y); | |||||
| zoom *= zoom2 | |||||
| this.ctx.drawImage (image, | |||||
| 0, 0, image.width, image.height, | |||||
| x, y, image.width * zoom, image.height * zoom); | |||||
| } | |||||
| putText (x, y, size, text, style = 'sans-serif', colour = 'black', | |||||
| italic = false) | |||||
| { | |||||
| let zoom; | |||||
| [x, y, zoom] = this.convertPosition (x, y); | |||||
| size *= zoom; | |||||
| this.ctx.font = `${italic ? 'italic ' : ''}${Math.trunc (size)}px ${style}`; | |||||
| this.ctx.fillStyle = colour; | |||||
| this.ctx.textBaseline = 'top'; | |||||
| this.ctx.fillText (text, x, y); | |||||
| } | |||||
| convertPosition (x, y) | |||||
| { | |||||
| const sizeX = this.canvas.width (); | |||||
| const sizeY = this.canvas.height (); | |||||
| const width = this.bg.height * 4 / 3; | |||||
| const height = this.bg.height; | |||||
| const vertical = sizeY / sizeX > height / width; | |||||
| const zoom = vertical ? (sizeX / width) : (sizeY / height); | |||||
| const baseX = vertical ? 0 : ((sizeX - width * zoom) / 2); | |||||
| const baseY = vertical ? ((sizeY - height * zoom) / 2) : 0; | |||||
| return [baseX + x * zoom, baseY + y * zoom, zoom]; | |||||
| } | |||||
| } | |||||
| $ (() => Talk.main ()); | |||||
| @@ -0,0 +1,11 @@ | |||||
| <?php | |||||
| if ($_SERVER['HTTP_HOST'] === 'nizika.monster') | |||||
| header ('location: //nizika.tv/talk.php'); | |||||
| $dt = htmlspecialchars ($_GET['dt']); | |||||
| $chat = htmlspecialchars ($_GET['chat']); | |||||
| $answer = htmlspecialchars ($_GET['answer']); | |||||
| require_once './talk.frm.php'; | |||||