Greasy Fork

Instant Wakamemo

わかめて上で動作するわかめてメモのようなものです。

目前为 2019-10-18 提交的版本。查看 最新版本

// ==UserScript==
// @name         Instant Wakamemo
// @namespace    http://mobajinro.s178.xrea.com/wakamemo/
// @version      1.2
// @description  わかめて上で動作するわかめてメモのようなものです。
// @author       udop_
// @match        http://jinrou.dip.jp/~jinrou/cgi_jinro.cgi
// @match        http://61.215.66.131/~jinrou/cgi_jinro.cgi
// @match        http://jinrou.dip.jp/~jinrou/kako/153359.html
// @require      https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// @run-at       document-start
// @grant        GM_addStyle
// @grant        GM_setClipboard
// ==/UserScript==

(function($) {
    'use strict';

    if(document.body.bgColor == "#fee3aa"){ //村画面でないときは出さない
        return false;
    }

    //**********************************************
    // 変数・クラスの宣言
    //**********************************************

    var playertable = $("table").eq(1)
    var frametable = $("table").eq(0)
    var textareatable = $("table[cellspacing=0]").eq(-1)
    var discusstable = $("table[cellpadding=0]").not(".CLSTABLE2").last();
    var infotable = $("table[cellspacing=0]").eq(0)
    var commandtable = $("table[cellspacing=0]").eq(-2)
    var textarea = $("textarea").eq(0)
    var body = $("body")

    var isdaytime = document.body.bgColor != "#000000"

    var setting = {
        "rewrite_css":{
            "default":"yes",
            "option":{"yes":"はい","no":"いいえ"},
            "name":"見た目を変更する",
        },
        "auto_import_log":{
            "default":"onetime",
            "option":{"none":"しない","onetime":"投票時","alltime":"常時"},
            "name":"自動ログ取得のタイミング"
        },
        "alert_vote":{
            "default":"onetime",
            "name":"未投票時に警告する",
            "option":{"yes":"はい","no":"いいえ"},
        },
        "send_support":{
            "default":"ctrl",
            "name":"支援キー+ENTERで送信",
            "option":{"none":"しない","ctrl":"CTRL","shift":"SHIFT"},
        },
        "highlight_deathnote":{
            "default":"yes",
            "name":"デスノートを拾った時強調表示する",
            "option":{"yes":"はい","no":"いいえ"},
        },
        "disp_iconsuggest":{
            "default":"yes",
            "name":"アイコンの入力支援をする",
            "option":{"yes":"はい","no":"いいえ"},
        },
        "autoreload_interval":{
            "default":"30",
            "name":"自動更新(観戦時のみ)",
            "option":{"10":"10秒","20":"20秒","30":"30秒","60":"60分"},
        },
        "theme_color": {
            "default":"navy",
            "name":"テーマカラー(更新後反映)",
            "option":{"crimson": "紅","darkorange": "オレンジ","darkgreen": "緑","navy": "蒼","purple":"紫","sienna": "茶"}
        }
    }

    var subcolor = {
        "crimson": "#EC365A",
        "darkorange": "#FF9F32",
        "darkgreen": "#009300",
        "navy": "#0000b2",
        "purple":"#B200B2",
        "sienna": "#C66538"
    }

    class Tr{
        constructor(id,cl){
            this.id=""
            this.cl=""
            if (id) this.id = id
            if (cl) this.cl = cl
            this.tds = []
        }
        add(val,cl){
            var c = cl ? `class='${cl}'` : ""
            var td = `<td ${c}>${val}</td>`
            this.tds.push(td)
        }
        text(){
            var tr ="<tr"
            if(this.id) tr = tr + " id='"+ this.id +"'"
            if(this.cl) tr = tr + " class='"+ this.cl +"'"
            tr += ">"
            tr += this.tds.join("")
            tr += "</tr>"
            return tr
        }
        appendTo(jQueryObject){
            jQueryObject.append(this.text())
        }
    }

    //**********************************************
    // 個人設定
    //**********************************************

    function getSetting(){
        for(var k in setting){
            setting[k].value = getLocal(k,setting[k].default)
        }
    }

    function setSetting(){
        $("#setting select").each(function(i,e){
            setLocal($(e).attr("id"), $(e).val());
        });
    }

    var COLORLIST = {
        0:"選択▼",
        1:"明灰", 2:"暗灰", 3:"黄色", 4:"オレンジ", 5:"赤",
        6:"水色", 7:"青", 8:"黄緑", 9:"紫", 10:"桃色",
        11:"肌色", 12:"茶色", 13:"緑", 14:"若草色", 15:"真紅",
        16:"薄茶色", 17:"藍色", 18:"蒼", 19:"ピンク", 20:"銀色",
        21:"薄紫",22:"象牙色", 23:"黒"
    };


    //**********************************************
    //  Session関係
    //**********************************************
    function getSession(keyname,def){
        if (sessionStorage.getItem(keyname)){
            var item = sessionStorage.getItem(keyname);
            item = JSON.parse(item);
            return item;
        } else {
            return def;
        }
    }

    function setSession(keyname, val){
        if(val){
            var j = JSON.stringify(val);
            sessionStorage.setItem(keyname, j);
        } else {
            sessionStorage.removeItem(keyname);
        }
    }

    function getLocal(keyname,def){
        if (localStorage.getItem(keyname)){
            var item = localStorage.getItem(keyname);
            item = JSON.parse(item);
            return item;
        } else {
            return def;
        }
    }

    function setLocal(keyname, val){
        if(val){
            var j = JSON.stringify(val);
            localStorage.setItem(keyname, j);
        } else {
            localStorage.removeItem(keyname);
        }
    }

    //**********************************************
    //   import関係
    //**********************************************

    function importLog(){
        updateplayerInfo()
        importDiscussLog()
    }

    function updateplayerInfo(){ //プレイヤー情報を取得
        //death…「能力がCOできなくなった日」を指す 3日目吊りなら4日目。3日目噛まれなら3日目

        if(discussLog.length >= 2){
            playertable.find("td:odd").each(function(i,v){
                if ($(v).html()) return false;
                playerInfo[i].vital = $(v).html().indexOf("生存中") > 0 ? "alive" : "death";
            })
        } else {
            playerInfo = [];
            playertable.find("td:odd").each(function(i,v){

                var html = $(v).html();
                if (!html) return false;

                var name = html.split("<br>")[0];
                var vital = html.indexOf("生存中") > 0 ? "alive" : "death";
                playerInfo.push({
                    no:i,
                    name: name,
                    vital: vital,
                    job: "gray",
                    reasoning: "gray",
                    jobresult: [],
                    vote: [],
                    death: 99,
                });
            });
            makePlayerIndex()
        }
        setSession("playerInfo",playerInfo);
    }

    function importDiscussLog(){//ログ取り込み
        var day = checkDay();
        if(isdaytime){
            discussLog[day] = [];
            discusstable.find("tr").each(function(i,v){
                if ($(v).children().length != 2) return true;
                discussLog[day].push({
                    name: $(v).children().eq(0).find("b:first").html(),
                    namehtml: $(v).children().eq(0).html(),
                    content: $(v).children().eq(1).html()
                });
            });
        }

        //投票結果・死亡ログ
        var votelog = [];
        discusstable.find("td[colspan='2']").each(function(i,v){
            if(/(無残な姿で発見|死体で発見|村民協議の結果処刑|突然死)/.test($(v).text())){
                importDeath(v);
            }
            if(/\d{1,2}日目 投票結果。/.test($(v).text())){
                votelog.push(v);
            }
        });
        if(votelog.length) importVote(votelog);

        filterlog( 99,newestDay );
        setSession("playerInfo",playerInfo);
        setSession("discussLog",discussLog);
    }

    function importDeath(log){//死体を記録。利便性のためplayerInfoにも保存。未完成。
        var day = checkDay();
        var deathday = day;
        var name = playerIndex[$(log).find("b").eq(0).text()];
        var text = $(log).text();
        var reason = ""
        if(deathlog[day] === undefined) deathlog[day] = {};
        if(deathlog[day-1] === undefined) deathlog[day-1] = {};
        if(/無残な姿で発見/.test(text)){
            reason = "bite"
        } else if(/死体で発見/.test(text)){
            reason = "note"
        } else if(/村民協議の結果/.test(text)){
            reason = "exec"
            if(!isdaytime) deathday++
        } else if(/突然死/.test(text)){
            reason = "sudden"
            deathday++
        }
        playerInfo[name].death = deathday;

        if (!deathlog[day][reason]){
            deathlog[day][reason] = [];
        }

        if(deathlog[day][reason].indexOf(name)<0){
            deathlog[day][reason].push(name);
        }

        setSession("deathlog",deathlog);
    }

    function importVote(logs){//投票を取り込む
        var day = $(logs[0]).text().match(/(\d{1,2})日目 投票結果。/);
        day = day[1] - 1 ;

        for (var player of playerInfo){
            playerInfo[player.no].vote[day] = [];
        }
        for(var log of logs){
            $(log).find("tr").each(function(i,vote){
                var voter = playerIndex[$(vote).find("b").eq(0).text()];
                var target = $(vote).find("b").eq(1).text();
                playerInfo[voter].vote[day].unshift(target);
            });
        }
    }

    //**********************************************
    //   refresh関係
    //**********************************************

    function refresh(){ //再表示まとめて
        refreshDiscussLog();
        refreshPlayerInfoTable();
        refreshVoteTable();
        refreshDeathTable();
    }

    function refreshPlayerInfoTable(){ //プレイヤー情報更新 くっそ長い

        //初期化
        playerInfoTable.empty();

        if(! playerInfo.length) return false;

        var graylist = getGrayList();
        var playersList = getPlayerList();
        var joblist = {"gray":"", "fortune":"占い", "necro":"霊能", "share":"共有", "guard":"狩人", "cat":"猫又", "beast":"人外"};
        var reasoninglist = {"gray":"", "real":"真", "fake":"偽", "villager":"村人", "madman":"狂人", "wolf":"人狼","fox":"妖狐"}
        var resultlist = {notinput:"",white:"○",black:"●"}
        var player,select;

        //-------------------------名前列
        var namerow = new Tr("","namerow")
        namerow.add("<a id='filterlink_99_99'>全ログ</a>");
        for (player of playerInfo){
            namerow.add(`<a id='filterlink_${player.no}_99'>${player.name}</a>`,`player_${player.no}`)
        }
        namerow.appendTo(playerInfoTable);
        coloring();

        //-------------------------発言数列
        for(var day=1; day<=newestDay; day++){

            var row = new Tr("","talknumrow");
            row.add(`<a id=filterlink_99_${day}>${day+1}日目</a>`);
            for (player of playerInfo){
                var talknum = $("#log_day"+day).find(".talk_player"+player.no).length;
                if (talknum === 0) talknum = "";
                row.add(`<a id=filterlink_${player.no}_${day}>${talknum}</a>`,"player_"+player.no)
            }
            row.appendTo(playerInfoTable);
        }

        //-------------------------CO列
        var jobrow = new Tr("","jobrow")
        jobrow.add("CO");
        for (player of playerInfo){
            select = createSelectBox(joblist, player.job, "player_"+player.no+"_job","jobselect");
            jobrow.add(select,"player_"+player.no)
        };
        jobrow.appendTo(playerInfoTable);

        //-------------------------推理列
        jobrow = new Tr("","jobrow")
        jobrow.add("推理");
        for (player of playerInfo){
            select = createSelectBox(reasoninglist, player.reasoning, "player_"+player.no+"_reasoning","reasoningselect");
            jobrow.add(select,"player_"+player.no)
        }
        playerInfoTable.append(jobrow.text());


        //-------------------------役職結果列
        for(day=1; day<=newestDay; day++){

            var resultrow = new Tr("result_"+day,"resultrow");
            resultrow.add(`占霊結果 ${day+1}日目`);

            for (player of playerInfo){
                if ( (player.job == "fortune" && day < player.death ) || (player.job == "necro" && day < player.death && day > 1) ){ //占い師、霊能者で、結果があるなら
                    var select1 = createSelectBox(playersList,99,"target_"+player.no+"_"+day,"jobResult");
                    var select2 = createSelectBox(resultlist,"notinput","judge_"+player.no+"_"+day,"jobResult");
                    resultrow.add(select1+select2,"player_"+player.no)
                } else {
                    resultrow.add("","player_"+player.no)
                }
            }
            playerInfoTable.append(resultrow.text());

            for (player of playerInfo){
                if ( (player.job == "fortune" && day < player.death ) || (player.job == "necro" && day < player.death && day > 1) ){ //占い師、霊能者で、結果があるなら

                    if (player.jobresult[day]) { //既に結果が入力されているとき
                        $("#target_"+player.no+"_"+day).val(player.jobresult[day].target);
                        $("#judge_"+player.no+"_"+day).val(player.jobresult[day].judge);
                    } else if(player.job == "necro" && deathlog[day-1] && deathlog[day-1].exec !== undefined){ //霊能者で死体情報があるとき
                        $("#target_"+player.no+"_"+day).val(deathlog[day-1].exec);
                    }
                }
            }
        }
        filteringAlive()
        filteringInputMode()

        //絞込機能つける
        $("#playerInfoTable a").on("click",function(e){
            var id = $(this).attr("id").split("_");
            filterlog( id[1],id[2] );
        });

        //変更は逐一反映
        $("select.jobselect").on("change",function(){
            var no = $(this).attr("id").split("_")[1] -0
            playerInfo[no].job = $(this).val()

            refreshPlayerInfoTable()
            refreshJobInitial()
            setSession("playerInfo",playerInfo);
        })

        $("select.reasoningselect").on("change",function(){
            var no = $(this).attr("id").split("_")[1] -0
            playerInfo[no].reasoning = $(this).val()

            refreshJobInitial()
            setSession("playerInfo",playerInfo);
        })

        $("select.jobResult").on("change",function(e){
            var id = $(this).attr("id").split("_");
            var type = id[0]
            var no = id[1] - 0;
            var day = id[2] - 0;

            if(! playerInfo[no].jobresult[day]) {
                playerInfo[no].jobresult[day] = {"target":99, "judge":"notinput"};
            }

            var v = $(this).val()
            playerInfo[no].jobresult[day][type] = isNaN(v-0) ? v : v-0;

            coloring();
            setSession("playerInfo",playerInfo);
        });
    }

    function refreshDiscussLog(){
        discussLogTable.empty();
        if (! discussLog.length) return false;

        discussLog.forEach(function(logs,day){

            if(!logs) return;

            var tbody = $("<tbody></tbody>",{id:"log_day"+day});
            var trs = `<tr class="systemlog"><td colspan="2">${day+1}日目</td></tr>`;

            for (var log of logs){
                var cl = ""
                if(log.name in playerIndex){
                    cl = "talk_player"+playerIndex[log.name]
                }
                trs += `<tr class="${cl}"><td>${log.namehtml}<span class='jobinitial'></span></td><td>${log.content}</td></tr>`;
            }
            tbody.append(trs).prependTo(discussLogTable);
        });
        refreshJobInitial()
    }

    function refreshJobInitial(){
        var jobinitial = {
            "fortune":"占",
            "necro":"霊",
            "share":"共",
            "cat":"猫",
            "guard":"狩",
            "wolf":"狼",
            "madman":"狂",
            "fox":"狐",
            "villager":"村",
            "real":"真",
            "fake": "偽",
            "beast":"外",
            "gray":"",
        }
        for(var player of playerInfo){
            var job = jobinitial[player.reasoning] + jobinitial[player.job]
            $("tr.talk_player"+player.no+" span").html(job)
        }
    }

    function refreshVoteTable(){//投票テーブルリライト
        voteTable.empty();

        var tr = new Tr()
        tr.add("プレイヤー")
        for(var day=1; day<=newestDay; day++){
            tr.add(`${day+1}日目`);
        }
        voteTable.append(tr.text())

        for(var player of playerInfo){
            tr = new Tr()
            tr.add(player.name)
            for(day=1; day<=newestDay; day++){
                var text = "-"
                if(player.vote[day]) text = player.vote[day].join("<br>")
                tr.add(text)
            }
            voteTable.append(tr.text());
        }
    }

    function refreshDeathTable(){//死人テーブルリライト

        var reasons = ["bite","note","exec","sudden"];
        var reasonflavor = {bite:"無残な姿で発見", note:"死体で発見", exec:"村民協議の結果処刑", sudden:"突然死"}

        deathTable.empty();

        var tr = new Tr()
        tr.add("死因")
        for(var day=1; day<=newestDay; day++){
            tr.add(`${day+1}日目`);
        }
        deathTable.append(tr.text())

        for(var reason of reasons){

            tr = new Tr();
            tr.add(reasonflavor[reason]);

            for(day=1; day<=newestDay; day++){
                var text = "-"
                if(deathlog[day] && deathlog[day][reason]) {
                    text = deathlog[day][reason].map(x => playerInfo[x].name).join("<br>");
                }
                tr.add(text);
            }
            deathTable.append(tr.text());
        }
    }


    //**********************************************
    //   player関係のツール
    //**********************************************

    function makePlayerIndex(){
        playerIndex = {}
        playerInfo.forEach( (player) => {playerIndex[player.name] = player.no;} );
    }

    function getGrayList(){ //灰リスト生成
        var notgray = playerInfo.filter(player => player.job != "gray").map(player => player.no);
        playerInfo.filter(player => player.job == "fortune").forEach(function(player,i){
            for (var jobresult of player.jobresult){
                if (jobresult) notgray.push(jobresult.target);
            }
        })
        var gray = playerInfo.map(player => player.no).filter(no => {return notgray.indexOf(no)<0});
        return gray;
    }

    function getPlayerList(){ //役職結果入力セレクトボックス用のリストを返す
        var l = {99:""};
        for(var player of playerInfo){
            l[player.no] = player.name;
        }
        return l;
    }


    //**********************************************
    //   表示
    //**********************************************

    var switchDispArea = function(mode){//メモのログ/投票表示切り替え
        $("#memoBody > div").hide();
        $("div.tab").removeClass("active")
        $(`#${mode}Area`).show();
        $(`#${mode}DispButton`).addClass("active")
    };

    function switchAliveFilter(){
        setSession("isfilter",isfilter);
        $("div.select.filter").removeClass("active")
        if(isfilter){
            $("#showAliveButton").addClass("active")
        } else {
            $("#showAllButton").addClass("active")
        }
        filteringAlive()
    }

    function switchInputMode(){
        setSession("inputMode",inputMode);
        filteringInputMode()
        $("div.select.inputmode").removeClass("active")
        $(`#input${inputMode}Button`).addClass("active")
    }

    function filterlog(player,day){
        if(player<99){
            $("#discussLogTable tr").hide();
            $(".systemlog").show()
            $(".talk_player"+player).show()
        } else {
            $("#discussLogTable tr").show();
        }

        if(day<99) {
            $("#discussLogTable tbody").hide();
            $("#log_day"+day).show();
        } else {
            $("#discussLogTable tbody").show();
        }
    }

    function filteringAlive(){
        for (var player of playerInfo){
            if(!isfilter || (player.vital == "alive" || player.job != "gray")){
                $(".player_"+player.no).show()
            } else {
                $(".player_"+player.no).hide()
            }
        }
    }

    function filteringInputMode(){
        for(var day=1; day<=newestDay; day++){
            if(inputMode == "full" || (inputMode == "simple" && day == newestDay)){
                $("#result_"+day).show()
            } else {
                $("#result_"+day).hide()
            }
        }
    }

    function coloring(){
        var graylist = getGrayList();
        $("tr.namerow td").removeClass("death").removeClass("gray")
        for (var player of playerInfo){
            if(player.vital == "death"){
                $("tr.namerow .player_"+player.no).addClass("death")
            } else if(graylist.indexOf(player.no)>=0){
                $("tr.namerow .player_"+player.no).addClass("gray")
            }
        }
    }

    function reset(){
        discussLog = [];
        playerInfo = [];
        deathlog = [];
        setSession("discussLog",[])
        setSession("playerInfo",[])
        setSession("deathlog",[])
        newestDay = checkDay();
    }

    //**********************************************
    //   乱数表
    //**********************************************

    function rnd(n,digit){//0~n-1の整数乱数 digitを指定するとゼロパディング
        digit = digit ? digit : false;
        var r = Math.floor(Math.random()*n);
        if(digit) r = padding(r,digit);
        return r;
    }

    function padding(num, digit){ //ゼロパディング
        return ("0000000000"+num).slice(-digit);
    }

    function makernd(){ //四桁乱数
        return rnd(10000,4);
    }

    function makematrix(num){ //乱数表
        var l = [];
        var mat = "";
        for(var i=0; i<num; i++) {
            l[i] = padding(i+1,2);
        }
        for(i=0; i<num; i++){
            var r = rnd(num - i);
            mat += l[r];
            mat += i%5==4 ? "\n" : " / ";
            for(var j = r; j<num-1; j++) {
                l[j] = l[j+1];
            }
        }
        return mat;
    }

    function makerndjob(num){ //役職対応
        var jobs = ["村 人","占い師","霊能者","狩 人","共有者","狂 人","背徳者"];
        var cir = ["①","②","③","④","⑤","⑥","⑦"];
        var result = "";
        var isimo = (num == 15 || num == 19);
        var n = isimo ? 7 : 6
        var i,r;
        if(isimo){
            result += cir[rnd(n)] + "\n\n";
        }
        var l = [0,1,2,3,4,5,6,7,8,9]
        for(i=0; i<n; i++){
            r = rnd(10-i)
            result = result + jobs[i] + ":" + l[r] + rnd(100,2) + "\n"
            for(var j=r; j<=num-1; j++){
                l[j] = l[j+1]
            }
        }
        return result;
    }

    //**********************************************
    //   その他小物
    //**********************************************

    function makeSummary(){
        var color = {"notinput":"?", "white":"○", "black":"●"}
        var table = $("<table></table>",{"id":"summary"})

        var tr = new Tr()
        tr.add("")
        for(var day=1; day<=newestDay; day++){
            tr.add("" + day+1 + "日目")
        }
        tr.appendTo(table)

        var fn = playerInfo.filter((player) => {return player.job == "fortune"})
        fn = fn.concat(playerInfo.filter((player) => {return player.job == "necro"}))

        for (var player of fn) {
            tr = new Tr()
            tr.add(player.name)
            for(day=1; day<=newestDay; day++){
                var name = "", judge = ""
                if(player.jobresult[day] && player.jobresult[day].target != 99){
                    name = playerInfo[player.jobresult[day].target].name
                    judge = color[player.jobresult[day].judge]
                }
                tr.add(name + judge)
            }
            table.append(tr.text())
        }
        console.log(table.html())
    }

    function checkDay(){ //表示されている日付チェック
        var day = /<font size="\+2">(\d{1,2})/.exec(body.html())
        day = day ? day[1] - 1 : 0
        return day;
    }

    function getNewestDay() {//最新の日付を返す(ブラバとかすると単純な日付チェックじゃ対応できない)
        return Math.max(checkDay(),discussLog.length-1);
    }

    function getVillageno(){
        var vno = $("title").text().match(/(\d{6})/)
        return vno[1] - 0
    }

    function createSelectBox(option,selected,id,cl){//optionを持つselectを作る。str。optionは{value: innerHTML}の形式で
        cl = cl || "";
        var s = `<select id="${id}" class="${cl}">`;
        var issl = "";
        for(var i in option) {
            issl = (i==selected) ? "selected" : "";
            s += `<option value="${i}" ${issl}>${option[i]}</option>`;
        }
        s += "</select>";
        return s;
    }

    function editSpeakField(text){ //発言欄をtextにする
        textarea.val(text);
        memoContainer.hide();
    }

    function popupMessage(text){ //メッセージ
        messageArea.text(text).show();
        setTimeout(function(){
            messageArea.hide();
        }, 1500);
    }


    //**********************************************
    //   便利設定
    //**********************************************

    function setAlertVote(){ //未投票アラート
        if( $("font[size=6]").size()){
            $("body").css("background-color","#f8e58c");
            frametable.css("background-color","white");
            setInterval( () => {warningArea.toggle();} , 750);
        }
    }

    function receiveKeyResponse(){ //キー入力を受け付けるかどうか
        $(window).on("keydown",function(e){
            if(setting.send_support.value == "ctrl" && e.ctrlKey && e.keyCode == 13){
                document.forms[0].submit();
            } else if(setting.send_support.value == "shift" && e.shiftKey && e.keyCode == 13){
                document.forms[0].submit();
            }
        });
    }

    function dispSuggest(){
        var colorselect = createSelectBox(COLORLIST,0,"colorlist");
        var coloredit = `<td class="coloredit">アイコン色:</td><td class="coloredit">${colorselect}</td>`;
        var iconsupport = "<td class='iconsupport'><input type='button' id='pasteurl' value='/../../imgbbs/img/'></td>"

        commandtable.find("td").eq(1).after(coloredit).after(iconsupport);
        commandtable.find("td").eq(-2).addClass("cmbplayer")
        commandtable.find("td").eq(-1).addClass("cmbplayer")

        $("#colorlist").on("change",function(){
            editSpeakField($(this).val());
        });
        $("#pasteurl").on("click",function(){
            editSpeakField("/../../imgbbs/img/");
        });
        $("select").eq(0).on("change",function(){
            $(".coloredit").hide();
            $(".iconsupport").hide();
            $(".cmbplayer").hide();
            if($(this).val() == "ICONCHG"){
                $(".coloredit").show();
            } else if($(this).val() == "BCONCHG"){
                $(".iconsupport").show();
            } else {
                $(".cmbplayer").show();
            }
        });
    }

    function highlightDeathnote(){
        var td = infotable.find("td:last");
        if(/アナタの家の前に/.test(td.html())){
            td.css("color","red");
        }
    }

    function setAutoImportLog(){
        if(setting.auto_import_log.value == "alltime"){
            setTimeout(function(){
                importLog()
                refresh();
            },500);
        } else if(setting.auto_import_log.value == "onetime"){
            var d = checkDay();
            if(/<font size="\+2">投票/.test(body.html())){
                setTimeout(function(){
                    importLog()
                    refresh();
                    popupMessage("ログを取り込みました。");
                },500);
            }
        }
    }

    var autoReloadFlg;

    function setAutoReload(){
        autoReloadFlg = setTimeout(function(){
            textarea.val("");
            document.forms[0].submit();
        },setting.autoreload_interval.value * 1000);
    }

    function castASpellOnMe(){
        var q = $("#caspequery").val()
        var num = $("#caspenum").val()
        if (num>10) num=10
        $.post("http://mobajinro.s178.xrea.com/caspe/getWaffle.php",{
            query: q,
            num: num
        },function(data){
            var txt = data.replace(/\n+/g,"<br>")
            var nos = txt.match(/\d{4}/g)
            if(nos){
                for(var no of nos){

                    var src = no
                    if(no-0 > 4674){
                        src = "/../../imgbbs/img/" + no
                    }
                    txt = txt.replace(no,`<img data-no='${no}' src='http://jinrou.dip.jp/~jinrou/img/alive_${src}.gif'>`)
                }
            }
            $("#casperesult").html(txt)
            $("#casperesult img").on("click",function(){
                $("#iconno").val($(this).data("no"))
                $("#casperesult img").removeClass("iconselected")
                $(this).addClass("iconselected")
            })
        })
    }


    //**********************************************
    //   本編
    //**********************************************


    //**********************************************
    //   個人設定の読み込み
    //**********************************************

    var playerInfo = getSession("playerInfo",[]);
    var discussLog = getSession("discussLog",[]);
    var deathlog = getSession("deathlog",[]);
    var isfilter = getSession("isfilter",false);
    var inputMode = getSession("inputMode","simple");
    var villageno = getSession("villageno",0)
    var newestDay;
    var playerIndex;

    makePlayerIndex()
    getSetting();

    setTimeout(function(){ //リロード速度の都合で遅らせる
        newestDay = getNewestDay();
        if(villageno != getVillageno()){
            reset()
            villageno = getVillageno()
            setSession("villageno",villageno)
        }
        refresh();
    },500);

    //**********************************************
    //   cssの追加と書き換え
    //**********************************************
    var content = []

    //見た目変更
    if(setting.rewrite_css.value == "yes"){
        content = [
            '.CLSTABLE tr td:nth-of-type(even) {font-size: 12px;line-height: 110%;padding: 2px;}',
            '.CLSTABLE tr td:nth-of-type(odd) {font-size: 0px;padding: 2px;}',
            'body[bgcolor="#000000"] font[color="#6666aa"] {color: #ccccff;}',
            'font[size="-1"] {font-size: 9pt;}',
            'img {padding: 0;}',
            'input,select {font-size: 9pt;}',
            'table {font-size: 13px;}',
            'textarea {font-family: "Meiryo";font-size: 11px;min-height: 100px;}',
            'table[cellpadding="0"] tr td:nth-of-type(2){word-break:break-all;}'
        ]
    }

    //追加分
    content = content.concat([
        `:root{--theme-color:${setting.theme_color.value}; --sub-color:${subcolor[setting.theme_color.value]};}`,
        "*{box-sizing:border-box;}",
        "#memoContainer{display:none; width:100%; height:100%; position:fixed; top:0px; left:0px; background-color:rgba(180,180,180,0.8); padding:15px; overflow:auto;}",
        "#floatButtonArea{position:fixed; right:15px; top:15px;}",
        "#memoMenu{width:100%; margin: 0 auto; font-size:10px;}",
        "#memoMenu input, #memoMenu select, #buttonArea input{font-size:11px;}",
        "#memoTab{margin-top:15px;}",
        "#memoBody{width:100%; height:calc(100% - 63px); margin: 0 auto; }",
        "#voteArea{display:none;}",
        "#messageArea{width:400px; height:80px; position:fixed; left:50%; transform: translate(-50%, 0); top:15px; display:none;}",
        "#messageArea{text-align:center;font-size:14px; color:black;vertical-align:middle;padding-top:5px;}",
        "#warningArea{width:300px; height:40px; position:fixed; right:15px; bottom:15px; display:none; background-color:red; border-radius:5px; text-align:center; font-size:36px; color:white; font-weight:bold;}",
        "#discussLogTable{border-collapse:collapse;}",
        "#discussLogTable td{text-align:left;vertical-align:top; color:black; word-break:break-all; font-size:10pt; line-height:140%; padding:2px;}",
        "#discussLogTable font{font-size:9pt;}",
        "#discussLogTable tr td:first-of-type{min-width:140px; }",
        "#playerInfoTable a{text-decoration:underline; color:blue; cursor:pointer;}",
        "#voteArea table{font-size:11px; border-collapse:collapse; margin-bottom:10px;color:black;}",
        "#voteArea table td{border:1px solid #ccc; padding:2px; }",
        '.death {background-color:pink;}',
        '.gray {background-color:#e3e3e3;}',
        "#discussLogTable tr.systemlog td{font-weight:bold;background-color:var(--theme-color) !important; color:white; text-align:center;}",
        "#floatButtonArea > div{margin:0px 2px; display:inline-block; vertical-align: top; width:110px;  box-shadow: 0 3px 5px rgba(0, 0, 0, 0.4);}",
        "#toolArea_hid {display:none;}",
        "#setting{font-size:13px; position:fixed; right:10px; bottom:10px; display:none; width:400px; height:300px; background-color:white;}",
        "#setting input[type=number], #setting input[type=text]{width:60px;}",
        ".coloredit, .iconsupport{display:none;}",
        ".voiceloud {padding:0px 5px;}",
        ".voiceloud div:not(:first-of-type){margin-top:5px;}",
        ".voice {width:30px;height:30px;font-size:16px;border:1px solid black; border-radius:2px;background-color:white;line-height:28px;text-align:center;color:black; cursor:pointer;}",
        ".voice.voice_selected{border:3px solid red; line-height:24px;}",
        "#caspe{display:none; position:fixed; right:10px; bottom:10px; width:400px; height:300px; solid #333; overflow:auto; font-size:9pt;}",
        "#caspe input[type=text]{width:100px;}",
        "#caspe input[type=number]{width:50px}",
        "#caspe,#setting, #messageArea{box-shadow: 0 3px 5px rgba(0, 0, 0, 0.4); border-top:16px solid var(--theme-color); background-color:#efefef; padding:10px;}",
        "#floatButtonArea a, .button{width:110px; color:white; background-color:var(--theme-color); cursor:pointer; display:inline-block; font-size:12px; font-weight:bold; line-height:24px; text-align:center;}",
        "div.button{margin:0px 2px; box-shadow: 0 3px 5px rgba(0, 0, 0, 0.4); padding:0px 3px;}",
        ".tab{width:150px; color:white; background-color:var(--theme-color);cursor:pointer;  display:inline-block; font-size:12px; line-height:24px; text-align:center; border-radius:8px 8px 0 0;}",
        ".tab.active{color:var(--theme-color); background-color:white;font-weight:bold;}",
        "#floatButtonArea a:hover, div.button:hover{background-color:var(--sub-color);}",
        ".closebutton{position:absolute; right:5px; top:5px;}",
        "#caspe img{padding:2px;}",
        "#caspe img.iconselected{border:2px solid var(--theme-color); padding:0px;}",
        ".jobinitial{user-select:none; color:var(--theme-color); font-weight:bold; font-size:80%;}",
        ".jobinitial:not(:empty):before{content:'[';}",
        ".jobinitial:not(:empty):after{content:']';}",
        "#discussLogTable td.color_GM{color: #ff9900;}",
    ]);
    if(true){
        content = content.concat([
            "#logArea,#voteArea{width:100%; height:100%; overflow:auto; margin: 0 auto; background-color:white; padding:10px; border-radius:0px 8px 8px 8px ;}",
            "#logArea{display: flex; flex-direction:column; flex-wrap:wrap;}",
            "#playerInfoArea{height:calc(100% - 50px); overflow:auto hidden; width:50%; padding:5px;}",
            "#playerInfoArea select{font-size:8.5pt;}",
            "#buttonArea{height:50px;font-size:12px;  padding:5px;}",
            "#buttonArea > div{ margin-bottom:5px;}",
            "#discussLogArea{width:50%; overflow:auto; padding:5px;}",
            ".select{color:var(--theme-color); border-width:2px 2px 2px 0px ;border-color:var(--theme-color); border-style:solid; background-color:white; cursor:pointer; text-align:center; display:inline-block;width:100px;}",
            ".select.active{color:white;background-color:var(--theme-color); font-weight:bold;}",
            "#buttonArea > div > div.select:first-of-type{margin-left:5px; border-left:2px solid var(--theme-color);}",
            "#buttonArea > div > div.select:lastof-type{margin-right:5px;}",
            "#playerInfoTable{background-color:white; text-align:center; font-size:8pt; color:black; width:100%; margin:0px 2px 2px 0px;}",
            "#playerInfoTable tbody{display:flex; flex-direction:row;border-spacing:0;}",
            "#playerInfoTable tr{display:flex; flex-direction:column; flex:0 0 40px;}",
            "#playerInfoTable tr.namerow{display:flex; flex-direction:column; flex:0 0 80px; position:sticky; left:0;}",
            "#playerInfoTable tr.resultrow{display:flex; flex-direction:column; flex:0 0 130px;}",
            "#playerInfoTable td{border-right:#666 solid 1px;border-bottom:#666 solid 1px;padding:1px; display:block; width:auto; height:1.8em; }",
            "#playerInfoTable tr:first-of-type td{border-left:#666 solid 1px;}",
            "#playerInfoTable tr td:first-of-type{border-top:#666 solid 1px;}",
        ]);
    } else {
        content = content.concat([
            "#logArea,#voteArea{width:100%; max-height:100%; overflow:auto; margin: 0 auto; background-color:white; padding:10px; border-radius:0px 8px 8px 8px ;}",
            "#buttonArea{padding:10px;line-height:24px;font-size:9pt;}",
            "#buttonArea > div{display:inline-block;}",
            "#playerInfoArea{overflow:auto;}",
            ".select{width:100px; color:var(--theme-color); border-width:2px 2px 2px 0px ;border-color:var(--theme-color); border-style:solid; background-color:white; cursor:pointer; display:inline-block; font-size:12px;  line-height:20px; text-align:center;}",
            ".select.active{color:white;background-color:var(--theme-color); font-weight:bold;}",
            "#buttonArea > div > div.select:first-of-type{margin-left:5px; border-left:2px solid var(--theme-color);}",
            "#buttonArea > div > div.select:last-of-type{margin-right:5px;}",
            "#playerInfoTable {background-color:white;text-align:center; font-size:8pt;border-collapse:collapse; color:black; margin:0 auto;}",
            "#playerInfoTable td{border:#666 solid 1px; padding:1px; }",
            "#playerInfoTable tr:first-child{height:35px;}",
            "#playerInfoTable tr td:first-child{min-width:50px;}",
        ]);
    }
    GM_addStyle(content.join("\n"))

    //**********************************************
    //   UI改善
    //**********************************************

    //クソデカ送信ボタン
    var voicebutton = "<td class='voiceloud'><div class='voice' data-value='MSG'>普</div><div class='voice' data-value='MSG2'><strong>強</strong></div><div class='voice' data-value='MSG3'><span style='color:#6666ee;'>弱</span></div></td>";
    var submitbutton = "<td><input type='submit' value='行動/更新' style='height:100px; width:150px;'></td>";
    textareatable.find("td:last").after(submitbutton).after(voicebutton);


    //**********************************************
    //   ツール用のもろもろを追加
    //**********************************************

    //大枠
    var memoContainer = $("<div></div>",{id:"memoContainer"}).appendTo(body);
    var messageArea = $("<div></div>",{id:"messageArea"}).appendTo(body);
    var warningArea = $("<div></div>",{id:"warningArea", text:"!未投票です!"}).appendTo(body);
    var floatButtonArea = $("<div></div>",{id:"floatButtonArea"}).appendTo(body);
    var settingArea = $("<div></div>",{id:"setting"}).appendTo(body);
    var caspe = $("<div></div>",{id:"caspe"}).appendTo(body);


    //共通のボタン部分とその下
    var memoMenu= $("<div></div>",{id:"memoMenu"}).appendTo(memoContainer);
    var memoTab= $("<div></div>",{id:"memoTab"}).appendTo(memoContainer);
    var memoBody= $("<div></div>",{id:"memoBody"}).appendTo(memoContainer);

    //各div
    var logArea = $("<div></div>",{id:"logArea"}).appendTo(memoBody);
    var voteArea = $("<div></div>",{id:"voteArea"}).appendTo(memoBody);
    var playerInfoArea = $("<div></div>",{id:"playerInfoArea"}).appendTo(logArea);
    var buttonArea = $("<div></div>",{id:"buttonArea"}).appendTo(logArea);
    var discussLogArea = $("<div></div>",{id:"discussLogArea"}).appendTo(logArea);

    //テーブル
    var playerInfoTable = $("<table>",{ id:"playerInfoTable" }).appendTo(playerInfoArea);
    var discussLogTable = $("<table>",{ id:"discussLogTable" }).appendTo(discussLogArea);
    var voteTable = $("<table>",{ id:"voteTable" }).appendTo(voteArea);
    var deathTable = $("<table>",{ id:"deathTable" }).appendTo(voteArea);

    //観戦時のみ自動更新ボタン
    if($("td.CLSTD01").eq(1).text() == "◆ 再表示") {
        var isAutoReload = getSession("isAutoReload",0);
        var onoff = isAutoReload ? "ON" : "OFF";
        floatButtonArea.append("<div><a id='autoReload'>自動更新:"+onoff+"</a></div>");
        if(isAutoReload) setAutoReload();

        $("#autoReload").click(function(){
            isAutoReload = 1-isAutoReload
            setSession("isAutoReload", isAutoReload)
            var onoff = isAutoReload ? "ON" : "OFF";
            $("#autoReload").text("自動更新:"+onoff);
            popupMessage("自動更新を"+onoff+"にしました。")

            if(isAutoReload){
                setAutoReload()
            } else {
                clearTimeout(autoReloadFlg);
            }
        });
    }

    floatButtonArea.append("<div id='toolArea'><a >ツール</a><div id='toolArea_hid'><a id='tool1'>四桁乱数</a><a id='tool2'>乱数表</a><a id='tool3'>役職一覧と丸数字</a><a  id='dispcaspe'>きゃすぺ</a><a  id='dispsetting'>設定</a></div></div>");
    floatButtonArea.append("<div><a id='toggleButton'>メモ表示/非表示</a></div>");

    //共通のボタン
    memoMenu.append("<div class='button' id='importButton'>ログの取り込み</div>");
    memoMenu.append("<div class='button' id='resetButton'>リセット</div>");
    memoMenu.append("<div class='button' id='reloadButton'>更新</div>");

    memoTab.append("<div class='tab active' id='logDispButton' data-value='log'>発言ログの表示</div>");
    memoTab.append("<div class='tab' id='voteDispButton' data-value='vote'>投票・死体の表示</div>");

    //各ブロックのボタン
    buttonArea.append("<div>絞り込み<div class='select filter active' id='showAllButton' data-value='off'>全員表示</div><div class='select filter' id='showAliveButton' data-value='on'>生存+役職のみ</div></div>");
    buttonArea.append("<div>役職入力<div class='select inputmode' id='inputnoneButton' data-value='none'>なし</div><div class='select inputmode active' id='inputsimpleButton' data-value='simple'>最新のみ</div><div class='select inputmode' id='inputfullButton' data-value='full'>全日</div></div>");

    var settingTable = $("<table></table>",{id:"settingtable"}).appendTo(settingArea)
    settingArea.append(`<div class="closebutton"><input type="button" onclick="document.getElementById('setting').style.display='none';" value='閉じる'></div>`)

    for(var k in setting){
        var item = setting[k]
        var tr = new Tr()
        tr.add(item.name)
        tr.add(createSelectBox(item.option,item.value,k))
        tr.appendTo(settingTable)
    }

    caspe.html([
        "キーワード:<input type='text' id='caspequery'>数:<input type='number' id='caspenum' value='1'><input type='button' id='castaspellonme' value='きゃすぺ'>",
        "<div id='casperesult'></div>",
        "<input type='button' id='changeicon' value='選択したアイコンを設定'>",
        "<input type='hidden' id='iconno' value=''>",
        `<div class="closebutton"><input type='button' onclick='document.getElementById("caspe").style.display = "none";' value='閉じる'></div>`
    ].join("\n"))

    //**********************************************
    //   個人設定に伴う便利設定
    //**********************************************

    if( setting.auto_import_log.value != "none") setAutoImportLog();
    if( setting.alert_vote.value == "yes" ) setAlertVote();
    if( setting.disp_iconsuggest.value == "yes" ) dispSuggest();
    if( setting.highlight_deathnote.value == "yes" ) highlightDeathnote();
    if( setting.send_support.value != "none") receiveKeyResponse();

    //**********************************************
    //   ボタン類への仕込み
    //**********************************************
    switchInputMode()
    switchAliveFilter()

    $("#toggleButton").on("click",() => {
        memoContainer.toggle();
    });
    $("#importButton").on("click",function(){
        importLog()
        refresh();
    });
    $(".tab").on("click", function(){
        switchDispArea($(this).data("value"));
    } );
    $("#resetButton").on("click",function(){
        if( window.confirm( "ログをすべてリセットします。本当によろしいですか?" ) ) {
            reset()
            refresh()
        }
    });
    $("#reloadButton").on("click",function(){
        textarea.val("");
        document.forms[0].submit();
    });

    $(".select.filter").on("click",function(){
        isfilter = $(this).data("value") == "on"
        switchAliveFilter()
    })

    $(".select.inputmode").on("click",function(){
        inputMode = $(this).data("value");
        switchInputMode()
    });

    $("#toolArea").hover( () => {
        $("#toolArea_hid").show();
    }, () => {
        $("#toolArea_hid").hide();
    });
    $("#tool1").on("click",() => {
        GM_setClipboard(makernd());
        popupMessage("コピーしました:4桁乱数")
    });
    $("#tool2").on("click",() => {
        updateplayerInfo();
        GM_setClipboard(makematrix(playerInfo.length));
        popupMessage("コピーしました:乱数表")
    });
    $("#tool3").on("click",() => {
        updateplayerInfo();
        GM_setClipboard(makerndjob(playerInfo.length));
        popupMessage("コピーしました:役職一覧")
    });
    $("#dispcaspe").on("click", ()=> {
        $("#caspe").show()
    })
    $("#dispsetting").on("click",() => {
        $("#setting").show();
    });
    $("#setting select").on("change",()=>{
        setSetting();
    });
    $("#castaspellonme").on("click",function(){
        castASpellOnMe();
    });
    $("#changeicon").on("click",function(){
        var no = $("#iconno").val()
        if(no=="") return false
        if(no-0 > 4674) no = "/../../imgbbs/img/" + no
        editSpeakField(no)
        $("select").eq(0).val("BCONCHG")
        document.forms[0].submit()
    })

    textarea.focus()

    $(".voice").click(function(){
        $("select").eq(0).val($(this).data("value"));
        $(".voice").removeClass("voice_selected");
        $(this).addClass("voice_selected");
    });

    $(window).on("keydown",function(e){
        if(e.keyCode == 27){
            memoContainer.hide();
        }
    });


})(jQuery);