Garticphone DRAW bot

Auto drawing bot!

// ==UserScript==
// @name         Garticphone DRAW bot
// @namespace    http://tampermonkey.net/
// @version      0.2.8
// @license      GNU
// @description  Auto drawing bot!
// @author       S&D (Scripts and Deeps team) - StickySkull & DoctorDeathDDrac & 44Type & HendyKey SAMA & Kristofer Sadness & 69Type
// @source       https://t.me/doctordeathddracula
// @source       https://t.me/stickyskull
// @supportURL   https://discord.gg/sHj5UauJZ4

// @match        *://garticphone.com/*
// @connect      garticphone.com
// @connect      greasyfork.org
// @exclude      *://garticphone.com/_next/*

// @icon         https://www.google.com/s2/favicons?domain=garticphone.com

// @grant        unsafeWindow
// @grant        GM_xmlhttpRequest
// @grant        GM_log

// @run-at       document-start
// ==/UserScript==








let me = {
    cn: (tag, options, parent) => {
        let t = Object.assign(document.createElement(tag), options);
        if (parent?.appendChild) parent.appendChild(t);
        return t;
    },
    c: {
        closeTimer: 15,
    },
    s: {
        save: ( key, value ) => {
            me.s[key] = value;
            localStorage.my = window.btoa(JSON.stringify(me.s.bank))
        },
        initStorage: () => {
            me.s.bank = Object.assign(me.s.bank, JSON.parse(window.atob(window.localStorage.my)));
            localStorage.my = window.btoa(JSON.stringify(me.s.bank));
        },
        resaveStorage: () => {
            localStorage.my = window.btoa(JSON.stringify(me.s.bank));
        },
        bank: {},
        ss: {
            onsettings: false,
        },
        html: {},
    },
    f: {
        request: function (url) {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    url,
                    method: 'GET',
                    onload: (response) => resolve(response.responseText),
                });
            });
        },
        requestPureJS: function (url) {
            return new Promise(resolve => {
                var xhr = new XMLHttpRequest();
                xhr.responseType = "arraybuffer";
                xhr.open('GET', url);
                xhr.send();
                xhr.onload = () => resolve(xhr.response)
            });
        },
        rgbToHex: function (r, g, b) {
            return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
        },
    },
    texts: {
        settings: {
            title: 'SETTINGS',
            chooseFile: 'CHOOSE FILE...\nOR CTRL+V',
            print: 'PRINT',
            clear: 'CLEAR',
        },
        update: {
            close: 'CLOSE',
            check: 'CHECK UPDATE',
            install: 'INSTALL',
            newTitle: 'NEW VERSION OF %s (%s) AVAILABLE!',
            closeIn: 'AUTO CLOSING IN... '
        },
    },
    game: {
        getScale: () => {return (window.innerWidth - (window.innerWidth < 1920 ? 180 : 320)) / 1150 },
        isAnimation: () => {return Boolean(document.getElementsByClassName('note').length)},
    },
    extension: {
        GM: GM,
    },
}



Node.prototype.appendChild = new Proxy( Node.prototype.appendChild, {
    async apply(target, thisArg, [element]) {
        if (element.tagName == "SCRIPT") {
            if (element.src.indexOf('draw') != -1) {
                let text = await me.f.request(element.src);
                text = editScript(text);
                text = window.btoa(text);
                element.src = `data:application/javascript;base64,${text}`;
            }
        }
        return Reflect.apply( ...arguments );
    }
});


