CookieCloud

CookieCloud的tampermonkey版本,目前仅支持上传cookie,兼容移动端gear浏览器;

2024-10-13 या दिनांकाला. सर्वात नवीन आवृत्ती पाहा.

// ==UserScript==
// @name         CookieCloud
// @namespace    http://tampermonkey.net/
// @version      v0.20
// @description  CookieCloud的tampermonkey版本,目前仅支持上传cookie,兼容移动端gear浏览器;
// @author       tomato
// @icon         https://store-images.s-microsoft.com/image/apps.63473.a0ccb631-d5e7-422b-bcc7-c0405274114b.be044f83-1292-4e84-a65d-e0527d895863.05fc1666-519a-4d36-8b67-8110c70b45cc?mode=scale&h=64&q=90&w=64
// @match        *://*/*
// @grant        GM_cookie
// @grant        GM_xmlhttpRequest
// @grant        GM_notification
// @grant        GM_setValue
// @grant        GM_getValue
// @connect *
// @require      https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js
// @run-at       document-start
// @license MIT
// ==/UserScript==

/* global $, jQuery, CryptoJS */


(function() {
    'use strict';

    const configStoreKey = '_cookieCloudConfig';
    const positionKey = '__cookieCloudPositionTop';
    const zIndexNum = Number.MAX_SAFE_INTEGER;
    const themeColor = '236, 97, 91';

    function color(opacity) {
        return `rgba(${themeColor}, ${opacity})`
    }

    function createEle(tag, config = {style: {}}) {
        const ele = document.createElement(tag);
        const {style = {}, ...otherConfig } = config;
        Object.assign(ele.style, style);
        Object.assign(ele, (otherConfig || {}));
        return ele;
    }

    function initDrage(draggable, target) {
        let offsetX, offsetY, isDragging = false;

        // 开始拖拽
        function startDrag(event) {
            event.preventDefault();
            event.stopPropagation();
            isDragging = true;
            if (event.type === 'mousedown') {
                offsetX = event.clientX - draggable.getBoundingClientRect().left;
                offsetY = event.clientY - draggable.getBoundingClientRect().top;
            } else if (event.type === 'touchstart') {
                const touch = event.touches[0];
                offsetX = touch.clientX - draggable.getBoundingClientRect().left;
                offsetY = touch.clientY - draggable.getBoundingClientRect().top;
            }

            document.addEventListener('mousemove', onDrag);
            document.addEventListener('touchmove', onDrag);
        }

        // 拖拽中
        function onDrag(event) {
            if (!isDragging) return;
            let clientX, clientY;
            if (event.type === 'mousemove') {
                clientX = event.clientX;
                clientY = event.clientY;
            } else if (event.type === 'touchmove') {
                const touch = event.touches[0];
                clientX = touch.clientX;
                clientY = touch.clientY;
            }

            const newY = clientY - offsetY;
            draggable.style.top = `${Math.max(18, Math.min(window.innerHeight - draggable.offsetHeight, newY))}px`;
            storePosition(draggable.style.top);
            // 固定左边
            draggable.style.left = '0px';
        }

        function storePosition(top) {
            GM_setValue(positionKey, top);
        }

        // 结束拖拽
        function endDrag() {
            isDragging = false;
            document.removeEventListener('mousemove', onDrag);
            document.removeEventListener('touchmove', onDrag);
        }

        // 监听事件
        target.addEventListener('mousedown', startDrag);
        target.addEventListener('touchstart', startDrag);
        document.addEventListener('mouseup', endDrag);
        document.addEventListener('touchend', endDrag);
    }

    async function init() {
        const top = GM_getValue(positionKey);
        const btnContainer = createEle('section', {
            style: {
                position: 'fixed',
                top,
                left: '0px',
                background: color(0.6),
                borderRadius: '0 20px 20px 0',
                zIndex: zIndexNum,
            }
        });
        const btnStyles = {
            display: 'block',
            width: '40px',
            height: '40px',
            borderRadius: '50%',
            fontSize: '12px',
            backgroundColor: color(0.4),
            border: 'none',
            color: '#fff',
            margin: '10px',
            boxShadow: '0 4px 8px rgba(0, 0, 0, 0.3)'
        }
        const asyncBtn = createEle('button', {
            style: btnStyles,
            innerHTML: '上传'
        });
        const asyncConfigBtn = createEle('button', {
            style: btnStyles,
            innerHTML: '配置',
            onclick: function() {
                const modal = initConfigForm();
                document.body.appendChild(modal);
            }
        });
        const moveBtn = createEle('button', {
            style: {
                position: 'absolute',
                width: '18px',
                height: '18px',
                top: '-15px',
                right: '-15px',
                background: color(1),
                border: 'none',
                borderRadius: '50%',
                padding: '0',
                display: 'flex',
                justifyContent: 'center',
                alignItems: 'center',
                color: '#fff',
            },
            innerHTML: `<svg style="width: 12px;height: 12px;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1024"><path d="M501.0944 1021.824c6.9376 2.8928 14.8224 2.8928 21.8112 0 3.4304-1.4336 6.528-3.4816 9.1136-6.0672 0.0256 0 0.0768-0.0256 0.0768-0.0256l158.9248-158.9248c11.1104-11.1104 11.1104-29.1328 0-40.2176-11.0848-11.0848-29.0816-11.0848-40.1664 0.0256l-110.4384 110.4128 0.0256-335.36c0-15.6928-12.7232-28.416-28.416-28.416s-28.416 12.6976-28.416 28.3904l0 335.3856-110.4128-110.4128c-11.1104-11.0848-29.1072-11.0848-40.1408 0-11.1104 11.1104-11.136 29.1072-0.0512 40.192l158.9504 158.9248c0.8192 0.8192 1.8944 1.1776 2.816 1.8688C496.7168 1019.1872 498.688 1020.8256 501.0944 1021.824zM522.9056 2.176c-6.9376-2.8928-14.8224-2.8928-21.7856 0C497.6896 3.584 494.592 5.632 491.9808 8.2176c-0.0256 0-0.0768 0.0512-0.0768 0.0512L332.9792 167.168c-11.1104 11.1104-11.1104 29.1328 0 40.2176 11.0848 11.0848 29.0816 11.0848 40.1664-0.0256l110.4384-110.4128-0.0256 335.36c0 15.6928 12.7232 28.416 28.416 28.416 15.6928 0 28.4416-12.6976 28.4416-28.3904L540.416 96.9472l110.4128 110.4128c11.1104 11.0848 29.1072 11.0848 40.1408 0 11.1104-11.1104 11.1616-29.1072 0.0512-40.192l-158.9504-158.8992c-0.8192-0.8448-1.8944-1.2032-2.816-1.8944C527.2832 4.8128 525.312 3.1744 522.9056 2.176zM1021.824 522.9056c2.8928-6.9376 2.8928-14.8224 0-21.8112-1.408-3.4304-3.456-6.528-6.0416-9.1136 0-0.0256-0.0512-0.0768-0.0512-0.0768l-158.8992-158.9248c-11.1104-11.1104-29.1584-11.1104-40.2432 0-11.0592 11.0848-11.0592 29.0816 0.0512 40.1664l110.3872 110.4384-335.36-0.0256c-15.6928 0-28.3904 12.7232-28.3904 28.416s12.6976 28.416 28.3904 28.416l335.36 0-110.3872 110.4128c-11.1104 11.1104-11.1104 29.1072 0 40.1408 11.1104 11.1104 29.1072 11.136 40.192 0.0512l158.8992-158.9504c0.8448-0.8192 1.2032-1.8944 1.8944-2.816C1019.1872 527.2832 1020.8256 525.312 1021.824 522.9056zM2.176 501.0944c-2.8928 6.9376-2.8928 14.8224 0 21.7856 1.408 3.456 3.456 6.5536 6.0416 9.1392l0.0512 0.0512 158.8992 158.9504c11.1104 11.1104 29.1584 11.1104 40.2432 0 11.0592-11.1104 11.0592-29.1072-0.0512-40.192l-110.3872-110.4384 335.36 0.0512c15.6928 0 28.3904-12.7488 28.3904-28.416 0-15.6928-12.6976-28.4416-28.3904-28.4416l-335.36 0.0256 110.3872-110.4128c11.1104-11.1104 11.1104-29.1072 0-40.1408-11.1104-11.1104-29.1072-11.1616-40.192-0.0512l-158.8992 158.9504c-0.8448 0.8192-1.2032 1.8944-1.8944 2.816C4.8128 496.7168 3.1744 498.688 2.176 501.0944z" p-id="1025"></path></svg>`
        });

        btnContainer.appendChild(asyncBtn);
        btnContainer.appendChild(asyncConfigBtn);
        btnContainer.appendChild(moveBtn);
        document.body.appendChild(btnContainer);
        initDrage(btnContainer, moveBtn);
        
        // 为按钮添加点击事件
        asyncBtn.onclick = async function(event) {
            event.stopPropagation();
            const that = this;
            const config = GM_getValue(configStoreKey);
            if (!config) {
                msg('请填写配置');
                return;
            };
            const {url, uuid, password, domain = location.host} = JSON.parse(config);
            if (!url) {
                msg('请填写服务器地址');
                return;
            };
            if (!uuid) {
                msg('请填写uuid');
                return;
            };
            if (!password) {
                msg('请填写密码');
                return;
            };

            if (that.uploading) {
                return;
            }
            that.uploading = true;
            asyncBtn.innerText = '上传中';
            const cookies = await getCookie(domain);
            const encryptCookies = cookie_encrypt(uuid, password, cookies);

            const payload = {
                uuid,
                encrypted: encryptCookies
            };

            const res = await syncCookie(url, payload);
            try {
                const resData = JSON.parse(res.response)
                console.log('resData:', resData);
                that.uploading = false;
                asyncBtn.innerText = '上传';
                if (resData.action === 'done') {
                    msg('同步成功')
                } else {
                    throw('错误')
                }
            } catch(e) {
                that.uploading = false;
                asyncBtn.innerText = '上传';
                msg(String(e))
            }
        };
    }

    const msg = (function () {
        const originalAlert = window.alert;
        return (title) => {
            originalAlert(title);
        }
    })();

    function initConfigForm() {
        // 创建遮罩层
        const overlay = createEle('div', {
            style: {
                position: 'fixed',
                top: '0',
                left: '0',
                width: '100%',
                height: '100%',
                backgroundColor: 'rgba(0, 0, 0, 0.5)',
                zIndex: zIndexNum,
            }
        });

        // 创建弹框(Modal)容器
        const modal = createEle('div', {
            style: {
                position: 'fixed',
                top: '50%',
                left: '50%',
                transform: 'translate(-50%, -50%)',
                width: '95%',
                maxWidth: '400px',
                backgroundColor: '#fff',
                padding: '20px',
                boxShadow: '0 4px 10px rgba(0, 0, 0, 0.3)',
                zIndex: zIndexNum,
                borderRadius: '8px',
            }
        });

        // 创建表单
        const form = createEle('form');
        const inputStyles = {
            width: '100%',
            padding: '8px',
            boxSizing: 'border-box',
            border: '1px solid #ddd',
            outline: 'none',
            borderRadius: '8px',
            marginBottom: '10px'
        };

        // 创建同步域名关键词·默认当前域名
        const domainEle = createEle('textarea', {
            style: inputStyles,
            placeholder: '同步域名关键词·一行一个',
            rows: 3,
        });

        // 创建输入框 服务器地址
        const urlEle = createEle('input', {
            style: inputStyles,
            type: 'text',
            placeholder: '服务器地址',
        });

        // 创建输入框 端对端加密密码
        const pwdEle = createEle('input', {
            style: inputStyles,
            type: 'text',
            placeholder: '输入框 端对端加密密码',
        });

        // 创建输入框 用户KEY · UUID
        const uuieEle = createEle('input', {
            style: inputStyles,
            type: 'text',
            placeholder: '用户KEY · UUID',
        });

        // 创建保存按钮
        const saveButton = createEle('button', {
            style: {
                width: '100%',
                padding: '10px',
                backgroundColor: '#87CEEB',
                border: 'none',
                color: '#fff',
                cursor: 'pointer',
                fontSize: '16px',
                borderRadius: '4px',
            },
            type: 'submit',
            innerText: '保存',
        });

        const config = GM_getValue(configStoreKey);
        if (config) {
            const {url, uuid, password, domain = ''} = JSON.parse(config);
            urlEle.value = url;
            pwdEle.value = password;
            uuieEle.value = uuid;
            domainEle.value = domain;
        };

        saveButton.onclick = function () {
            const configStr = JSON.stringify({
                url: urlEle.value,
                password: pwdEle.value,
                uuid: uuieEle.value,
                domain: domainEle.value
            });
            GM_setValue(configStoreKey, configStr);
            overlay.remove();
        }
        modal.onclick = function (event) {
            event.stopPropagation();
        }
        overlay.onclick = function () {
            overlay.remove();
        }
        // 将输入框和保存按钮添加到表单
        form.appendChild(domainEle);
        form.appendChild(urlEle);
        form.appendChild(pwdEle);
        form.appendChild(uuieEle);
        form.appendChild(saveButton);

        // 将表单添加到弹框中
        modal.appendChild(form);
        overlay.appendChild(modal);
        return overlay;
    }

    // 用aes对cookie进行加密
    function cookie_encrypt( uuid, password, cookies ) {
        const the_key = CryptoJS.MD5(uuid+'-'+password).toString().substring(0,16);
        const data_to_encrypt = JSON.stringify({"cookie_data":cookies,"update_time":new Date()});
        return CryptoJS.AES.encrypt(data_to_encrypt, the_key).toString();
    }

    async function getCookie(domain) {
        const domains = domain?.trim().length > 0 ? domain?.trim().split("\n") : [];
        return new Promise((res, rej) => {
            GM_cookie.list({}, function(cookies, error) {
                console.log('cookies:', cookies)
                if (!error) {
                    const ret_cookies = {};
                    if( Array.isArray(domains) && domains.length > 0 ) {
                        console.log("domains", domains);
                        for( const domain of domains ) {
                            ret_cookies[domain] = [];
                            for( const cookie of cookies ) {
                                if( cookie.domain?.includes(domain) ) {
                                    ret_cookies[domain].push( cookie );
                                }
                            }
                        }
                    }
                    console.log('ret_cookies:', ret_cookies)
                    res(ret_cookies);
                } else {
                    console.error(error);
                    rej(error)
                }
            });
        })
    }
    // 上传cookie
    async function syncCookie(url, body) {
        return new Promise((res, rej) => {
            GM_xmlhttpRequest({
                method: 'POST',
                url: url+'/update',
                data: JSON.stringify(body),
                headers: {
                    'Content-Type': 'application/json',
                },
                onload: function(response) {
                    console.log('Response:', response.responseText);
                    res(response);
                },
                onerror: function(error) {
                    console.error('Error:', error);
                    rej(error);
                }
            });
        })
    }

    window.addEventListener('load', init);
})();