// ==UserScript== // @name CookieCloud // @namespace http://tampermonkey.net/ // @version v0.23 // @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 // @downloadURL https://update.greasyfork.icu/scripts/510119/CookieCloud.user.js // @updateURL https://update.greasyfork.icu/scripts/510119/CookieCloud.meta.js // ==/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 defaultTop = window.innerHeight / 2; const top = GM_getValue(positionKey) || `${defaultTop}px`; 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: '22px', height: '22px', top: '-15px', right: '-15px', background: color(1), border: 'none', borderRadius: '50%', padding: '0', display: 'flex', justifyContent: 'center', alignItems: 'center', color: '#fff', }, innerHTML: `` }); 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); })();