class MyWebSocket extends WebSocket {
    constructor(...args) {
        let ws = super(...args);
        me.s.ss.ws = ws;
        ws.addEventListener('message', (e) => {
            if (e.data && typeof e.data == 'string' && e.data.includes('[')) {
                let t = JSON.parse(e.data.replace(/[^\[]{0,}/, ''))[2];
                if (t?.hasOwnProperty('turnNum')) me.s.ss.turnNum = t.turnNum;
            }
        });
        return ws;
    }
    send(...args) {
        return super.send(...args);
    }
};
unsafeWindow.WebSocket = MyWebSocket;


String.prototype.format = function (){
    let string = this.toString();
    for (let i=0; i<arguments.length; i++){
        string = string.replace('%s', arguments[i])
    }
    return string;
}


/* stroke configuration note */
/* [toolID, strokeID, [color, 18, 0.6], [x0, y0]. [x1, y1], ..., [xn, yn]] */

function editScript(text){
    let functionFinalDraw = text.match(/function\s\w{1,}\(\w{0,}\){[^\{]+{[^\}]{0,}return\[\]\.concat\(Object\(\w{0,}\.*\w{0,}\)\(\w{0,}\),\[\w{0,}\]\)[^\}]{0,}}[^\}]{0,}}/g)[0];
    let setDataVar = functionFinalDraw.match(/\w{1,}(?=\.setData)/g)[0];
    text = text.replace(/(?<=\(\(function\(\){)(?=if\(!\w{1,}\.disabled\))/, `;window.setData = ${setDataVar}.setData;`);
    return text;
}













function draw(image, x=0, y=0, width=1516, height=848, pen_size=2, pixel=false){
    let story = [];
    let canvas = me.cn('canvas', {
        width: 848,
        height: 424,
    }),
        ctx = canvas.getContext('2d');
    ctx.drawImage(image, x, y, 848, 424);

    let data = ctx.getImageData(0, 0, 848, 424).data;

    if (me.game.isAnimation()){
        for (let i=0; i<848*424; i++) {
            y = i / 848 | 0;
            x = i - 848 * (i / 848 | 0);
            let pos = y * 848 * 4 + x * 4,
                color = me.f.rgbToHex(data[pos], data[pos+1], data[pos+2]);

            me.s.ss.ws.send(`42[2,7,{"t":${me.s.ss.turnNum},"d":1,"v":[1,-1,["${color}",${pen_size},${data[pos+3]/255}],[${x},${y}]]}]`);
            story.push([1, -1, [color, 2, data[3]/255], [x, y]]);
        }
        console.log('DONE');
        unsafeWindow.setData( (function(e){ return story })() )

    } else {
        let dict = {};
        for (let i=0; i<848*424; i++) {
            y = i / 848 | 0;
            x = i - 848 * (i / 848 | 0);
            let pos = y * 848 * 4 + x * 4,
                color = me.f.rgbToHex(data[pos], data[pos+1], data[pos+2]);
            if (!dict[color]) {
                dict[color] = `[8,-1,["${color}",${data[3]/255}],${x},${y},1,1`;
            } else {
                dict[color] += `,${x},${y},${1},${1}`;
            }
        }
        for (let key in dict){
            let stroke = `42[2,7,{"t":${me.s.ss.turnNum},"d":1,"v":`+dict[key]+`]}]`;
            story.push(JSON.parse(dict[key] + `]`));
            me.s.ss.ws.send(stroke);
        }
        unsafeWindow.setData( (function(e){ return story })() )
    }

}









/* first function */
(() => {
    document.addEventListener('DOMContentLoaded', createButton);
    window.addEventListener('resize', () => {
        let e = me.game.getScale();
        if (me.s.html.updateBackground) me.s.html.updateBackground.firstChild.style.transform = `scale(${e/1.4})`;
        if (me.s.html.background) me.s.html.background.firstChild.style.transform = `scale(${e/1.2})`;
        if (document.querySelector("#my-sb")) document.querySelector("#my-sb").style.transform = `scale(${e/1.2})`;
    });
})();

function createButton(){
    document.head.insertAdjacentHTML('beforeEnd', '<style type="text/css">.side{display:none !important;}.settings-button:hover{color:#000 !important;background-color:#fff !important;}.hover-input-box:hover{border-color:#000 !important}.setting-button:hover{background-color:#fff !important;box-shadow:#000 0 4px}.setting-button{transform-origin:top;position:fixed;z-index:10;height:30px;background-color:rgb(0,0,0);top:0;left:30px;border-radius:0 0 10px 10px;box-shadow:#fff 0 3px;cursor:pointer;text-align:center;display:flex;flex-direction:row;-webkit-box-pack:center;justify-content:center;-webkit-box-align:center;align-items:center;padding:0 12px 0 10px}.setting-button:hover :first-child{color:#000 !important}.setting-button:hover :last-child{color:#000 !important}.settings-opened{background-color:#fff !important;box-shadow:#000 0 4px}.settings-opened :last-child{color:#000 !important}.settings-opened :first-child{color:#000 !important}</style>');
    document.body.insertAdjacentHTML('beforeEnd', '<div id="my-sb" class="setting-button"><div style="color: rgb(255, 255, 255); font-size: 22px; margin: 0px 4px 0px 0px;">⚙</div><div style="color: rgb(255, 255, 255); font-family: Black;">AUTO DRAW</div></div>');
    document.querySelector("#my-sb").onclick = me.f.generateSettingsMenu ;
    document.querySelector("#my-sb").style.transform = `scale(${me.game.getScale()/1.2})`;
}



me.f.closeSettings = function () {
    if (!me.s.html.background) return;
    document.querySelector("#my-sb").classList.remove('settings-opened');
    me.s.html.background.parentNode.removeChild(me.s.html.background);
    delete me.s.html.background
}


me.f.generateSettingsMenu = function () {
    if (me.s.html.background) return me.f.closeSettings();

    document.querySelector("#my-sb").classList.add('settings-opened');

    me.s.html.background = me.cn('div', {
        id: 'my-bg',
        style: 'position:absolute;display:flex;inset:0;background-color:rgba(0,0,0,0.8);-webkit-box-pack:center;justify-content:center;-webkit-box-align:center;align-items:center;z-index:5;',
    }, document.body);

    let settings = me.cn('div', {
        style: `position:relative;display:flex;flex-direction:column;-webkit-box-align:center;align-items:center;background-color:rgb(255,255,255);padding:30px;border-radius:12px;transform:scale(${me.game.getScale()/1.2});`,
    }, me.s.html.background),

        settingsTitle = me.cn('div', {
            innerText: me.texts.settings.title,
            style: "font-family:'Black';font-size:35px;margin:0 0 20px;color:#000;",
        }, settings),

        inputBox = me.cn('div', {
            id: 'input-box',
            className: "hover-input-box",
            style: `width:758px;height:424px;border:4px dashed lightgray;border-radius:17px;cursor:pointer;background-image:url(${me.s.ss.imageData});background-size:95% 95%;background-repeat:no-repeat;background-position:center;`,
        }, settings),

        fileTitle = me.cn('div', {
            style: "text-align:center;font-family:'Black';font-size:50px;color:lightgray;position:relative;top:160px;",
            innerText: me.texts.settings.chooseFile,
        }, inputBox),

        fileInput = me.cn('input', {
            id: 'file-title',
            type: 'file',
            style: 'width:758px;height:424px;cursor:pointer;position:relative;top:-100px;opacity:0;',
            title: '',
            accept: 'image/*' ,
            oninput: async (e) => {
                me.f.imageLoading();
                let data = await me.f.getDataFromInternalStorage(e);
                me.f.createImage(data);
                me.f.imageLoaded();
            },
        }, inputBox),
        closeButton = me.cn('button', {
            innerText: '',
            style: 'border:none;background:none;position:absolute;top:15px;right:15px;width:30px;height:30px;display:flex;-webkit-box-align:center;align-items:center;-webkit-box-pack:center;justify-content:center;cursor:pointer;font-family:ico;color:#000;font-size:25px;',
            onclick: me.f.closeSettings,
        }, settings),

        bottom = me.cn('div', {
            style: 'display:flex;flex-direction:row;margin: 20px 0px 0px;',
        }, settings),

        linkInput = me.cn('input', {
            id: "link-input",
            placeholder: "URL",
            type: 'text',
            style: 'height:46px;display:block;border:none;background-color:rgba(255,255,255,.3);border:4px #000 solid;border-radius:7px;font-family:"Bold";font-size:28px;color:#000;padding:0 10px;',
            oninput: async () => {
                let url = linkInput.value,
                    imageText = await Promise.any([me.f.requestPureJS(url), me.f.request(url)]).catch(() => '');
                if (imageText){
                    var bb = new Blob([imageText], {type: 'image/png'});
                    var reader = new FileReader();
                    reader.onload = function (e) {
                        me.f.createImage(e.target.result);
                    };
                    reader.readAsDataURL (bb);
                }
            },
        }, bottom),

        clearButton = me.cn('button', {
            className: 'settings-button',
            innerText: me.texts.settings.clear,
            style: "color:#fff;font-size:24px;border-radius:7px;border:4px solid #000;background-color:#000;font-family:'Black';margin:0 0 0 10px;padding:0 30px;cursor:pointer;",
            onclick: () => {
                delete me.s.ss.imageData;
                linkInput.value = "";
                inputBox.style.backgroundImage = '';
                fileInput.type = "text";
                fileInput.type = "file";
                fileInput.value = "";
            },
        }, bottom),

        printButton = me.cn('button', {
            className: 'settings-button',
            innerText: me.texts.settings.print,
            style: "color:#fff;font-size:24px;border-radius:7px;border:4px solid #000;background-color:#000;font-family:'Black';margin:0 0 0 10px;padding:0px 55px;cursor:pointer;",
            onclick: () => {
                if (window.location.href.indexOf('draw') != -1){
                    me.f.closeSettings();
                    draw(me.s.ss.image);
                } else {
                    alert('You not in the draw section');
                }
            },
        }, bottom);

}



me.f.imageLoading = function(){}
me.f.imageLoaded = function(){}




me.f.getDataFromInternalStorage = function(e) {
    return new Promise ( resolve => {
        let file = e.target.files[0],
            fr = new FileReader();
        fr.readAsDataURL(file);
        fr.onload = (e) => {resolve(e.target.result)};
    })
}


me.f.createImage = function(data){
    (!data || !me.s.html.background) && alert('something goes wrong');
    me.s.ss.imageData = data;
    document.querySelector("#input-box").style.backgroundImage = `url(${data})`;
    me.s.ss.image = new Image();
    me.s.ss.image.src = data;
}



document.onpaste = async function(event){
    if (!me.s.html.background) return;
    var items = (event.clipboardData || event.originalEvent.clipboardData).items;
    for (let index in items) {
        var item = items[index];
        if (item.kind === 'file') {
            me.f.imageLoading = function(){}
            let data = await (() => {
                return new Promise(resolve => {
                    var blob = item.getAsFile();
                    var reader = new FileReader();
                    reader.onload = function(event) {
                        resolve(event.target.result)
                    };
                    reader.readAsDataURL(blob);
                })
            })()
            me.f.createImage(data);
            me.f.imageLoaded = function(){}
        }
    }
}











/*-------------------------------------------AUTO-UPDATE--------------------------------------------*/
/*-IS-THAT-LEGAL,-GREASEFORK?-----------------------------------------------------------------------*/
/*--------------------------------------------------------------------------------------------------*/

me.f.createPopup = function(){
    me.s.html.updateBackground = me.cn('div', {
        id: 'my-bg',
        style: 'position:absolute;display:flex;inset:0;background-color:rgba(0,0,0,0.8);-webkit-box-pack:center;justify-content:center;-webkit-box-align:center;align-items:center;z-index:100;',
    }, document.body);
    let settings = me.cn('div', {
        style: `position:relative;display:flex;flex-direction:column;-webkit-box-align:center;align-items:center;background-color:rgb(255,255,255);padding:30px;border-radius:12px;transform:scale(${me.game.getScale()/1.4});`,
    }, me.s.html.updateBackground),
        settingsTitle = me.cn('div', {
            innerText: me.texts.update.newTitle.format(GM.info.script.name, me.s.ss.version),
            style: "font-family:'Black';font-size:35px;margin:0 0 20px;color:#000;",
        }, settings),

        bottom = me.cn('div', {
            style: 'height:55px;display:flex;flex-direction:row;margin:20px 0px 0px;width:100%;',
        }, settings),

        closeButton = me.cn('button', {
            className: 'settings-button',
            innerText: me.texts.update.close,
            style: "flex:1;color:#fff;font-size:24px;border-radius:7px;border:4px solid #000;background-color:#000;font-family:'Black';cursor:pointer;",
            onclick: () => {
                me.s.html.updateBackground.parentNode.removeChild(me.s.html.updateBackground);
            },
        }, bottom),

        checkForUpdates = me.cn('button', {
            className: 'settings-button',
            innerText: me.texts.update.check,
            style: "flex:1;color:#fff;font-size:24px;border-radius:7px;border:4px solid #000;background-color:#000;font-family:'Black';margin:0 0 0 10px;cursor:pointer;",
            onclick: () => {
                window.open('https://greasyfork.org/ru/scripts/436728-garticphone-draw-bot' + '/versions', '_blank');
                me.s.html.updateBackground.parentNode.removeChild(me.s.html.updateBackground);
            },
        }, bottom),

        installNow = me.cn('button', {
            className: 'settings-button',
            innerText: me.texts.update.install,
            style: "flex:1;color:#fff;font-size:24px;border-radius:7px;border:4px solid #000;background-color:#000;font-family:'Black';margin:0 0 0 10px;cursor:pointer;",
            onclick: () => {
                window.location.replace(me.s.ss.newLink);
                me.s.html.updateBackground.parentNode.removeChild(me.s.html.updateBackground);
            },
        }, bottom),

        autoCloseText = me.cn('div', {
            innerText: me.texts.update.closeIn + me.c.closeTimer,
            style: "flex:1;color:#000;font-size:20px;border-radius:7px;font-family:'Black';margin:20px 0 0;",
        }, settings);

    me.s.ss.updateCounter = me.c.closeTimer - 1;
    me.s.ss.updateIntervalID = setInterval( () => {
        autoCloseText.innerText= me.texts.update.closeIn + me.s.ss.updateCounter;
        me.s.ss.updateCounter--;
        if (me.s.ss.updateCounter < -1) {
            me.s.html.updateBackground.parentNode.removeChild(me.s.html.updateBackground);
            clearInterval(me.s.ss.updateIntervalID);
        }
    }, 1000);
};


(async () => {
    let htmlText = await me.f.request('https://greasyfork.org/ru/scripts/436728-garticphone-draw-bot'),
        div = document.createElement('div');
    div.insertAdjacentHTML('afterBegin', htmlText);
    let currentVersion = GM.info.script.version,
        newestVersion = div.getElementsByClassName('script-show-version')[1].innerText;
    me.s.ss.version = newestVersion;
    me.s.ss.newLink = div.getElementsByClassName('install-link')[0].href.replace('https://garticphone.com/', 'https://greasyfork.org/');

    console.log(currentVersion, newestVersion);
    console.log(me.s.ss.newLink);

    if (newestVersion && currentVersion != newestVersion) me.f.createPopup();
})();