aimg Progress Bar

あいもげのスレ寿命をプログレスバーで表示します

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         aimg Progress Bar
// @name:ja      あいもげプログレスバー
// @namespace    http://tampermonkey.net/
// @version      2.0.0
// @description  あいもげのスレ寿命をプログレスバーで表示します
// @author       nanasy
// @match        https://nijiurachan.net/*/thread*
// @icon         https://nijiurachan.net/favicon.ico
// @license      MIT
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    const height = "4px"; // バーの高さ

    let wrap, fill, add, start, addTime, end, origin, updater;
    const expiry = document.querySelector('.thread-expiry-footer');

    // サイズ調整
    function adjustWidth() {
        const scrollbar = window.innerWidth - document.documentElement.clientWidth;
        wrap.style.width = `calc(100vw - ${scrollbar}px)`;
    }

    // 表示更新
    function updateBar() {
        const now = new Date();
        if(expiry.textContent){
            const text = expiry.textContent.trim().slice(0, -5);
            let [, y, mo, d, h, m] = text.match(/(?:(\d+)年)?(?:(\d+)月)?(?:(\d+)日)?(?:(\d+):(\d+))?/).map(v => (v == null? null: Number(v)));
            h ??= 0;
            m ??= 0;
            d ??= mo? 1: now.getDate();
            mo ??= now.getMonth() + 1;
            y ??= now.getFullYear();
            if(y < 2000)y += 2000;

            end = new Date(y, mo - 1, d, h, m);
            while (end <= now) {
                if (text.includes('年')) {
                    break;
                } else if (text.includes('月')) {
                    y++;
                } else if (text.includes('日')) {
                    mo++;
                    if (mo > 12) {
                        mo = 1;
                        y++;
                    }
                } else break;
                end = new Date(y, mo - 1, d, h, m);
            }
        }
        const ratio = (now - start) / (end - start);
        const formatTime = time => {
            const min = Math.ceil(time / 60000);
            return [
              min >= 1440 && `${min / 1440 | 0}日と`,
              min >= 60 && `${min / 60 % 24 | 0}時間`,
              `${min % 60}分`
            ].filter(Boolean).join('');
        }
        if (ratio < 1 && expiry.textContent) {
            // 寿命が1ヶ月以上なら年月換算
            let remY = end.getFullYear() - now.getFullYear();
            let remMo = end.getMonth() - now.getMonth();
            if (end.getDate() < now.getDate()) remMo--;
            if (remMo < 0) {
                remY--;
                remMo += 12;
            }

            wrap.title = 'あと' + (remY > 0? `${remY}年${remMo}ヶ月`: remMo > 0? `${remMo}ヶ月`: formatTime(end - now));
            fill.style.width = `${ratio * 100}%`;
        } else {
            wrap.title = "まもなく消えます";
            fill.style.width = "100%";
        }
        origin ??= new Date(end - addTime);
        addTime = end - origin;
        if (addTime) wrap.title += ` (${formatTime(addTime)}延長)`;
        add.style.width = `${addTime * 100 / (end - start)}%`;

        adjustWidth();
    }

    // UI生成, 初回実行
    function init() {
        if (/\d+:\d+/.test(expiry.textContent) && !document.querySelector('#progressbar')) {
            const bar = document.createElement('div');
            bar.id = 'progressbar';
            bar.innerHTML = `<div class="wrap"><div class="fill"></div><div class="add"></div></div>`;
            document.body.appendChild(bar);
            wrap = bar.querySelector('.wrap');
            fill = bar.querySelector('.fill');
            add = bar.querySelector('.add');

            const style = document.createElement('style');
            style.textContent = `
                #progressbar{ position: fixed; left: 0px; bottom: ${height}; z-index: 200; }
                #progressbar .wrap{  background: #222; display: flex; position: absolute; }
                #progressbar .fill{ background: #e04000; transition: 1s ease-out; height: ${height}; }
                #progressbar .add{ background: #fff; position: absolute; right: 0px; transition: 1s ease-out; opacity: 0.2; height: ${height}; }
            `;
            document.head.appendChild(style);

            start = new Date(document.querySelector('.thre .cnw').firstChild.nodeValue.trim());
            // 延長時間を取得
            const sod = document.querySelector('.thre .sod').textContent.match(/\d+/);
            addTime = Number(sod? sod[0]: 0) * 600000;

            updateBar();
            // スレに同期して更新
            new MutationObserver(updateBar).observe(expiry, { childList: true, characterData: true });
            // 自動リロードがOFFの場合タイマーで更新
            if (localStorage.getItem('autoReloadEnabled') == 0) updater = setInterval(updateBar, 15000);
        }
        // そうだねが押されたら反映
        document.querySelector('a[id^=sd]')?.addEventListener('click', () => setTimeout(reloadThread, 100));
    }

    // スレの中身が生成・更新されたらinitを実行
    const node = document.querySelector('.thre div[data-thread-op]');
    if (node.firstChild) init();
    new MutationObserver(init).observe(node, { childList: true });

    // 自動リロードの設定が変更されたらタイマーを切り替え
    document.querySelector('#contres').addEventListener('click', e => {
        if (e.target.nodeName === 'INPUT') {
            if(updater)clearInterval(updater);
            if (!e.target.checked) updater = setInterval(updateBar, 15000);
        }
    });

    window.addEventListener('resize', adjustWidth);
})();