dev_group_list2

Better Submit-to-group dialog

当前为 2020-08-17 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         dev_group_list2
// @namespace    http://www.deviantart.com/
// @version      3.2
// @description  Better Submit-to-group dialog
// @author       Dediggefedde
// @match        https://www.deviantart.com/*
// @require      https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js
// @require      http://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
// @grant        GM.xmlHttpRequest
// @grant        GM.setValue
// @grant        GM.getValue

// ==/UserScript==
/* globals $*/

//dgl2=dgl2 identifiert/classname
(function () {
    'use strict';
    var userName = "";
    var moduleID = 0;
    var grPerReq = 24;
    var groups = []; //{userId,useridUuid,username,usericon,type,isNewDeviant}
    // isNewDeviant not needed.
    // type=oneof{group, super group}
    // usericon always starts with https://a.deviantart.net/avatars-big
    // useridUuid specific to the group, contains submission rights
    // userid of the group
    var groupN = 0;
    var listedGroups = [];
    var devID;
    var collections = [{
        id: 0,
        name: "all",
        groups: [],
        showing: 1
    }];
    var collectionOrder = [];
    var colMode = 0; //0 show, 1 add, 2 remove, 3 delete
    var colModeTarget = 0;
    var macros = []; //{id, name, data:[{folderName, folderID, groupID, type}]}
    var macroOrder = [];
    var macroMode = 0; //0 idle, 1 record, 2 play, 3 remove
    var macroModeTarget = 0; //for recording
    var lastFilter = 0;
    var colListMode = 0; //0 collection, 1 macro;
    var svgTurnArrow = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1.507856 1.2692268" style="height:0.5em;"> <defs id="defs2"> <marker id="Arrow2Send" style="overflow:visible"> <path id="path8454" style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.625;stroke-linejoin:round;stroke-opacity:1" d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z" transform="matrix(-0.3,0,0,-0.3,0.69,0)" /> </marker> </defs> <g id="layer1" transform="translate(-1.2565625,-0.79875001)"> <path style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:0.26499999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#Arrow2Send)" d="m 2.38125,0.93125 c -1.3229167,0 -1.3229166,0.79375 0,0.79375" id="path8419"/> </g> </svg> ';
    var fetchingGroups=false; //prevent refresh button requesting multiple times at once
    var entityMap = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#39;',
        '/': '&#x2F;',
        '`': '&#x60;',
        '=': '&#x3D;'
    };
    var loadedFolders=new Map();
    var lastGroupClickID;

    //API calls getting data
    function fillGroups(offset) { //async+callback //load all groups
        //console.log("https://www.deviantart.com/_napi/da-user-profile/api/module/groups/members?username=" + userName + "&moduleid=" + moduleID + "&offset=" + offset + "&limit=" + grPerReq);
        return new Promise(function (resolve, reject) {
            GM.xmlHttpRequest({
                method: "GET",
                url: "https://www.deviantart.com/_napi/da-user-profile/api/module/groups/members?username=" + userName + "&moduleid=" + moduleID + "&offset=" + offset + "&limit=" + grPerReq,
                onerror: function (response) {
                    reject(response);
                },
                onload: function (response) {
                    if(response.status==500){
                        resolve(oldFillGroups(offset));
                        //reject(response.status);
                        //console.log(response);
                        return
                    }
                    var resp = JSON.parse(response.responseText);

                    if(offset==0)groups = [];
                    groups = groups.concat(resp.results);
                    groupN = resp.total;

                    var frac = 0;
                    if (groupN > 0) frac = groups.length / groupN * 100;
                    frac = Math.round(frac);

                    $("#dgl2_refresh rect").css("fill", "url(#dgl2_grad1)");
                    $("#dgl2_grad1_stop1").attr("offset", frac + " %");
                    $("#dgl2_grad1_stop2").attr("offset", (frac + 1) + " %");
                    $("#dgl2_refresh").attr("title", frac + " %");
                    $("span.dgl2_descr").text("Loading List of Groups... " + frac + " %");

                    if (resp.hasMore) {
                        resolve(fillGroups(resp.nextOffset));
                    } else {
                        GM.setValue("groups", JSON.stringify(groups));
                        insertGroups();
                        $("#dgl2_refresh rect").css("fill", "");
                        $("#dgl2_refresh").css("cursor", "pointer");

                        resolve(groups);
                    }
                }
            });
        });
    }

    var ngroups=[];
    function oldFillGroups(offset){ //only fills names and icons
        return new Promise(function(resolve,reject){
            GM.xmlHttpRequest({
                method: "GET",
                url: "https://www.deviantart.com/"+userName+"/modals/mygroups/?offset=" + offset + "&limit=100",
                onerror: function (response) {
                    reject(response);
                },
                onload: function (response) {

                    var tgroups=response.responseText.split("<div class=\"mygroup")
                    var rex=/<img.*?class="avatar".*?src="(.*?)".*?title="(.*?)"/i
                    var rex2=/data-gruser-type="(.*?)"/i

                    tgroups=tgroups.map(str=>{ //parse websites to group data
                        var ret={};
                        var mat=str.match(rex);
                        if(mat==null)return null;
                        ret.usericon=mat[1];
                        ret.username=mat[2];
                        ret.type=str.match(rex2)[1];
                        return ret;
                    }).filter(el=>el!=null);
                    ngroups = ngroups.concat(tgroups);

                    $("#dgl2_refresh rect").css("fill", "url(#dgl2_grad1)");
                    $("span.dgl2_descr").text("Loading List of Groups... ? %");

                    if(response.responseText.indexOf("class=\"next disabled")==-1){ //next not disabled = new page available
                        resolve(oldFillGroups(offset+100));
                    }else{
                        ngroups.forEach(el=>{ //new groups not previously in list
                            if(el==null)return;
                            for(var gr of groups){
                                if(gr.username==el.username){
                                    el=gr; //copy old information to prevent id/uuid loss
                                }
                            }
                        });
                        groups=ngroups;
                        ngroups=[];

                        GM.setValue("groups", JSON.stringify(groups));
                        insertGroups();
                        $("#dgl2_refresh rect").css("fill", "");
                        $("#dgl2_refresh").css("cursor", "pointer");
                        resolve(groups);
                    }
                }
            });
        });
    }
    function grabIDfromPage(name){
        return new Promise(function (resolve, reject) {
            GM.xmlHttpRequest({
                method: "GET",
                url: "https://www.deviantart.com/" + name,
                onerror: function (response) {
                    reject(response);
                },
                onload: async function (response) {
                    //console.log(response);
                   // console.log(response.responseText);

                    var rex=/itemid":(.*?),"friendid":"(.*?)"/i;
                    var mat = response.responseText.match(rex);
                    if(mat==null){reject(0);return;}

                    groups.forEach(gr=>{
                        if(gr.username==name){
                            gr.userId=mat[1];
                            gr.useridUuid=mat[2];
                        }
                    });
                    GM.setValue("groups", JSON.stringify(groups));
                    resolve(mat[1]);
                }
            });
        });
    }
    function fillSubFolder(groupID, type,name) { //async+callback //type =[collection,gallery]

        if(groupID == "undefined"){
            $(".dgl2_groupdialog ").css("cursor", "wait");
            $(".dgl2_groupButton").css("cursor", "wait");
            return grabIDfromPage(name).then(id=>{
                groupID=id;
                lastGroupClickID=id;
                return fillSubFolder(groupID, type,name);
            }).catch(erg=>{
                console.log("Error at folder ID fetch "+name + " " +erg);
                $(".dgl2_groupdialog ").css("cursor", "pointer");
                           return null;
            });
        }

        return new Promise(function (resolve, reject) {
            GM.xmlHttpRequest({
                method: "GET",
                url: "https://www.deviantart.com/_napi/shared_api/deviation/group_folders?groupid=" + groupID + "&type=" + type,
                onerror: function (response) {
                    reject(response);
                },
                onload: async function (response) {
                    //console.log(response);
                    var resp;
                    try {
                        resp = JSON.parse(response.responseText);
                    } catch (ex) {
                        myAlert("Page problems. Please try again later!<br/>" + ex, "Error");
                    }

                    insertSubFolders(resp.results);
                    if(macroMode==1){
                        for(var gr of macros[macroModeTarget].data){
                            if(gr.groupID==groupID){
                                $("button.dgl2_groupButton[folderID='"+gr.folderID+"']").addClass("folderInMacro");
                                break;
                            }
                        }
                    }
                    $(".dgl2_groupdialog").css("cursor", "");
                    $(".dgl2_groupButton").css("cursor", "pointer");
                    $("div.groupPopup").focus();
                    resolve(resp.results);

                }
            });
        });
    }

    function fillModuleID() { //async+callback //get Module ID for group submission
        return new Promise(function (resolve, reject) {
            GM.xmlHttpRequest({
                method: "GET",
                url: "https://www.deviantart.com/" + userName + "/about",
                onerror: function (response) {
                    reject(response);
                },
                onload: async function (response) {
                    try {
                        var resp = (response.responseText);
                        resp = resp.match(/{\\\"id\\\":(\d+),\\\"type\\\":\\\"group_list_members/i);
                        if (resp.length > 1) {
                            moduleID = resp[1];
                            resolve(moduleID);
                        } else {
                            reject(response);
                        }
                    } catch (ex) {
                        reject([ex, response]);
                    }
                }
            });
        });
    }

    function fillListedGroups(devID) {
        return new Promise(function (resolve, reject) {
            GM.xmlHttpRequest({
                method: "GET",
                url: "https://www.deviantart.com/deviation/" + devID + "/groups",
                onerror: function (response) {
                    reject(response);
                },
                onload: async function (response) {
                    var tex = response.responseText;
                    var res = tex.matchAll(/gmi-groupid="(\d+)"/gi);

                    for (var entr of res) {
                        listedGroups.push(parseInt(entr[1]));
                    }
                    resolve(listedGroups);
                }
            });
        });

    }

    function requestAddSubmission(token, devID, folderID, groupID, type) { //async+callback //type =[collection,gallery]
        var macroFchanged=false;
        if (macroMode == 1) {
            if(macros[macroModeTarget].data.some(e => e.groupID === groupID)){
                macros[macroModeTarget].data.forEach(function(el){
                    if(el.groupID===groupID){
                        el.folderID=folderID;
                        el.folderName=loadedFolders.get(folderID);
                    }
                });//change folder of present group
                macroFchanged=true;
            }else{//don't add if included already
                macros[macroModeTarget].data.push({
                    folderName:loadedFolders.get(folderID),
                    folderID: folderID,
                    groupID: groupID,
                    type: type
                });
            }
            GM.setValue("macros", JSON.stringify(macros));
        }

        return new Promise(function (resolve, reject) {
            var dat = {
                "groupid": parseInt(groupID),
                "type": type.toString(),
                "folderid": parseInt(folderID),
                "deviationid": parseInt(devID),
                "csrf_token": token.toString()
            };
            if (macroMode == 1){ //don't submit while adding to macros
                resolve({success:true, gname:groupById(groupID).username, fname:loadedFolders.get(folderID),fchanged:macroFchanged});
            }else{
                GM.xmlHttpRequest({
                    method: "POST",
                    url: "https://www.deviantart.com/_napi/shared_api/deviation/group_add",
                    headers: {
                        "accept": 'application/json, text/plain, */*',
                        "content-type": 'application/json;charset=UTF-8'
                    },
                    dataType: 'json',
                    data: JSON.stringify(dat),
                    onerror: function (response) {
                        response.gname = groupById(groupID).username;
                        reject(response);
                    },
                    onload: async function (response) {
                        var resp = JSON.parse(response.responseText);
                        resp.gname = groupById(groupID).username;
                        resolve(resp);
                    }
                });
            }
        });
    }


    function playMacro(index) {
        macroMode = 2;
        var promises = [];
        var token = $("input[name=validate_token]").val();
        for (var d of macros[index].data) {
            promises.push(requestAddSubmission(token,devID, d.folderID, d.groupID, d.type));
        }
        Promise.all(promises).catch(err => {
            alert(macros[index].name + " Error!<br/>" + JSON.stringify(macros[index].data) + " " + JSON.stringify(err), "Error");
        }).then(res => {
            myAlert(
                res.map(obj => {
                    var retval = "<strong>" + obj.gname + "</strong>: "
                    if (obj.success) {
                        retval += "Success! ";
                        if (obj.needsVote == true) retval += " Vote pending";
                    } else {
                        retval += "Error! ";
                        if (obj.errorDetails) retval += obj.errorDetails;
                    }
                    return retval;
                }).join("<br/>"), "Play Macro " + macros[macroModeTarget].name);
        })
        macroMode = 0;
    }
    //event handlers
    function Ev_groupClick(event) { //event propagation
        event.stopPropagation();

        var targetBut = $(event.target).closest(".dgl2_groupButton");
        var groupID = targetBut.attr("groupID");
        var groupNam = targetBut.attr("groupname");

        if(groupID=="undefined"){
            grabIDfromPage(groupNam).then(id=>{
                $(event.target).closest(".dgl2_groupButton").attr("groupID",id);
                Ev_groupClick(event);
            });
            return;
        }
        var elInd;
        switch (colMode) {
            case 1: //add
                elInd = collections[colModeTarget].groups.indexOf(groupID);
                if (elInd == -1) {
                    collections[colModeTarget].groups.push(groupID);
                    GM.setValue("collections", JSON.stringify(collections));
                    targetBut.addClass("dgl2_inCollection");
                }
                break;
            case 2: //remove
                switch (colListMode) {
                    case 0: //collection
                        elInd = collections[colModeTarget].groups.indexOf(groupID);
                        if (elInd > -1) {
                            collections[colModeTarget].groups.splice(elInd, 1);
                            GM.setValue("collections", JSON.stringify(collections));
                        }
                        insertFilteredGroups(colModeTarget);
                        break;
                    case 1: //macro
                        for (elInd = 0; elInd < macros[macroModeTarget].data.length; ++elInd) {
                            if (macros[macroModeTarget].data[elInd].groupID == groupID) break;
                        }

                        if (elInd < macros[macroModeTarget].data.length) {
                            macros[macroModeTarget].data.splice(elInd, 1);
                            GM.setValue("macros", JSON.stringify(macros));
                        }
                        insertMacroGroups(macroModeTarget);
                        break;
                }
                break;
            case 0:
            default:
                if (targetBut.attr("type") == "group") {
                    lastGroupClickID=targetBut.attr("groupID");
                    fillSubFolder(groupID, "gallery",groupNam);
                    $("span.dgl2_titleText").text("< Add to " + targetBut.attr("groupName"));
                    $("span.dgl2_descr").text("Add this deviation to a group folder");
                    //Add this deviation to a group folder
                } else if (targetBut.attr("type") == "folder") {
                    var token = $("input[name=validate_token]").val();
                    requestAddSubmission(token, devID, targetBut.attr("folderID"), targetBut.attr("groupID"), targetBut.attr("folderType")).then(function (arg) {
                        if (arg.success == true) {
                            if(macroMode==1){
                                if(arg.fchanged){
                                    myAlert(arg.gname+" target folder changed to "+arg.fname, "Info");
                                }else{
                                    myAlert(arg.gname+"/"+arg.fname+" added to macro", "Info");
                                }
                                insertMacros();//update titles
                                insertGroups();//go back to groups view
                            }else{
                                if (arg.needsVote) {
                                    myAlert("Success! Submission pending group's vote", "Info");
                                } else {
                                    myAlert("Success! Submission added to group", "Info");
                                }
                            }
                        } else {
                            // myAlert("Error! Something went wrong:<br/>devID: "+devID+"<br/>" + JSON.stringify(arg), "Error");
                            throw arg;
                        }
                        /*
                        deviationGroupCount: 1
                        needsVote: true
                        */
                    }).catch(function (arg) {
                        console.log("dev_group_list_2 Error: ",arg);
                        myAlert("deviation-ID: "+devID+"<br/>"+
                                "Group-Name: "+(arg.gname?arg.gname:"Unknown")+"<br/>"+
                                (arg.errorDescription?arg.errorDescription:"Unexpected error.")+"<br/>"+
                                (arg.errorDetails?arg.errorDetails:JSON.stringify(arg)),
                                (arg.error?arg.error:"Error"));
                    });
                }
        }
    }
    function Ev_ContextSubmit(event){
        event.stopPropagation();
        event.preventDefault();
        event.target=$("#dgl2_grContext select option:selected").get(0);
        Ev_groupClick(event);
        $("#dgl2_grContext").hide().find("select").empty();
    }
    function Ev_groupContext(event){
        event.stopPropagation();
        event.preventDefault();
        var el=$("#dgl2_grContext");
        if(el.length==0){
            el=$("<div id='dgl2_grContext'><span class='desc'>Submit to a Folder</span><br /><select size=5></select><br/><button>Submit</button></div>").appendTo(document.body);
            el.find("button").click(Ev_ContextSubmit);
        }
        el.find("select").hide();
        el.finish().show().css({
            top: event.pageY + "px",
            left: event.pageX + "px"
        });
        var groupID=$(event.target).closest("button.dgl2_groupButton").attr("groupid")
        fillSubFolder(groupID, "gallery",$(event.target).closest("button.dgl2_groupButton").attr("groupname")).then(function(){
            el.find("select").show().focus().get(0).selectedIndex = 0;
        });

    }

    function switchColList() {
        switch (colListMode) {
            case 0:
                colListMode = 1;
                $("span.dgl2_CollTitle").html("Macros");
                insertMacros();
                break;
            case 1:
                $("span.dgl2_CollTitle").html("Collections");
                colListMode = 0;
                insertCollections();
        }
    }

    function highlightLetter(which){

        $(".dgl2_letterfound").removeClass("dgl2_letterfound");
        $(".dgl2_groupdialog button.dgl2_groupButton[groupName^='"+which+"' i]").addClass("dgl2_letterfound").focus();
        $(".dgl2_groupdialog button.dgl2_groupButton[folderName^='"+which+"' i]").addClass("dgl2_letterfound").focus();
    }

    function Ev_colListClick(event) {
        event.stopPropagation();

        if ($(event.target).closest(".dgl2_CollTitleBut").length > 0) {
            switchColList();
        }
        var id = $(event.target).closest("li").first().attr("colID");
        if (id == undefined && $(event.target).closest("button").hasClass("dgl2_topBut")) id = 0;
        else if (id == undefined) return;
        var clasNam = $(event.target).closest("button").attr("class");
        var index;
        if (colListMode == 0) index = colIndexById(id);
        else if (colListMode == 1) index = makIndexById(id);
        $("div.dgl2_groupWrapper").removeClass("dgl2_addGroup").removeClass("dgl2_remGroup");
        $("button.dgl2_inCollection").removeClass("dgl2_inCollection");
        var el;

        if (clasNam) clasNam = clasNam.replace(" dgl2_topBut", "");
        switch (clasNam) {
            case "dgl2_export":
                var obj = {
                    collections: collections,
                    collectionOrder: collectionOrder,
                    macros: macros,
                    macroOrder: macroOrder
                };
                var d = new Date();
                var dat = d.getFullYear() + ("0" + d.getMonth()).slice(-2) + ("0" + d.getDate()).slice(-2) + "-" + ("0" + d.getHours()).slice(-2) + ("0" + d.getMinutes()).slice(-2) + ("0" + d.getSeconds()).slice(-2);
                download(JSON.stringify(obj), "dev_group_list2_data_" + dat + ".txt");
                break;
            case "dgl2_import":
                upload().then(function (imp) {
                    try {
                        var obj = JSON.parse(imp);
                        if (obj.macros && obj.macroOrder) {
                            macros = obj.macros;
                            macroOrder = obj.macroOrder;
                        }
                        if (obj.collections && obj.collectionOrder) {
                            collections = obj.collections;
                            collectionOrder = obj.collectionOrder;
                        } else if (obj[0] && obj[0][0].indexOf("_grouplist") != -1) { //v1 compatibility mode
                            collections = [{
                                id: 0,
                                name: "all",
                                groups: [],
                                showing:1
                            }];
                            var oldList = obj[1][1].split("\u0002");
                            for (var lists of oldList) {
                                var oldCol = lists.split("\u0001");
                                var newCol = [];
                                for (var nams of oldCol) {
                                    el = $("button[groupName='" + nams + "']").attr("groupID");
                                    if (el != undefined) {
                                        newCol.push(el);
                                    }
                                }
                                collections.push({
                                    id: getLowestFree(collections),
                                    name: oldCol[0],
                                    groups: newCol,
                                    showing:1
                                });
                            }
                        } else {
                            throw "No collections found!";
                        }
                    } catch (ex) {
                        myAlert("Not a valid dev_group_list2 file!<br/>" + ex, "Error");
                        return;
                    }

                    GM.setValue("collectionOrder", JSON.stringify(collectionOrder));
                    GM.setValue("collections", JSON.stringify(collections));
                    GM.setValue("macroOrder", JSON.stringify(macroOrder));
                    GM.setValue("macros", JSON.stringify(macros));
                    myAlert("Import successfull!", "Info");
                    insertGroups();
                    insertCollections();
                    if (colListMode != 0) switchColList();
                });
                break;
            case "dgl2_add":
                insertGroups();
                switch (colListMode) {
                    case 0: //collection
                        $("span.dgl2_descr").text("Add groups to the collection " + collections[index].name);
                        colMode = 1;
                        colModeTarget = index;
                        $("div.dgl2_groupWrapper").addClass("dgl2_addGroup");
                        for (el of collections[index].groups) {
                            $("button.dgl2_groupButton[groupID=" + el + "]").addClass("dgl2_inCollection");
                        }
                        break;
                    case 1:
                        colMode = 0;
                        insertGroups();
                        $("div.dgl2_groupWrapper").addClass("dgl2_addGroup");
                        $("span.dgl2_descr").text("macro " + macros[index].name + " is recording.");
                        macroMode = 1;
                        macroModeTarget = index;
                        break;
                }
                break;
            case "dgl2_sub":
                switch (colListMode) {
                    case 0: //collection
                        insertFilteredGroups(index);
                        $("span.dgl2_descr").text("Remove groups from the collection " + collections[index].name);
                        colMode = 2;
                        colModeTarget = index;
                        $("div.dgl2_groupWrapper").addClass("dgl2_remGroup");
                        break;
                    case 1: //macro
                        insertMacroGroups(index);
                        $("span.dgl2_descr").text("Remove groups from the macro " + macros[index].name);
                        colMode = 2;
                        macroModeTarget = index;
                        $("div.dgl2_groupWrapper").addClass("dgl2_remGroup");
                        break;
                }
                break;
            case "dgl2_del":
                var con;
                switch (colListMode) {
                    case 0: //collection
                        myConfirm("Delete Collection " + collections[index].name + " ?", "Collection").then(con => {
                            if (!con) return;
                            collections.splice(index, 1);
                            collectionOrder.splice(collectionOrder.indexOf("dgl2item-" + id), 1);

                            GM.setValue("collectionOrder", JSON.stringify(collectionOrder));
                            GM.setValue("collections", JSON.stringify(collections));

                            insertGroups();
                            insertCollections();
                        });
                        break;
                    case 1: //macro
                        myConfirm("Delete Macro " + collections[index].name + " ?", "Macro").then(con => {
                            if (!con) return;
                            macros.splice(index, 1);
                            macroOrder.splice(macroOrder.indexOf("dgl2item-" + id), 1);

                            GM.setValue("macroOrder", JSON.stringify(macroOrder));
                            GM.setValue("macros", JSON.stringify(macros));

                            insertGroups();
                            insertMacros();
                        });
                        break;
                }
                break;
            case "dgl2_new":
                switch (colListMode) {
                    case 0: //collection
                        el = {
                            name: "New Collection",
                            groups: [],
                            showing:1
                        };
                        el.id = getLowestFree(collections);
                        collections.push(el);
                        collectionOrder.push("dgl2item-" + el.id);

                        GM.setValue("collectionOrder", JSON.stringify(collectionOrder));
                        GM.setValue("collections", JSON.stringify(collections));

                        insertGroups();
                        insertCollections();
                        break;
                    case 1: //macros
                        el = {
                            name: "New Macro",
                            data: []
                        };
                        el.id = getLowestFree(macros);
                        macros.push(el);
                        macroOrder.push("dgl2item-" + el.id);

                        GM.setValue("macroOrder", JSON.stringify(macroOrder));
                        GM.setValue("macros", JSON.stringify(macros));

                        insertGroups();
                        insertMacros();
                        break;
                }
                break;
            case "dgl2_visible":

                switch (colListMode) {
                    case 0: //collection
                        if(!collections[index].hasOwnProperty("showing"))collections[index].showing=0;
                        else collections[index].showing=1-collections[index].showing; //toggle 0 and 1
                        GM.setValue("collections", JSON.stringify(collections));

                        $(event.target).closest("li").attr("active",collections[index].showing);
                        insertGroups();
                        break;
                    case 1: //macro
                        //donothing
                        break;
                }
                break;
            case "dgl2_edit":
                var nam;
                switch (colListMode) {
                    case 0: //collection
                        myPrompt("Please enter a new collection name!", "Change Collection Name", collections[index].name).then(nam => {
                            if (!nam) return;
                            collections[index].name = nam;
                            GM.setValue("collections", JSON.stringify(collections));
                            insertCollections();
                        });
                        break;
                    case 1: //macro
                        myPrompt("Please enter a new macro name!", "Change Macro Name", macros[index].name).then(nam => {
                            if (!nam) return;
                            macros[index].name = nam;
                            GM.setValue("macros", JSON.stringify(macros));
                            insertMacros();
                        });
                        break;
                }
                break;
            case undefined:
            default:
                switch (colListMode) {
                    case 0: //collection
                        colMode = 0;
                        insertFilteredGroups(index);
                        break;

                    case 1: //macro
                        myConfirm("Do you want to add this to the following groups?<br/>" + macros[index].data.map(obj => {
                            return groupById(obj.groupID).username
                        }).join(", "), "Submit to Groups").then(con => {
                            if (!con) return;
                            macroMode = 2;
                            playMacro(index);
                        });
                        break;
                }

        }
    }

    function Ev_getGroupClick() {

        if(fetchingGroups)return;
        fetchingGroups=true;

        $("span.dgl2_descr").text("Loading Module ID...");
        $("#dgl2_refresh").css("cursor", "pointer");
        fillModuleID().then(function () {
            $("span.dgl2_descr").text("Loading List of Groups...");
            $("#dgl2_refresh").css("cursor", "wait");
            return fillGroups(0);
        }).then(function () {
            $("span.dgl2_descr").text("Add this deviation to one of your groups");
        }).catch(function (e) {
            console.log("dev_group_list_2 error:", e);
        }).finally(function(){
            fetchingGroups=false;
        });
    }
    //templates
    function getGroupTemplate(name, img, id) { //return HTML string
        //return "<button groupID="+id+" type='group' groupName="+name+" class='_3UhBt _3PBqc _3PBqc dgl2_groupButton'><div class='_1cFkE _11Rtb _3Lbo4 dgl2_groupButDiv'><div class='_3_Bqs _1lUlZ'><div class='pqF_I 3tZow'><span class='_1X7Yj _14i4i _23Ekg _3q2EJ'><img data-hook='user_avatar' alt='"+name+"'s avatar' class='_19ZLc dIDzJ' src='"+img+"'></span></div><div class='_3po1a _3_Nxk'><span class='_3g6BC _2PY8v _3YKkm _1sm3t _3HH04 _3y1P2 _3kEx1 _32s2-'><svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'><path d='M4.75,3.25 L7,3.25 L7,4.75 L4.75,4.75 L4.75,7 L3.25,7 L3.25,4.75 L1,4.75 L1,3.25 L3.25,3.25 L3.25,1 L4.75,1 L4.75,3.25 Z'></path></svg></span></div></div><div class='_3j_sQ _22Jp8'>"+name+"</div></div></button>";
        return "<button class='dgl2_groupButton' groupID=" + id + " type='group' groupName='" + escapeHtml(name) + "' >" +
            "  <div class='dgl2_imgwrap'>" +
            "    <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8' class='dgl2_hover'>" +
            "      <path d='M4.75,3.25 L7,3.25 L7,4.75 L4.75,4.75 L4.75,7 L3.25,7 L3.25,4.75 L1,4.75 L1,3.25 L3.25,3.25 L3.25,1 L4.75,1 L4.75,3.25 Z'></path>" +
            "    </svg>" +
            "    <img class='dgl2_group_image' title='" + escapeHtml(name) + "' src='" + img + "'/>" +
            "  </div>" +
            "  <div class='dgl2_groupName'>" + escapeHtml(name) + "</div>" +
            "</button>";
    }

    function getSubFolderOptionTemplate(name, devCnt, grID, foID, foType, img){
        //text option only needs name,IDs and type
        return "<option class='dgl2_groupButton' groupID="+grID+" folderName='" + escapeHtml(name) + "' folderID=" + foID + " folderType='" + foType + "' type='folder'>"+escapeHtml(name)+"</option>";
    }
    function getSubFolderTemplate(name, devCnt, grID, foID, foType, img) { //return HTML string
        loadedFolders.set(""+foID,name);
        //return "<button class='_3UhBt _3PBqc _3PBqc dgl2_groupButton' groupID="+grID+" folderName="+name+" folderID="+foID+" folderType='"+foType+"' type='folder'><div class='_26MiR _2yz_F'><div class='_3Q4xp'><div class='_1PQmd'><div role='img' aria-label='Suggestions unwatch fav' class='_2i5F4 _2m25r' style='width: 112px; height: 48px; background-position: center center; background-size: cover; background-repeat: no-repeat; background-image: url(&quot;"+img+"&quot;);'><noscript></noscript></div></div><div class='_2ghXZ'><span class='_3g6BC _2PY8v _3YKkm _1AjcI'><svg width='0' height='0' viewBox='0 0 8 8' xmlns='http://www.w3.org/2000/svg'><path d='M1.237 6.187L0 4.95l1.237-1.238L2.475 4.95l3.712-3.713 1.238 1.238-4.95 4.95-1.238-1.238z' fill-rule='evenodd'></path></svg></span></div></div><div class='_3a6pU'><div class='_1lEBY'>"+name+"</div><span class='_3_DY3'>"+devCnt+" deviations</span></div></div></button>";
        var imgstring;
        if (img.textContent) { //journal
            imgstring = "<p class='dgl2_journalSubF'>" + img.textContent.excerpt + "</p>";
        } else {
            var i;
            var cstr = "";
            img = img.media;
            for (i of img.types) {
                if (i.c != undefined) {
                    cstr = i.c;
                    break;
                }
            }
            if (cstr == "") {
                for (i of img.types) {
                    if (i.s != undefined) {
                        cstr = i.s;
                        break;
                    }
                }
            }
            if (img.baseUri) imgstring = img.baseUri + "/";
            if (img.prettyName) imgstring += cstr.replace("<prettyName>", img.prettyName);
            if (img.token) imgstring += "?token=" + img.token[0];
            imgstring = "<img class='dgl2_group_image' title='" + escapeHtml(name) + "' src='" + imgstring + "'/>";
        }

        return "<button class='dgl2_groupButton' groupID=" + grID + " folderName='" + escapeHtml(name) + "' folderID=" + foID + " folderType='" + foType + "' type='folder'>" +
            "  <div class='dgl2_imgwrap'>" +
            "    <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8' class='dgl2_hover'>" +
            "      <path d='M4.75,3.25 L7,3.25 L7,4.75 L4.75,4.75 L4.75,7 L3.25,7 L3.25,4.75 L1,4.75 L1,3.25 L3.25,3.25 L3.25,1 L4.75,1 L4.75,3.25 Z'></path>" +
            "    </svg>" +
            imgstring +
            "  </div>" +
            "  <div class='dgl2_groupName'>" + escapeHtml(name) + "</div>" +
            "  <div class='dgl2_devCnt'>" + devCnt + "</div>" +
            "</button>";
    }

    function getSearchBarTemplate() {
        return "<input id='dgl2_searchbar' type='text' placeholder='Search'/>";
    }

    function getCollectionColTemplate() {
        return "<div id='dgl2_CollTab'><div class='dgl2_CollTitleBut'>" + svgTurnArrow + "<span class='dgl2_CollTitle'>" +
            "Collections</span></div><div class='buttons'></div><ul class='sortableList'></ul></div>";
    }

    function getAddButTemplate() {
        var sty = getComputedStyle(document.body);
        return "<button title='Add group to collection/macro' class='dgl2_add'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172'>" +
            "	<g style='stroke:" + sty.color + ";stroke-width:15;'>" +
            "		<rect x='00' y='00' style='opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
            "		<line x1='86' y1='30' x2='86' y2='142' />" +
            "		<line x1='30' y1='86' x2='142' y2='86' />" +
            "	</g>" +
            "</svg></button>";
    }

    function getRecButTemplate() {
        var sty = getComputedStyle(document.body);
        return "<button title='Add groups to macro' class='dgl2_add'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172'>" +
            "	<g style='stroke:" + sty.color + ";stroke-width:15;'>" +
            "		<rect x='00' y='00' style='opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
            "		<ellipse cx='86' cy='86' rx='40' ry='40'></ellipse>" +
            "	</g>" +
            "</svg></button>";
    }

    function getNewColTemplate() {
        var sty = getComputedStyle(document.body);
        return "<button title='New collection/macro' class='dgl2_new dgl2_topBut'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172'>" +
            "	<g style='stroke:" + sty.color + ";stroke-width:15;'>" +
            "		<rect x='00' y='00' style='opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
            "		<line x1='86' y1='30' x2='86' y2='142' />" +
            "		<line x1='30' y1='86' x2='142' y2='86' />" +
            "	</g>" +
            "</svg></button>";
    }

    function getSubButTemplate() {
        var sty = getComputedStyle(document.body);
        return "<button title='Remove groups from collection/macro' class='dgl2_sub'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172'>" +
            "	<g style='stroke:" + sty.color + ";stroke-width:15;'>" +
            "		<rect x='00' y='00' style='opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
            "		<line x1='30' y1='86' x2='142' y2='86' />" +
            "	</g>" +
            "</svg></button>";
    }

    function getRefreshButTemplate() {
        var sty = getComputedStyle(document.body);
        return "<button  title='refresh list of groups' id='dgl2_refresh'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172' style=' fill:#000000;'><g fill='none' fill-rule='nonzero' stroke='none' stroke-width='1' stroke-linecap='butt' stroke-linejoin='miter' stroke-miterlimit='10' stroke-dasharray='' stroke-dashoffset='0' font-family='none' font-weight='none' font-size='none' text-anchor='none' style='mix-blend-mode: normal'><path d='M0,172v-172h172v172z' fill='none'></path>" +
            " <linearGradient id='dgl2_grad1' x1='0%' y1='100%' x2='0%' y2='0%'><stop id='dgl2_grad1_stop1' offset='0%' style='stop-color:rgb(0,255,0);stop-opacity:1' /><stop id='dgl2_grad1_stop2' offset='100%' style='stop-color:rgb(255,0,0);stop-opacity:1' /></linearGradient>" +
            "<rect x='00' y='00' style='stroke:" + sty.color + ";stroke-width:5;opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
            "<g fill='" + sty.color + "'><path d='M62.00062,36.98l7.99979,10.89333h21.44625c18.10591,0 32.68,14.57409 32.68,32.68v21.78667h-16.34l21.78667,29.78646l21.78667,-29.78646h-16.34v-21.78667c0,-23.99937 -19.57396,-43.57333 -43.57333,-43.57333zM42.42667,39.87354l-21.78667,29.78646h16.34v21.78667c0,23.99938 19.57396,43.57333 43.57333,43.57333h29.44604l-7.99979,-10.89333h-21.44625c-18.10591,0 -32.68,-14.57409 -32.68,-32.68v-21.78667h16.34z'></path></g><path d='M43.86,172c-24.22321,0 -43.86,-19.63679 -43.86,-43.86v-84.28c0,-24.22321 19.63679,-43.86 43.86,-43.86h84.28c24.22321,0 43.86,19.63679 43.86,43.86v84.28c0,24.22321 -19.63679,43.86 -43.86,43.86z' fill='none'></path><path d='M47.3,168.56c-24.22321,0 -43.86,-19.63679 -43.86,-43.86v-77.4c0,-24.22321 19.63679,-43.86 43.86,-43.86h77.4c24.22321,0 43.86,19.63679 43.86,43.86v77.4c0,24.22321 -19.63679,43.86 -43.86,43.86z' fill='none'></path></g></svg></button";
    }

    function getDelButTemplate() {
        var sty = getComputedStyle(document.body);
        return "<button title='Delete collection/macro' class='dgl2_del'><svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' width='24' height='24' viewBox='0 0 172 172'>" +
            "<g style='stroke-width:5;stroke:" + sty.color + ";fill:none'>" +
            "	<rect x='00' y='00' style='opacity:0.1' rx='50' ry='50' width='172' height='172'></rect>" +
            "	<rect x='50' y='50' rx='5' ry='5' width='72' height='92'></rect>" +
            "	<rect x='65' y='35' rx='5' ry='5' width='42' height='15'></rect>" +
            "  <line x1='40' y1='50' x2='132' y2='50'/>" +
            "  <line x1='70' y1='132' x2='70' y2='60'/>" +
            "  <line x1='86' y1='132' x2='86' y2='60'  />" +
            "  <line x1='104' y1='132' x2='104' y2='60' />" +
            "  </g>" +
            "</svg></button>";
    }

    function getExportButTemplate() {
        return '<button title="Export collection/macro list to file" class="dgl2_export dgl2_topBut"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 5.2916665 5.2916668">' +
            '   <g transform="translate(0,-291.70832)">' +
            '      <path style="fill:#008000;"' +
            '          d="M 0.26458332,291.9729 H 5.0270831 v 4.7625 H 0.79345641 l -0.52887309,-0.51217 z" />' +
            '      <rect style="fill:#ffffff;" width="3.7041667" height="1.8520833" x="0.79374999" y="292.23749" />' +
            '      <rect style="fill:#ffffff;" width="2.6458333" height="1.3229259" x="1.3229166" y="295.41248" />' +
            '      <rect style="fill:#008000;" width="0.52916676" height="0.79375702" x="2.9104166" y="295.67706" />' +
            '   </g>' +
            '</svg></button>';
    }

    function getImportButTemplate() {
        return '<button class="dgl2_import dgl2_topBut" title="Import collection/macro list from file" ><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 5.2916665 5.2916668">' +
            '	<g transform="translate(0,-291.70832)">' +
            '		<rect style="fill:#806600;" width="3.96875" height="2.9104137" x="0.52916664" y="293.03125" />' +
            '		<path style="fill:#ffcc00;" d="m 0.52916666,295.94165 0.79375004,-2.11666 h 3.96875 l -0.7937501,2.11666 z" />' +
            '		<rect style="fill:#00DD00;" width="0.52916664" height="1.0583333" x="3.4395833" y="292.50208" />' +
            '		<path style="fill:#00DD00;" d="m 3.175,292.50207 0.5291667,-0.52917 0.5291667,0.52917 z" />' +
            '	</g>' +
            '</svg></button>';
    }

    function getTitleBarTemplate() {
        return '<span class="dgl2_titleText">Add to Group</span>' +
            '<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill-rule="evenodd" d="M8.84210526,13 L8.84210526,21.1578947 L2,21.1578947 L2,9.57894737 L12,3 L22,9.57894737 L22,21.1578947 L15.1578947,21.1578947 L15.1578947,13 L8.84210526,13 Z"></path></svg>' +
            '<span class="dgl2_descr">Add this deviation to one of your groups</span>'
    }

    function getEditButTemplate() {
        return '<button title="Change collection/macro name" class="dgl2_edit"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20.389 6.4503l-3-3-1.46-1.45-1.41 1.42-11.52 11.58-1 .97v6.03h5.987l1.013-1 11.41-11.76 1.39-1.41-1.41-1.38zm-4.45-1.62l3 3-.88.87-3-3 .88-.87zm.74 5.33l-8.21 8.2-2.801-3.0118 8.0028-8.099 3.0083 2.9108zm-12.68 9.84v-3.17l3.0433 3.17H3.9991z"></path></svg></button>';
    }

    function getVisibleButTemplate() {
        return `<button title="Hide/show groups within collection" class="dgl2_visible">
        <svg xmlns="http://www.w3.org/2000/svg"  viewBox="0 0 500 250" stroke-width="20">
        <defs>
            <radialGradient id="c1" cx="0.5" cy="0.5" r="0.5">
                <stop offset="0" stop-color="#ffffff" />
                <stop offset=".5" stop-color="hsl(40, 60%, 60%)" />
                <stop offset="1" stop-color="#3dff3d" />
            </radialGradient>
        </defs>
        	<ellipse role="sclera" cx="250" cy="125" rx="200" ry="100" fill="white"/>
        	<ellipse role="iris" cx="250" cy="125" rx="95" ry="95" stroke="black" fill="url(#c1)"/>
        	<ellipse role="pupil" cx="250" cy="125" rx="50" ry="50" stroke="none" fill="black"/>
        	<ellipse role="light" cx="200" cy="80" rx="50" ry="50" stroke="none" fill="#fffffaee"/>
        	<ellipse role="outline" cx="250" cy="125" rx="200" ry="100" stroke="black" fill="none"/>
        </svg>
        </button>`;
    }

    function addCss() {
        if ($("#dgl2_style").length > 0) return;
        var style = $("<style type='text/css' id='dgl2_style'></style>");

        //searchbar
        style.append("#dgl2_searchbar{background: var(--L3);box-shadow: inset 0 1px 4px 0 rgba(0,0,0,.25);padding: 5px;width: 50%;}");

        //right collection column
        style.append(`
                #dgl2_CollTab{padding-left:15px; flex:1;overflow-y: auto;font-family: CalibreSemiBold,sans-serif;font-weight: 600;font-size: 20px;font-display: swap;line-height: 24px;letter-spacing: .3px;margin-bottom: 28px;}
                #dgl2_CollTab ul{overflow: auto;list-style: none;padding-left: 10px;margin-top: 20px;}
                #dgl2_CollTab ul li{cursor:pointer;padding:2px;display:grid;grid-template: auto/7px auto 16px 16px 16px 16px 16px;}
                #dgl2_CollTab ul li:hover{background:linear-gradient(to right, rgba(255,0,0,0.1), rgba(255,0,0,0));}
                #dgl2_CollTab button{cursor:pointer;border-width: 0;padding: 0;margin: 0;background-color: transparent;}
                #dgl2_CollTab button:hover rect{fill:red;user-select: none; }
                #dgl2_refresh{margin-left:auto;border-width:0px;background:transparent;cursor:pointer}
                #dgl2_CollTab div.buttons{display: inline-block;vertical-align: middle;margin: 0 5px;}
                div.dgl2_groupCol{width:700px;flex:3;overflow-y:auto}
                div.dgl2_groupdialog{display:flex;}
                div.dgl2_titlebar{display:flex;justify-content:space-between;margin: 0 20px;}
                #dgl2_refresh:hover rect{fill:red;}
                #dgl2_refresh rect{fill:rgba(255,0,0,0.1);}
                #dgl2_CollTab ul li span.handle{vertical-align:middle; display:inline-block; width:5px; height:100%; cursor:move; text-align:center;background-color:#363; background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABZJREFUeNpi2r9//38gYGAEESAAEGAAasgJOgzOKCoAAAAASUVORK5CYII=);}
                button.dgl2_groupButton{border-radius:15px; background-color:rgba(255,255,255,0.5);margin:5px; padding:5px; width:120px; border-width:0px; overflow:hidden; position:relative; cursor:pointer; }
                div.dgl2_imgwrap{ position: relative;}
                svg.dgl2_hover{ position: absolute; left: 50%; width:50%; height:50%; transform: translate(-50%,50%); opacity:0; transition: ease 0.25s; }
                img.dgl2_group_image{ opacity:1; width:100px; height:50px; transition: ease 0.25s; border-radius:2px; }
                div.dgl2_groupName{ font-family: CalibreSemiBold; font-size: 15px; line-height: 15px; font-weight: 600; letter-spacing: 0.3px; word-break: break-word; }
                button.dgl2_groupButton:hover{background-color:rgba(255,255,255,0.8);}
                button.dgl2_groupButton:hover svg.dgl2_hover{opacity:1;}
                button.dgl2_groupButton:hover img.dgl2_group_image{opacity:0.3;}
                button.dgl2_groupButton:active{background-color:rgba(255,255,255,0.3);}
                button.dgl2_groupButton.dgl2_inGroup{background-image:linear-gradient(red, transparent);}
                span.dgl2_titleText{cursor:pointer;}
                span.dgl2_descr{font-family: CalibreRegular,sans-serif; font-weight: 400; font-size: 13px; font-display: swap; letter-spacing: 1.3px; margin-left: 32px; text-transform: uppercase;}
                button.dgl2_edit{height:0.5em;}
                button.dgl2_edit:hover path{fill:red;}
                #dgl2_CollTab button svg{width: 90%;}
                #dgl2_CollTab button.dgl2_visible:hover ellipse{stroke: red;}
                div.dgl2_addGroup{background-color: rgba(0, 255, 0, 0.3);}
                div.dgl2_remGroup{background-color: rgba(255, 0, 0, 0.3);}
                button.dgl2_inCollection{background-color: rgba(15, 104, 5, 0.7);}
                button.dgl2_export:hover ,button.dgl2_import:hover {opacity:0.8}
                button.dgl2_export:active ,button.dgl2_import:active {opacity:1}
                div.dgl2_CollTitleBut{cursor:pointer;display:inline-block;}
                div.dgl2_CollTitleBut:hover span{color:red;}
                .dgl2_journalSubF { overflow: hidden; height: 50px; font-size: xx-small; text-align: left; margin-bottom: 5px;}
                .groupPopup .ui-widget-content{background-color:#afcaa9 !important;color:black;}
                button.dgl2_letterfound {background-color: rgba(105, 14, 5, 0.7);}
                .folderInMacro {background-color:rgba(205, 24, 25, 0.6)!important;}
                @keyframes shadowPulse {0% {box-shadow: 0px 0px 50px 20px #f00;} 100% {box-shadow: 0px 0px 50px 20px #ff000000;}}
                .shadow-pulse {animation-name: shadowPulse;animation-duration: 0.5s;animation-iteration-count: infinite;animation-timing-function: linear; animation-direction:alternate;}
                #dgl2_grContext{display: none;z-index: 1000;position: absolute;overflow: hidden;white-space: nowrap;padding: 5px;background-color: #afcaa9;border-radius: 5px;border: 2px solid green;}
                #dgl2_grContext select{background: none; border: none;width:100%;margin:5px 0;}
                #dgl2_grContext select option{background-color: #ddffd8;}
                #dgl2_grContext select option:nth-child(even) {background-color: #6fd061;}
                #dgl2_grContext select option::selection {color: red;background: yellow;}
                #dgl2_grContext button {cursor:pointer; width: 100%;background-color: #408706;color: white;border: 1px outset black;border-radius: 5px;}
                #dgl2_grContext button:hover { background-color: #608706;}
                #dgl2_CollTab li[active='0'] button.dgl2_visible ellipse[role='iris'] { fill: lightgray;stroke:lightgray}
                #dgl2_CollTab li[active='0'] button.dgl2_visible ellipse { fill: lightgray;}
                #dgl2_CollTab li button{display: flex; height: 100%;align-items: center;}
                `);

        // style.append(".noTitleDialog .ui-dialog-titlebar {display:none}");


        $("head").append(style);

        $("head").append(
            '<link ' +
            'href="//ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/le-frog/jquery-ui.min.css" ' +
            'rel="stylesheet" type="text/css">'
        );

    }
    //filling GUI DOM
    function hideHiddenGroups(){
        collections.forEach(col=>{
            if(col.showing==0){
                col.groups.forEach(grID=>{
                    $("button[groupID='" + grID + "']").hide();
                });
            }
        });
    }

    function insertFilteredGroups(id) {
        insertGroups();
        lastFilter = id;
        var allButs = $("button[type='group']");
        if (collections[id].groups.length == 0) {
            allButs.show();
        } else {
            allButs.hide();
            for (var grID of collections[id].groups) {
                $("button[groupID='" + grID + "']").show();
            }
        }
        hideHiddenGroups()
    }

    function insertMacroGroups(id) {
        insertGroups();
        lastFilter = id;
        var allButs = $("button[type='group']");
        if (macros[id].data.length == 0) {
            allButs.show();
        } else {
            allButs.hide();
            for (var grID of macros[id].data) {
                $("button[groupID='" + grID.groupID + "']").show();
            }
        }
    }

    function insertMacros() {
        var coltab = 0;
        var toAffect = $("div.dgl2_groupdialog").not("[dgl2]").attr("dgl2", 1);
        if (toAffect.length > 0) {
            coltab = $(getCollectionColTemplate());
            toAffect.append(coltab);
            coltab.find("div.buttons")
                .append(getNewColTemplate())
                .append(getExportButTemplate())
                .append(getImportButTemplate());

            coltab.click(Ev_colListClick);
        } else {
            coltab = $("#dgl2_CollTab");
        }
        var colList = coltab.find("ul");
        colList.empty();
        var el,descr;
        for (var col of macros) {
            descr="";
            for(var gr of col.data){
                descr+=groupById(gr.groupID).username+"/"+gr.folderName+"\n";
            }
            el = "<li colid=" + col.id + " title='"+escapeHtml(descr)+"' id='dgl2item-" + col.id + "'><span class='handle'></span><span>" + col.name + "</span>" + getEditButTemplate();
            el += getRecButTemplate() + getSubButTemplate() + getDelButTemplate();
            el += "</li>";
            colList.append(el);
        }
        if (macroOrder.length > 0) {
            $.each(macroOrder, function (i, position) {
                var $target = colList.find('#' + position);
                $target.appendTo(colList); // or prependTo for reverse
            });
        }
        $(".sortableList").sortable({
            revert: true,
            cursor: 'move',
            axis: 'y',
            handle: 'span.handle',
            update: function (event, ui) {
                macroOrder = $(this).sortable('toArray');
                GM.setValue("macroOrder", JSON.stringify(macroOrder));
            }
        });
    }

    function insertCollections() {
        var coltab = 0;
        var toAffect = $("div.dgl2_groupdialog").not("[dgl2]").attr("dgl2", 1); //group submission container
        if (toAffect.length > 0) {
            coltab = $(getCollectionColTemplate());
            toAffect.append(coltab);
            coltab.find("div.buttons")
                .append(getNewColTemplate())
                .append(getExportButTemplate())
                .append(getImportButTemplate());

            coltab.click(Ev_colListClick);
        } else {
            coltab = $("#dgl2_CollTab");
        }


        var colList = coltab.find("ul");
        colList.empty();
        var el;
        for (var col of collections) {
            el=`<li colid=${col.id} active='${col.showing}' id='dgl2item-${col.id}'><span class='handle'></span><span>${col.name}</span>${getEditButTemplate()}`;
            if (col.id > 0) el += getVisibleButTemplate()+ getAddButTemplate() + getSubButTemplate() + getDelButTemplate();
            el += "</li>";
            colList.append(el);
        }
        if (collectionOrder.length > 0) {
            $.each(collectionOrder, function (i, position) {
                var $target = colList.find('#' + position);
                $target.appendTo(colList); // or prependTo for reverse
            });
        }
        $(".sortableList").sortable({
            revert: true,
            cursor: 'move',
            axis: 'y',
            handle: 'span.handle',
            update: function (event, ui) {
                collectionOrder = $(this).sortable('toArray');
                GM.setValue("collectionOrder", JSON.stringify(collectionOrder));
            }
        });
    }

    function insertSearchBar() {
        var bar = $(getSearchBarTemplate());
        var refrBut = $(getRefreshButTemplate());

        $("div.dgl2_titlebar").append(refrBut).append(bar);
        bar.keyup(function () {
            var search = $(this).val();
            var allButs = $("button[type='group']").show();
            allButs.filter(function () {
                if (lastFilter > 1 && collections[lastFilter].groups.indexOf($(this).attr("groupID")) == -1) return 1;
                if (search == "") return 0;
                var words = search.split(" ");
                for (var word of words) {
                    if ($(this).attr('groupName').toLowerCase().indexOf(word) == -1) return 1;
                }
                return 0;
            }).hide();
        });
        //bar.mousedown(function(event){event.stopPropagation(); event.preventDefault();event.target.focus();});


        refrBut.click(Ev_getGroupClick);

        $("span.dgl2_titleText").click(function(){
            //insertGroups();
            $("li[colid="+lastFilter+"]").click();
            // console.log(lastFilter,"li[colid="+lastFilter+"]",$("li[colid="+lastFilter+"]"));
            var lastgrBut=$("button[groupid="+lastGroupClickID+"]")
            lastgrBut[0].scrollIntoView();
            lastgrBut.addClass("shadow-pulse");
        });
    }
    function pulsing(element) {
        element.animate({ opacity: 0 }, 250, function() {
            $(this).animate({ opacity: 1 }, 250, pulsing);
        });
    }

    function insertSubFolders(subfolders) { //fill view with subfolders //subfolders not stored, request when needed
        var buts = $("button.dgl2_groupButton"); //button wrapper

        subfolders.sort(function (l, u) {
            return l.name.toLowerCase().localeCompare(u.name.toLowerCase());
        });

        if (buts.length > 0) {
            var subf
            if($("#dgl2_grContext").is(":visible")){
                var cont=$("#dgl2_grContext select");
                var newopt;
                cont.empty();
                for (subf of subfolders) {
                    if (subf.thumb == null) continue; //no thumb=db problem, so also no text entry
                    newBut = $(getSubFolderOptionTemplate(subf.name, subf.size, subf.owner.userId, subf.folderId, subf.type, subf.thumb));
                    cont.append(newBut);
                }
                if(subfolders.length==0){
                    $("#dgl2_grContext span.desc").html("This group does<br/>not allow submissions<br/>using the gallery<br/>system!");
                }else{
                    $("#dgl2_grContext span.desc").text("Submit to a Folder");
                }

            }else{
                var par = buts.first().parent();
                var newBut;
                par.empty();
                for (subf of subfolders) {
                    if (subf.thumb == null) continue;
                    newBut = $(getSubFolderTemplate(subf.name, subf.size, subf.owner.userId, subf.folderId, subf.type, subf.thumb));
                    par.append(newBut);
                }
                if(subfolders.length==0){
                    par.append("This group does not allow submissions using the gallery system!");
                }
                par.not("[dgl2]").attr("dgl2", 1).click(Ev_groupClick);
            }
        }
    }

    function insertGroups() { //fill view with groups //groups are stored
        lastFilter = 0;
        groups.sort(function (l, u) {
            return l.username.toLowerCase().localeCompare(u.username.toLowerCase());
        });

        $("span.dgl2_titleText").text("Add to Group");
        $("span.dgl2_descr").text("Add this deviation to one of your groups");

        var par = $("div.dgl2_groupWrapper"); //group list wrapper
        var newBut;
        par.empty();
        for (var gr of groups) {
            newBut = $(getGroupTemplate(gr.username, gr.usericon, gr.userId));
            newBut.contextmenu(Ev_groupContext);
            if (listedGroups.includes(gr.userId)) {
                newBut.addClass("dgl2_inGroup"); //button div inside wrapper; used in template
            }
            if(par.find("div[groupName='"+gr.username+"']").length==0){
                par.append(newBut);
            }
        }
        par.not("[dgl2]").attr("dgl2", 1).click(Ev_groupClick);
        hideHiddenGroups()
    }
    function uniqBy(a, key) {
        let seen = new Set();
        return a.filter(item => {
            let k = key(item);
            return seen.has(k) ? false : seen.add(k);
        });
    }
    function insertHTML() {

        // console.log("check");
        if ($("div.dgl2_groupdialog").length > 0) return;

        // console.log("run");
        addCss();
        $("<div class='dgl2_groupdialog'><div class='dgl2_groupCol'><div class='dgl2_titlebar'></div><div class='dgl2_groupWrapper'></div></div></div>").appendTo($("body"));
        $("div.dgl2_titlebar").html(getTitleBarTemplate());
        insertSearchBar();


        var devInd=location.href.indexOf("?");
        if(devInd==-1){
            devID = location.href.match(/\d+$/)[0];
        }else{
            devID = location.href.substring(0,devInd).match(/\d+$/)[0];
        }

        //  console.log(devID);
        userName =$("a.user-link").attr("data-username"); // "dediggefedde";

        var proms=[
            GM.getValue("collections", ""),
            GM.getValue("collectionOrder", ""),
            GM.getValue("macros", ""),
            GM.getValue("macroOrder", "")
        ];

        Promise.all(proms).then(([cols, colOrder,macs,macOrder]) => {
            if (cols != "") {
                collections = JSON.parse(cols);
            }
            collections.forEach(el=>{if(!el.hasOwnProperty("showing")){el.showing=1;};}); //backward-compatibility for collection-showing attribute before v3.0

            if (colOrder != "") {
                collectionOrder = JSON.parse(colOrder);
            }

            if (macs != "") {
                macros = JSON.parse(macs);
                macros.forEach(function(el){el.data=uniqBy(el.data,JSON.stringify);});//unique macros
            }

            if (macOrder != "") {
                macroOrder = JSON.parse(macOrder);
            }
            insertCollections();
        }).catch(function (e) {
            console.log("Error Loading Database:",e);
            return insertCollections();
        });

        fillListedGroups(devID).then(function () {
            return GM.getValue("groups", "");
        }).then(function (grps) {
            if (grps == "") {
                Ev_getGroupClick();
            } else {
                groups = JSON.parse(grps);
                insertGroups();
            }
        }).catch(function (e) {
            console.log("dev_group_list_2 error:", e);
        });

        ; //can run in parallel

    }

    //outdated, now I'm using my own template
    //in case classNames (hopefully) change sometime. Also necessary since browsing=other class names than refreshing site.
    // function classNormalizer() { //_2n5r3 _3Isw- _3yw1l _1F3vX _34hvg
    //     // if($("div.dgl2_groupdialog").length>0)return;
    //     $("<div class='dgl2_groupdialog'><div class='dgl2_groupCol'><div class='dgl2_titlebar'></div><div class='dgl2_groupWrapper'></div></div></div>").appendTo($("body"));

    //     // $("div.dgl2_groupdialog div._13bQf").hide();
    //     // $("div.dgl2_groupdialog button._2-StF").hide();

    //     // $("span.u1km9, span._3M1Kg").addClass("dgl2_descr"); //refr u1km9 browse _3M1Kg //top description //added by template
    //     // $("button._3PBqc, button._3UhBt").addClass("dgl2_groupButton"); //refr _3UhBt _3PBqc, browse _3PBqc //button to add to group //added by template
    //     // $("div._1cFkE, div._3Lbo4").addClass("dgl2_groupButDiv"); //refr _1cFkE _11Rtb, browse _3Lbo4 _3EbZ8 //div inside button //outdated
    //     // $("div.A9uFL, div.JcCFR").addClass("dgl2_groupWrapper"); //refr A9uFL _2IeoY, browse JcCFR _159Ra //div parent of group buttons
    //     // $("div._1HNK0, div._1ezbx").addClass("dgl2_titlebar"); //refr _1HNK0, browse _1ezbx //title bar
    //     //  $("div._2n5r3, div._2qgZz").addClass("dgl2_groupdialog"); //refr _2n5r3, browse _2qgZz // dialog(opening div) for group submission
    //     // $("div._2UK-E, div._2kU4L").addClass("dgl2_groupCol"); //refr _2UK, browse _2kU4L//left column (containing groups)


    //     //         if($("div.dgl2_titlebar").length==0 && $("div.dgl2_groupCol:not([nogroups])").length>0){
    //     //             $("div.dgl2_groupCol").attr("nogroups",1).html('<div class="_2UK-E"><div class="_38jjU"><div class="_1HNK0">Add to Group<span class="_3g6BC _1Re00 _3YKkm _1d2qE"><svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill-rule="evenodd" d="M8.84210526,13 L8.84210526,21.1578947 L2,21.1578947 L2,9.57894737 L12,3 L22,9.57894737 L22,21.1578947 L15.1578947,21.1578947 L15.1578947,13 L8.84210526,13 Z"></path></svg></span><span class="u1km9">Add this deviation to one of your groups</span></div><div class="_2ZROG"><div class="A9uFL _2IeoY"><button class="_3UhBt"><div class="_1cFkE _11Rtb"><div class="_3_Bqs"><div class="pqF_I"><span class="_1X7Yj _14i4i"><img alt="NatureNation\'s avatar" data-hook="user_avatar" class="_19ZLc" src="https://a.deviantart.net/avatars-big/n/a/naturenation.jpg?2" loading="lazy"></span></div><div class="_3po1a"><span class="_3g6BC _2PY8v _3YKkm _1sm3t"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8"><path d="M4.75,3.25 L7,3.25 L7,4.75 L4.75,4.75 L4.75,7 L3.25,7 L3.25,4.75 L1,4.75 L1,3.25 L3.25,3.25 L3.25,1 L4.75,1 L4.75,3.25 Z"></path></svg></span></div></div><div class="_3j_sQ">NatureNation</div></div></button></div></div></div></div>');
    //     //             classNormalizer();
    //     //         }

    // }

    function getLowestFree(collection) {
        collection.sort(function (a, b) {
            return a.id - b.id;
        }); //changing order does not matter thanks to index/order array
        var lowest = -1;
        var i;
        for (i = 0; i < collection.length; ++i) {
            if (collection[i].id != i) {
                lowest = i;
                break;
            }
        }
        if (lowest == -1 && collection.length > 0) {
            lowest = collection[collection.length - 1].id + 1;
        } else if (collection.length == 0) lowest = 0;
        return lowest;

    }

    function escapeHtml (string) {
        return String(string).replace(/[&<>"'`=\/]/g, function (s) {
            return entityMap[s];
        });
    }
    function download(data, filename) {
        var file = new Blob([data], {
            type: "application/json"
        });
        var a = document.createElement("a"),
            url = URL.createObjectURL(file);
        a.href = url;
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        setTimeout(function () {
            document.body.removeChild(a);
            window.URL.revokeObjectURL(url);
        }, 0);
    }

    function upload() {
        return new Promise(function (resolve, reject) {
            var inp = $('<input type="file" id="input">').appendTo("body").click()
            inp.change(function () {
                var reader = new FileReader();
                reader.onload = function (evt) {
                    resolve(evt.target.result);
                };
                reader.readAsBinaryString($(this).prop("files")[0]);
            });
            return "";
        });
    }

    function colIndexById(id) {
        for (var i = 0; i < collections.length; ++i) {
            if (collections[i].id == id) return i;
        }
        return -1;
    }

    function makIndexById(id) {
        for (var i = 0; i < macros.length; ++i) {
            if (macros[i].id == id) return i;
        }
        return -1;
    }

    function myAlert(tex, titl = "") {
        var box = $("#dgl2_alertBox");
        if (box.length == 0) box = $("<div id='dgl2_alertBox'></div>").appendTo("body");
        box.html(tex);
        box.attr("title", titl);
        box.dialog({
            width: "50%",
            height: "auto",
            maxHeight: (window.innerHeight * 0.8),
            buttons: {
                Ok: function () {
                    $(this).dialog("close");
                }
            }
        });
    }

    function myConfirm(tex, titl = "") {
        var dfd = new $.Deferred();
        var box = $("#dgl2_alertBox");
        if (box.length == 0) box = $("<div id='dgl2_alertBox'></div>").appendTo("body");
        box.html(tex);
        box.attr("title", titl);
        box.dialog({
            width: "50%",
            height: "auto",
            maxHeight: (window.innerHeight * 0.8),
            buttons: {
                "OK": function () {
                    $(this).dialog("close");
                    dfd.resolve(true);
                },
                Cancel: function () {
                    $(this).dialog("close");
                    dfd.resolve(false);
                }
            }
        });
        return dfd.promise();
    }

    function myPrompt(tex, titl = "", def = "") {
        var dfd = new $.Deferred();
        var box = $("#dgl2_alertBox");
        if (box.length == 0) box = $("<div id='dgl2_alertBox'></div>").appendTo("body");
        box.html(tex + "<br/><input type='text' id='dgl2_promptVal' value='" + def + "' class='text ui-widget-content ui-corner-all'>");
        box.attr("title", titl);
        box.dialog({
            width: "50%",
            height: "auto",
            maxHeight: (window.innerHeight * 0.8),
            buttons: {
                "OK": function () {
                    $(this).dialog("close");
                    dfd.resolve($("#dgl2_promptVal").val());
                },
                Cancel: function () {
                    $(this).dialog("close");
                    dfd.resolve(false);
                }
            }
        });
        return dfd.promise();

    }

    function showPopup(event) {
        event.preventDefault();
        event.stopPropagation();
        insertHTML(); //does nothing if already inserted
        $("div.dgl2_groupdialog").dialog({
            width: "80%",
            height: (window.innerHeight * 0.8),
            draggable: false,
            resizable: false,
            dialogClass: 'groupPopup',
            create: function (event) {
                $(event.target).parent().css({
                    'position': 'fixed',
                    "left": '10%',
                    "top": '10%'
                });
                $(event.target).parent().find("div.ui-dialog-titlebar").prepend($("div.dgl2_titlebar"));
                $(event.target).parent().find("span.ui-dialog-title").hide();
            }
        });
        $("div.groupPopup").keydown(function(event){
            if(event.target.tagName!="INPUT" && !$("#dgl2_grContext").is(":visible")){
                highlightLetter(String.fromCharCode(event.which));
            }
        });
    }


    function groupById(id) {
        for (var g of groups) {
            if (g.userId == id) return g;
        }
        return null;
    }
    //bind script to buttons. dynamic browsing compatible
    function addListener() {
        var els = $('*[data-hook="group_counter"]:not([dgl2=1])');
        els.click(showPopup).attr("dgl2", 1).find("svg").html(
            '<path d="M18.63 17l1.89 5h2l-2.53-7h-6.67l.64 2zM4.04 15l-2.52 7h2l1.88-5h4.23l1.89 5h2l-2.53-7zM7.52 4.33c1.9304.011 3.4873 1.5829 3.48 3.5133-.0074 1.9303-1.5762 3.4903-3.5066 3.4866C5.563 11.3263 4 9.7604 4 7.83c0-1.933 1.567-3.5 3.5-3.5h.02zm-.02-2C4.4624 2.33 2 4.7924 2 7.83s2.4624 5.5 5.5 5.5 5.5-2.4624 5.5-5.5-2.4624-5.5-5.5-5.5zM13 3.37a5.59 5.59 0 0 1 1.5 1.45 3.41 3.41 0 0 1 1.5-.35c1.933 0 3.5 1.567 3.5 3.5s-1.567 3.5-3.5 3.5a3.41 3.41 0 0 1-1.5-.35 5.63 5.63 0 0 1-1.5 1.46c1.968 1.2806 4.532 1.1706 6.3831-.2738 1.8511-1.4445 2.5812-3.9047 1.8175-6.125C20.437 3.9608 18.348 2.4702 16 2.47a5.4102 5.4102 0 0 0-3 .9z"/>' +
            '<path stroke="#0A0" stroke-width="4" stroke-opacity="0.8" d="M12 18H24M18 12V24"/>'
        );
    }
    $(document).ready(function(){
        $(document).mousedown(function(event){
            if($(event.target).closest("#dgl2_grContext").length==0){
                $("#dgl2_grContext").hide().find("select").empty();
            }
        });
    });

    setInterval(addListener, 1000);
})();

/* resources:
group template
<button class="_3PBqc"> <!--_3UhBt-->
 <div class="_3Lbo4 _3EbZ8">
  <div class="_1lUlZ">
   <div class="_3tZow">
    <span class="_23Ekg _3q2EJ">
     <img data-hook="user_avatar" alt="iterators's avatar" class="dIDzJ" src="https://a.deviantart.net/avatars-big/i/t/iterators.png?3">
    </span>
   </div>
   <div class="_3_Nxk">
    <span class="_3HH04 _3y1P2 _3kEx1 _32s2-">
     <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8">
      <path d="M4.75,3.25 L7,3.25 L7,4.75 L4.75,4.75 L4.75,7 L3.25,7 L3.25,4.75 L1,4.75 L1,3.25 L3.25,3.25 L3.25,1 L4.75,1 L4.75,3.25 Z"></path>
     </svg>
    </span>
   </div>
  </div>
  <div class="_22Jp8">iterators</div>
 </div>
</button>

group_request_response_structure
    userId	14471598
    useridUuid	55b0c0d3-fed7-45ff-8951-0d8554eb01b1
    username	deviantARTSupporters
    usericon	https://a.deviantart.net/avatars-big/d/e/deviantartsupporters.gif?12
    type	group
    isNewDeviant	false

subfolder request response structure
    commentCount: 0
    description: ""
    folderId: 13361368
    name: "Featured"
    owner: Object { userId: 11209451, useridUuid: "294e75e1-94ec-4009-883a-b13eff743164", username: "iterators", … }
    parentId: null
    size: 5
    thumb:
       author: Object { userId: 7514199, useridUuid: "56613871-de84-49fc-b1c1-f9fd0f796461", username: "Championx91", … }
       blockReason: null
       deviationId: 710763111
       files: Array(9) [ {…}, {…}, {…}, … ]
          0: Object { type: "150", src: "https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/56613871-de84-49fc-b1c1-f9fd0f796461/dbr64br-58702ee6-5e4e-4bab-b2ea-c5ca2563bb7f.png/v1/fit/w_150,h_150,strp/suggestions_unwatch_fav_by_championx91_dbr64br-150.png?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9ODIxIiwicGF0aCI6IlwvZlwvNTY2MTM4NzEtZGU4NC00OWZjLWIxYzEtZjlmZDBmNzk2NDYxXC9kYnI2NGJyLTU4NzAyZWU2LTVlNGUtNGJhYi1iMmVhLWM1Y2EyNTYzYmI3Zi5wbmciLCJ3aWR0aCI6Ijw9NjQxIn1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmltYWdlLm9wZXJhdGlvbnMiXX0.vyFqJs1s5cyralkRXS31gJbHrwFsBm72c2q9Hb0uJDs", height: 150, … }
       isAntisocial: true
       isBlocked: false
       isCommentable: true
       isDeleted: false
       isDownloadable: false
       isFavourited: false
       isJournal: false
       isMature: false
       isPublished: true
       isPurchasable: false
       isShareable: false
       isTextEditable: false
       legacyTextEditUrl: null
       printId: null
       publishedTime: "2017-10-20T10:39:40-0700"
       stats: Object { comments: 17, favourites: 19 }
       title: "Suggestions unwatch fav"
       typeId: 1
       url: "https://www.deviantart.com/championx91/art/Suggestions-unwatch-fav-710763111"
    type: "gallery"

required:
	groupid =  groups request
	folderid = subfolders/favourites requests
	deviationid = url \d+$
	csrf_token = $("input[name=validate_token]").value; //deviation page
	moduleID = window.document.body.innerHTML.match(/{\\\"id\\\":(\d+),\\\"type\\\":\\\"group_list_members/i) //https://www.deviantart.com/dediggefedde/about

groups GET
	https://www.deviantart.com/_napi/da-user-profile/api/module/groups/members?username=Dediggefedde&moduleid=1725444339&offset=30&limit=24
	Limit max 60
	finished if hasMore==false
	needs moduleID, username

subfolders GET
	https://www.deviantart.com/_napi/shared_api/deviation/group_folders?groupid=13042771&type=gallery
	needs groupID

favourites GET
	https://www.deviantart.com/_napi/shared_api/deviation/group_folders?groupid=28209298&type=collection
	needs groupID

add to subfolder POST
  {"groupid":11209451,"type":"gallery","folderid":23881334,"deviationid":501848540,"csrf_token":"wPPvYvd4br1JpNjT.pyuz1q.lVPn5jT3ohUO8uD0QLruZb-V9d5NDb1FOyl7OcHe7w"}
	https://www.deviantart.com/_napi/shared_api/deviation/group_add
	needs:
		csrf_token	THjPWGH1SRH_-epZ.py75wu.VTYjdFg4YApQBqsZBS5ISBHG1W4aWv-oTkk5WDA4t-w
		deviationid	298766207
		folderid	23881334
		groupid	11209451
		type	gallery
*/