CookieCloud

CookieCloud的tampermonkey版本,目前仅支持上传cookie。

当前为 2024-09-25 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         CookieCloud
// @namespace    http://tampermonkey.net/
// @version      2024-09-24
// @description  CookieCloud的tampermonkey版本,目前仅支持上传cookie。
// @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
// @connect *
// @require      https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js
// @run-at       document-end
// @license MIT
// ==/UserScript==

/* global $, jQuery, CryptoJS */


(function() {
    'use strict';

    const configStoreKey = '_cookieCloudConfig';

    async function init() {
        const btnContainer = document.createElement('section');
        const asyncBtn = document.createElement('button');
        const asyncConfigBtn = document.createElement('button');
        asyncBtn.innerText = '上传';
        asyncConfigBtn.innerText = '配置';
        const btnContainerStyles = {
            position: 'fixed',
            bottom: '200px',
            left: '20px',
            fontSize: '14px',
            zIndex: 1000,
        };

        const btnStyles = {
            display: 'block',
            width: '50px',
            height: '50px',
            borderRadius: '50%',
            backgroundColor: '#87CEEB',
            border: 'none',
            color: '#fff',
            marginBottom: '10px',
            boxShadow: '0 4px 8px rgba(0, 0, 0, 0.3)'
        }
        Object.assign(btnContainer.style, btnContainerStyles);
        Object.assign(asyncBtn.style, btnStyles);
        Object.assign(asyncConfigBtn.style, btnStyles);

        btnContainer.appendChild(asyncBtn);
        btnContainer.appendChild(asyncConfigBtn);
        document.body.appendChild(btnContainer);

        asyncConfigBtn.onclick = function() {
            const modal = initConfigForm();
            document.body.appendChild(modal);
        }
        // 为按钮添加点击事件
        asyncBtn.onclick = async function(event) {
            event.stopPropagation();

            const config = localStorage.getItem(configStoreKey);
            if (!config) {
                alert('请填写配置');
                return;
            };
            const {url, uuid, password, domain = location.host} = JSON.parse(config);
            if (!url) {
                alert('请填写服务器地址');
                return;
            };
            if (!uuid) {
                alert('请填写uuid');
                return;
            };
            if (!password) {
                alert('请填写密码');
                return;
            };

            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);
                if (resData.action === 'done') {
                    alert('同步成功')
                } else {
                    throw('错误')
                }
            } catch(e) {
                alert(String(e))
            }
        };
    }

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

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

        // 创建表单
        const form = document.createElement('form');
        const inputStyles = {
            width: '100%',
            padding: '8px',
            boxSizing: 'border-box',
            marginBottom: '10px'
        };

        // 创建同步域名关键词·默认当前域名
        const domainEle = document.createElement('input');
        domainEle.type = 'text';
        domainEle.placeholder = '同步域名关键词·默认当前域名';
        Object.assign(domainEle.style, inputStyles);

        // 创建输入框 服务器地址
        const urlEle = document.createElement('input');
        urlEle.type = 'text';
        urlEle.placeholder = '服务器地址';
        Object.assign(urlEle.style, inputStyles);

        // 创建输入框 端对端加密密码
        const pwdEle = document.createElement('input');
        pwdEle.type = 'text';
        pwdEle.placeholder = '输入框 端对端加密密码';
        Object.assign(pwdEle.style, inputStyles);

        // 创建输入框 用户KEY · UUID
        const uuieEle = document.createElement('input');
        uuieEle.type = 'text';
        uuieEle.placeholder = '用户KEY · UUID';
        Object.assign(uuieEle.style, inputStyles);

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

        const config = localStorage.getItem(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
            });
            localStorage.setItem(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) {
        return new Promise((res, rej) => {
            GM_cookie.list({}, function(cookies, error) {
                if (!error) {
                    const ret_cookies = {};
                    for( const cookie of cookies ) {
                        if( cookie.domain?.includes(domain) ) {
                            if (!ret_cookies[domain]) {
                                ret_cookies[domain] = [cookie];
                            } else {
                                ret_cookies[domain].push(cookie);
                            }
                        }
                    }
                    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);
                }
            });
        })
    }

    init();
})();