Use Trello AS Playing Game

You can set the level of the difficulty for every job, and when you complete it, you can get the proper reward.

질문, 리뷰하거나, 이 스크립트를 신고하세요.
// ==UserScript==
// @name         Use Trello AS Playing Game
// @name:zh-CN   将Trello改造为玩游戏做任务
//
// @description  You can set the level of the difficulty for every job, and when you complete it, you can get the proper reward.
// @description:zh-CN  可以在Trello上设定任务难度的级别,并且在完成任务后,发放对应级别的奖励值。
//
// @namespace    http://tampermonkey.net/
// @version      0.2
// @match        https://trello.com/b/*
// @match        https://trello.com/c/*
// @author       oraant
// @grant        none
// ==/UserScript==

// 更新:
// 修复了Trello更新后,无法正常使用的问题。
// 增加了自动触发功能,无需在看板页面等待,现在可以在卡片页面等待了。

window.onload = function(){ // 必须这么搞,否则选择器获取不到东西。decument.ready也不行!

    // ------------------------------------------------------------------------------------------------------------------
    // 前置模块,存放通用的变量
    // ------------------------------------------------------------------------------------------------------------------

    var CustomFields = document.getElementsByClassName("custom-field-detail-item");
    var RandomButtons = document.getElementsByClassName("random-button");
    var WindowWrapper = document.getElementsByClassName("window-wrapper")[0];
    var CheckLists = document.getElementsByClassName("editable non-empty checklist-title");
    var CheckItems = document.getElementsByClassName("checklist-item");
    var CardTitle = document.getElementsByClassName('window-title');
    var LandTitle = document.getElementsByClassName('js-board-editing-target');

    // ------------------------------------------------------------------------------------------------------------------
    // common模块,存放通用的方法函数
    // ------------------------------------------------------------------------------------------------------------------

    // ----------------- 从自定义字段中存取配置 -----------------

    function GetDataDom(field){ // 获取指定的自定义字段的dom
        var dom; var i;
        switch (field){
            case 'Total': i = 0; break;
            case 'Copy': i = 1; break;
            case 'Object': i = 2; break;
            case 'Domain': i = 3; break;
            case 'Monster': i = 4; break;
            case 'Fstwin': i = 5; break;
        }
        dom = CustomFields[i].childNodes[1]
        return dom
    }
    function SetDataDom(field, value){ // 设置指定的自定义字段的内容
        var dataDom = GetDataDom(field);
        if (dataDom.value != value){
            dataDom.value = value;
            dataDom.focus({preventScroll: true});
            dataDom.blur();
        }
    }
    function GetDirectlyData(field){ // 获取指定的自定义字段的内容,若为空则解析为空字符串
        var dataDom = GetDataDom(field)
        var value = dataDom.value?dataDom.value:''
        return value
    }
    function SetDirectlyData(field, data){ // 覆写指定的自定义字段的内容
        var dataDom = GetDataDom(field)
        var value = JSON.stringify(data);
        SetDataDom(field, value);
    }
    function GetInternalData(field){ // 获取指定的自定义字段的内容,若为空则解析为空json
        var dataDom = GetDataDom(field)
        var value = dataDom.value?dataDom.value:'{}'
        return JSON.parse(value);
    }
    function SetInternalData(field, data){ // 覆写指定的自定义字段的内容
        var dataDom = GetDataDom(field)
        var value = JSON.stringify(data);
        SetDataDom(field, value);
    }

    // ----------------- 获取某一DOM的内容中,是否存在符合格式的标记,格式为:ID#LV,比如3#S -----------------
    function GetSigns(dom){
        // 判断标题格式是否正确
        var titles = dom.innerText.split(' ');
        if (titles.length < 2){return []}

        // 若标题格式正确,则判断标记格式是否正确
        var signs = titles[0].split('#');
        if (signs.length != 2){return []}

        // 若标记格式正确,则返回标志列表
        return signs;
    }

    // 获取徽章、最小值、最大值
    function GetConfigure(cls, level){
        // bedge, min, max configuration
        var LandConfiguration = [["🧱", 1, 10], ["💰", 10, 100], ["💿", 100, 1000], ["📀", 1000, 10000], ["💎", 10000, 100000], ["💣", 0, 0]];
        var AreaConfiguration = [["🧱", 1, 10], ["💰", 10, 100], ["💿", 100, 1000], ["📀", 1000, 10000], ["💎", 10000, 100000], ["💣", 0, 0]];
        var CopyConfiguration = [["🧱", 1, 10], ["💰", 10, 100], ["💿", 100, 1000], ["📀", 1000, 10000], ["💎", 10000, 100000], ["💣", 0, 0]];
        var DomainConfiguration = [["⭐", 1, 10], ["🌟", 10, 100], ["🌙", 100, 1000], ["🌝", 1000, 10000], ["🌞", 10000, 100000], ["🌠", 0, 0]];
        var MonsterConfiguration = [["🧱", 1, 10], ["💰", 10, 100], ["💿", 100, 1000], ["📀", 1000, 10000], ["💎", 10000, 100000], ["💣", 0, 0]];

        var ObjectConfiguration = [["🧱", 1, 10], ["💰", 10, 100], ["💿", 100, 1000], ["📀", 1000, 10000], ["💎", 10000, 100000], ["💣", 0, 0]];
        var FstwinConfiguration = [["🌀", 3, 30], ["🌌", 30, 300], ["💧", 300, 3000], ["🔥", 3000, 30000], ["⚡", 30000, 300000], ["💀", 0, 0]];

        var config; var suffix;
        switch(cls){
            case 'Land': config = LandConfiguration; break;
            case 'Area': config = AreaConfiguration; break;
            case 'Copy': config = CopyConfiguration; break;
            case 'Domain': config = DomainConfiguration; break;
            case 'Monster': config = MonsterConfiguration; break;

            case 'Object': config = ObjectConfiguration; break;
            case 'Fstwin': config = FstwinConfiguration; break;
        }
        switch(level){
            case 'S': suffix = 4; break;
            case 'A': suffix = 3; break;
            case 'B': suffix = 2; break;
            case 'C': suffix = 1; break;
            case 'D': suffix = 0; break;
            default: suffix = 5;
        }

        return config[suffix];
    }

    // 获取一个随机数
    function GetRandomNum(min, max){
        return parseInt(Math.random()*(max-min+1)+min,10);
    }

    // 判断按钮的内容是否正确,若正确时还一直插入,会引起栈溢出
    function SetInnerHTML(dom, inner){
        // console.log("正确内容为:" + inner + " 现在内容为:" + dom.innerHTML);
        if (dom.innerHTML != inner){
            dom.innerHTML = inner;
        }
    }

    // 向某一dom中插入新的dom,index为插在第几个后面,0代表最前面。数太大则插在最后面
    function InsertCustomDOM(fdom, ndom, index){ // 旧的文本处理方式
        if (index == 0){
            fdom.innerHTML = ndom + fdom.innerHTML;
        }else if (index>fdom.children.length){
            fdom.innerHTML += ndom
        }else{
            fdom.children[index-1].innerHTML += ndom;
        }
    }
    // 将字符串转为dom
    function parseDom(arg) {
        var objE = document.createElement("div");
        objE.innerHTML = arg;
        return objE.children[0];
    };
    // 像Dom中的最后添加一个新的Dom,并且为新Dom指定一个点击监听器
    function CustomTopButton(head, button, listener){
        if(head.children.length == 1){
            var ButtonDom = parseDom(button);
            ButtonDom.addEventListener("click", listener);
            head.appendChild(ButtonDom);
        };
    }

    // ------------------------------------------------------------------------------------------------------------------
    // 自定义计算总分
    // ------------------------------------------------------------------------------------------------------------------

    var TotalButton = '<a class="random-button card-label button" style="display:inline;margin-left:5px;">🏆🏆🏆</a>'

    function TotalButtonListener(event){
        // 获取所有的内部数据
        var copy_data = GetDirectlyData('Copy');
        var object_data = GetDirectlyData('Object');
        var domain_data = GetInternalData('Domain');
        var monster_data = GetInternalData('Monster');
        var fstwin_data = GetInternalData('Fstwin');

        // 把所有存储的数据加起来
        var number = 0;
        if (copy_data != ''){number += parseInt(copy_data)};
        if (object_data != ''){number += parseInt(object_data)};
        if (domain_data != {}){
            for (var key1 in domain_data){
                number += domain_data[key1]
            }
        }
        if (monster_data != {}){
            for (var key2 in monster_data){
                number += monster_data[key2]
            }
        }
        if (fstwin_data != {}){
            for (var key3 in fstwin_data){
                number += fstwin_data[key3][1]
            }
        }

        // 把数据在内部数据中显示出来
        SetDirectlyData('Total', number);
    }

    function CustomTotalButton(){
        CustomTopButton(CustomFields[0].children[0], TotalButton, TotalButtonListener);
    }

    // ------------------------------------------------------------------------------------------------------------------
    // 自定义副本积分
    // ------------------------------------------------------------------------------------------------------------------

    var CopyButton = '<a class="random-button card-label button" style="display:inline;margin-left:5px;">🎲🎲🎲</a>'

    function CopyButtonListener(event){
        // 获取标志
        var signs = GetSigns(CardTitle[0].children[0]);
        if (!signs.length){return}
        var id = signs[0]; var level = signs[1];

        // 获取数据和配置
        var copy_data = GetDirectlyData('Copy');
        var copy_config = GetConfigure('Copy', level) // 获取配置信息 // todo: 获取级别、ID之类的
        var copy_bedge = copy_config[0]; var copy_min = copy_config[1]; var copy_max = copy_config[2];
        var copy_number = GetRandomNum(copy_min, copy_max);
        SetDirectlyData('Copy', copy_number);
    }

    function CustomCopyButton(){
        CustomTopButton(CustomFields[1].children[0], CopyButton, CopyButtonListener);
    }

    // ------------------------------------------------------------------------------------------------------------------
    // 自定义副本奖励
    // ------------------------------------------------------------------------------------------------------------------

    var ObjectButton = '<a class="random-button card-label button" style="display:inline;margin-left:5px;">🎁🎁🎁</a>'

    function ObjectButtonListener(event){
        // 获取标志
        var signs = GetSigns(CardTitle[0].children[0]);
        if (!signs.length){return}
        var id = signs[0]; var level = signs[1];

        // 获取数据和配置
        var object_data = GetDirectlyData('Object'); // todo:换成object
        var object_config = GetConfigure('Object', level) // 获取配置信息
        var object_bedge = object_config[0]; var object_min = object_config[1]; var object_max = object_config[2];
        var object_number = GetRandomNum(object_min, object_max);
        SetDirectlyData('Object', object_number);
    }

    function CustomObjectButton(){
        CustomTopButton(CustomFields[2].children[0], ObjectButton, ObjectButtonListener);
    }

    // ------------------------------------------------------------------------------------------------------------------
    // 自定义清单
    // ------------------------------------------------------------------------------------------------------------------

    var CheckListButton = '<a class="random-button button subtle hide-on-edit" style="margin:0 0 0 6px;color:#fff;background-color:#f17143;font-weight:bold;">???</a>'

    function CustomCheckListsListener(event){
        var target = event.currentTarget;
        var title = target.parentNode.previousSibling; // 获取标题、ID、级别
        var signs = GetSigns(title);
        if (!signs.length){return}
        var id = signs[0]; var level = signs[1];

        var domain_data = GetInternalData('Domain'); // 获取内部数据
        var domain_config = GetConfigure('Domain', level) // 获取配置信息

        if (typeof(domain_data[id]) != "undefined"){return} // 若已有内部数据则禁止再次生成

        var domain_bedge = domain_config[0]; var domain_min = domain_config[1]; var domain_max = domain_config[2];
        var domain_number = GetRandomNum(domain_min, domain_max);
        domain_data[id] = domain_number; // 更新内部数据

        SetInternalData('Domain', domain_data)
        SetInnerHTML(target, domain_number);
    }

    function CustomCheckLists(){
        if(CheckLists.length == 0){return};
        for (var i=0; i<CheckLists.length; i++){
            // 获取关键DOM、获取检查项的名称DOM、自定义DOM
            var title = CheckLists[i].children[0];
            var option = CheckLists[i].children[1];

            // 获取标记中的信息
            var signs = GetSigns(title);
            if (!signs.length){continue}
            var id = signs[0]; var level = signs[1];

            // 添加自定义按钮
            if(option.children.length == 3){
                var CheckListButtonDom = parseDom(CheckListButton);
                CheckListButtonDom.addEventListener("click", CustomCheckListsListener);
                option.appendChild(CheckListButtonDom)
            }

            // 实时调整其按钮显示的内容
            else if(option.children.length == 4){
                var buttons = option.children; // 获取添加的自定义按钮
                var domain_data = GetInternalData('Domain'); // 获取内部数据
                var bedge = GetConfigure('Domain', level)[0] // 获取配置信息中的图标

                // 计算应正确显示的内容
                var inner = ""; // 按钮要显示的内容
                if (typeof(domain_data[id]) == "undefined"){ // 打开卡片时,若数据中没有相关的数据,则显示礼包按钮
                    inner = bedge;
                }else{ // 若已经有相关数据了,则显示相关数据
                    inner = domain_data[id];
                }

                // 判断按钮的内容是否正确,若正确时还一直插入,会引起栈溢出
                SetInnerHTML(buttons[0], '显');
                SetInnerHTML(buttons[1], '隐');
                SetInnerHTML(buttons[2], '删');
                SetInnerHTML(buttons[3], inner);
            }
        }
    }

    // ------------------------------------------------------------------------------------------------------------------
    // 自定义检查项
    // ------------------------------------------------------------------------------------------------------------------

    var CheckItemTag =    '<span class="oraant-custom card-label"                  style="float:left; max-height:20px;padding:0px 2px;margin:8px 5px;background-color:#e3e7e9;overflow:initial;color:#17394d;">?</span>'
    var CheckItemCoin =   '<span class="oraant-custom card-label card-label-green" style="float:right;max-height:20px;padding:0px 5px;margin:8px 2px;text-overflow:initial;   overflow:initial;font-weight:bold;">?</span>'
    var CheckItemFstwin = '<span class="oraant-custom card-label card-label-sky"   style="float:right;max-height:20px;padding:0px 5px;margin:8px 2px;text-overflow:initial;   overflow:initial;">?</span>'

    // 自定义检查项后的信息
    function CustomCheckItems(){
        if(CheckItems.length == 0){return};

        for (var i=0; i<CheckItems.length; i++){
            // 获取详情DOM、获取检查项的名称DOM、自定义DOM
            var detail = CheckItems[i].children[1].children[0];
            var cbtag, cbtext, cbcoin, cbfstw;
            var signs, id, level;

            // 计算应正确显示的内容
            // 添加自定义按钮
            if(detail.children.length == 2){
                // 单独验证格式是否合适,因为插入的原因,两次cbtext的位置是不一样的
                cbtext = detail.children[0];
                signs = GetSigns(cbtext);
                if (!signs.length){continue}

                InsertCustomDOM(detail, CheckItemTag, 0)
                InsertCustomDOM(detail, CheckItemCoin, 10)
                InsertCustomDOM(detail, CheckItemFstwin, 20)
            }

            // 实时调整其按钮显示的内容(之前那些DOM里Onclick的功能,也要做到这里面来。因为这个不是个按钮,不需要去按。)
            else if(detail.children.length == 5){
                // 获取各组件的dom
                cbtag = detail.children[0];
                cbtext = detail.children[1];
                cbcoin = detail.children[3];
                cbfstw = detail.children[4];

                // 单独验证格式是否合适,因为插入的原因,两次cbtext的位置是不一样的
                signs = GetSigns(cbtext);
                if (!signs.length){continue}
                id = signs[0]; level = signs[1];

                // console.log("->内容全面,判断是否要进行修正");
                var state = CheckItems[i].getAttribute("class"); // 获取类属性
                var monster_data = GetInternalData('Monster'); // 获取内部数据
                var fstwin_data = GetInternalData('Fstwin'); // 获取内部数据
                var cbcoin_config = GetConfigure('Monster', level) // 获取配置信息
                var cbfstw_config = GetConfigure('Fstwin', level) // 获取配置信息
                var cbcoin_bedge = cbcoin_config[0]; var cbcoin_min = cbcoin_config[1]; var cbcoin_max = cbcoin_config[2];
                var cbfstw_bedge = cbfstw_config[0]; var cbfstw_min = cbfstw_config[1]; var cbfstw_max = cbfstw_config[2];

                var cbcoin_inner = ""; // 金币标签要显示的内容
                var cbfstw_inner = ""; // 首胜标签要显示的内容
                SetInnerHTML(cbtag, cbfstw_bedge); // 不管是否勾选,都在前面显示图标

                if (state == "checklist-item"){ // 若未勾选,则根据难度,显示图标
                    cbcoin_inner = cbcoin_bedge;
                    cbfstw_inner = '';
                }else if(state.search("checklist-item-state-complete") != -1){ // 若已勾选 // todo:这里应该能去掉,和下面的一起if
                    // 计算首胜标签应正确显示的内容
                    // ----------------------------------------

                    // 判断今天的有没有记录过(必须得在coin前边,需要根据coin的状态,判断是否是已经有数据的)
                    // 若已勾选但没有今日数据,且这个检查项也没有存过数据(否则昨天加了11号的后,今天还会加11号的首胜),则在数据栏中添加数据
                    var options = {year: 'numeric', month: 'numeric', day: 'numeric' };
                    var date = new Date().toLocaleDateString('ch-zh', options);
                    if (typeof(fstwin_data[date]) == "undefined" && typeof(monster_data[id]) == "undefined"){
                        var cbfstw_number = GetRandomNum(cbfstw_min, cbfstw_max); // 每日首胜奖励+3倍
                        fstwin_data[date] = [id, cbfstw_number];
                        SetInternalData('Fstwin', fstwin_data)
                    }

                    // 根据以前的数据,将首胜信息展示出来
                    for (var k in fstwin_data){
                        if(fstwin_data[k][0] == id){ // 若已经有今日数据了,且是这个ID,则显示相关数据
                            cbfstw_inner = fstwin_data[k][1];
                        }
                    }

                    // 计算金币标签应正确显示的内容
                    // ----------------------------------------
                    if (typeof(monster_data[id]) == "undefined"){ // 已勾选但未曾保存数据,则插入数据
                        var cbcoin_number = GetRandomNum(cbcoin_min, cbcoin_max);
                        monster_data[id] = cbcoin_number;
                        SetInternalData('Monster', monster_data)
                        cbcoin_inner = cbcoin_number
                    }else{ // 若已经有相关数据了,则显示相关数据
                        cbcoin_inner = monster_data[id];
                    }

                }else{console.log('很奇怪,检查项的类属性和预期的不同:'+state)}

                SetInnerHTML(cbfstw, cbfstw_inner);
                SetInnerHTML(cbcoin, cbcoin_inner);
                if(cbfstw_inner == ""){
                    cbfstw.style.display = "none";
                }else{
                    cbfstw.style.display = "initial";
                }
            }
        }
    }

    // ------------------------------------------------------------------------------------------------------------------
    // 程序入口
    // ------------------------------------------------------------------------------------------------------------------

    var callback = function (records){
        // 检查看板的标题是否符合格式
        if(LandTitle.length == 0){return};
        var title_signs = GetSigns(LandTitle[0]);
        if (!title_signs.length){return}

        // 校验是否有自定义域
        if(CustomFields.length == 0){return};

        // 获取判断卡片标题是否符合要求
        var card_signs = GetSigns(CardTitle[0].children[0]);
        if (!card_signs.length){return}

        CustomTotalButton()
        CustomCopyButton()
        CustomObjectButton()

        CustomCheckLists()
        CustomCheckItems()

        console.log('看看能不能输出日志')
    };
    var mo = new MutationObserver(callback);
    mo.observe(WindowWrapper, {'childList': true, 'subtree': true}); // 设置一个监听器,页面由变化就触发。
    callback(); // 如果直接打开一个页面的话,默认监听器不会被触发。这时手动触发一次就很有必要了。
};