CookieCloud

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

  1. // ==UserScript==
  2. // @name CookieCloud
  3. // @namespace http://tampermonkey.net/
  4. // @version v0.23
  5. // @description CookieCloud的tampermonkey版本,目前仅支持上传cookie,兼容移动端gear浏览器;
  6. // @author tomato
  7. // @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
  8. // @match *://*/*
  9. // @grant GM_cookie
  10. // @grant GM_xmlhttpRequest
  11. // @grant GM_notification
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // @connect *
  15. // @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js
  16. // @run-at document-start
  17. // @license MIT
  18. // ==/UserScript==
  19.  
  20. /* global $, jQuery, CryptoJS */
  21.  
  22.  
  23. (function() {
  24. 'use strict';
  25.  
  26. const configStoreKey = '_cookieCloudConfig';
  27. const positionKey = '__cookieCloudPositionTop';
  28. const zIndexNum = Number.MAX_SAFE_INTEGER;
  29. const themeColor = '236, 97, 91';
  30.  
  31. function color(opacity) {
  32. return `rgba(${themeColor}, ${opacity})`
  33. }
  34.  
  35. function createEle(tag, config = {style: {}}) {
  36. const ele = document.createElement(tag);
  37. const {style = {}, ...otherConfig } = config;
  38. Object.assign(ele.style, style);
  39. Object.assign(ele, (otherConfig || {}));
  40. return ele;
  41. }
  42.  
  43. function initDrage(draggable, target) {
  44. let offsetX, offsetY, isDragging = false;
  45.  
  46. // 开始拖拽
  47. function startDrag(event) {
  48. event.preventDefault();
  49. event.stopPropagation();
  50. isDragging = true;
  51. if (event.type === 'mousedown') {
  52. offsetX = event.clientX - draggable.getBoundingClientRect().left;
  53. offsetY = event.clientY - draggable.getBoundingClientRect().top;
  54. } else if (event.type === 'touchstart') {
  55. const touch = event.touches[0];
  56. offsetX = touch.clientX - draggable.getBoundingClientRect().left;
  57. offsetY = touch.clientY - draggable.getBoundingClientRect().top;
  58. }
  59.  
  60. document.addEventListener('mousemove', onDrag);
  61. document.addEventListener('touchmove', onDrag);
  62. }
  63.  
  64. // 拖拽中
  65. function onDrag(event) {
  66. if (!isDragging) return;
  67. let clientX, clientY;
  68. if (event.type === 'mousemove') {
  69. clientX = event.clientX;
  70. clientY = event.clientY;
  71. } else if (event.type === 'touchmove') {
  72. const touch = event.touches[0];
  73. clientX = touch.clientX;
  74. clientY = touch.clientY;
  75. }
  76.  
  77. const newY = clientY - offsetY;
  78. draggable.style.top = `${Math.max(18, Math.min(window.innerHeight - draggable.offsetHeight, newY))}px`;
  79. storePosition(draggable.style.top);
  80. // 固定左边
  81. draggable.style.left = '0px';
  82. }
  83.  
  84. function storePosition(top) {
  85. GM_setValue(positionKey, top);
  86. }
  87.  
  88. // 结束拖拽
  89. function endDrag() {
  90. isDragging = false;
  91. document.removeEventListener('mousemove', onDrag);
  92. document.removeEventListener('touchmove', onDrag);
  93. }
  94.  
  95. // 监听事件
  96. target.addEventListener('mousedown', startDrag);
  97. target.addEventListener('touchstart', startDrag);
  98. document.addEventListener('mouseup', endDrag);
  99. document.addEventListener('touchend', endDrag);
  100. }
  101.  
  102. async function init() {
  103. const defaultTop = window.innerHeight / 2;
  104. const top = GM_getValue(positionKey) || `${defaultTop}px`;
  105. const btnContainer = createEle('section', {
  106. style: {
  107. position: 'fixed',
  108. top,
  109. left: '0px',
  110. background: color(0.6),
  111. borderRadius: '0 20px 20px 0',
  112. zIndex: zIndexNum,
  113. }
  114. });
  115. const btnStyles = {
  116. display: 'block',
  117. width: '40px',
  118. height: '40px',
  119. borderRadius: '50%',
  120. fontSize: '12px',
  121. backgroundColor: color(0.4),
  122. border: 'none',
  123. color: '#fff',
  124. margin: '10px',
  125. boxShadow: '0 4px 8px rgba(0, 0, 0, 0.3)'
  126. }
  127. const asyncBtn = createEle('button', {
  128. style: btnStyles,
  129. innerHTML: '上传'
  130. });
  131. const asyncConfigBtn = createEle('button', {
  132. style: btnStyles,
  133. innerHTML: '配置',
  134. onclick: function() {
  135. const modal = initConfigForm();
  136. document.body.appendChild(modal);
  137. }
  138. });
  139. const moveBtn = createEle('button', {
  140. style: {
  141. position: 'absolute',
  142. width: '22px',
  143. height: '22px',
  144. top: '-15px',
  145. right: '-15px',
  146. background: color(1),
  147. border: 'none',
  148. borderRadius: '50%',
  149. padding: '0',
  150. display: 'flex',
  151. justifyContent: 'center',
  152. alignItems: 'center',
  153. color: '#fff',
  154. },
  155. 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>`
  156. });
  157.  
  158. btnContainer.appendChild(asyncBtn);
  159. btnContainer.appendChild(asyncConfigBtn);
  160. btnContainer.appendChild(moveBtn);
  161. document.body.appendChild(btnContainer);
  162. initDrage(btnContainer, moveBtn);
  163. // 为按钮添加点击事件
  164. asyncBtn.onclick = async function(event) {
  165. event.stopPropagation();
  166. const that = this;
  167. const config = GM_getValue(configStoreKey);
  168. if (!config) {
  169. msg('请填写配置');
  170. return;
  171. };
  172. const {url, uuid, password, domain = location.host} = JSON.parse(config);
  173. if (!url) {
  174. msg('请填写服务器地址');
  175. return;
  176. };
  177. if (!uuid) {
  178. msg('请填写uuid');
  179. return;
  180. };
  181. if (!password) {
  182. msg('请填写密码');
  183. return;
  184. };
  185.  
  186. if (that.uploading) {
  187. return;
  188. }
  189. that.uploading = true;
  190. asyncBtn.innerText = '上传中';
  191. const cookies = await getCookie(domain);
  192. const encryptCookies = cookie_encrypt(uuid, password, cookies);
  193.  
  194. const payload = {
  195. uuid,
  196. encrypted: encryptCookies
  197. };
  198.  
  199. const res = await syncCookie(url, payload);
  200. try {
  201. const resData = JSON.parse(res.response)
  202. console.log('resData:', resData);
  203. that.uploading = false;
  204. asyncBtn.innerText = '上传';
  205. if (resData.action === 'done') {
  206. msg('同步成功')
  207. } else {
  208. throw('错误')
  209. }
  210. } catch(e) {
  211. that.uploading = false;
  212. asyncBtn.innerText = '上传';
  213. msg(String(e))
  214. }
  215. };
  216. }
  217.  
  218. const msg = (function () {
  219. const originalAlert = window.alert;
  220. return (title) => {
  221. originalAlert(title);
  222. }
  223. })();
  224.  
  225. function initConfigForm() {
  226. // 创建遮罩层
  227. const overlay = createEle('div', {
  228. style: {
  229. position: 'fixed',
  230. top: '0',
  231. left: '0',
  232. width: '100%',
  233. height: '100%',
  234. backgroundColor: 'rgba(0, 0, 0, 0.5)',
  235. zIndex: zIndexNum,
  236. }
  237. });
  238.  
  239. // 创建弹框(Modal)容器
  240. const modal = createEle('div', {
  241. style: {
  242. position: 'fixed',
  243. top: '50%',
  244. left: '50%',
  245. transform: 'translate(-50%, -50%)',
  246. width: '95%',
  247. maxWidth: '400px',
  248. backgroundColor: '#fff',
  249. padding: '20px',
  250. boxShadow: '0 4px 10px rgba(0, 0, 0, 0.3)',
  251. zIndex: zIndexNum,
  252. borderRadius: '8px',
  253. }
  254. });
  255.  
  256. // 创建表单
  257. const form = createEle('form');
  258. const inputStyles = {
  259. width: '100%',
  260. padding: '8px',
  261. boxSizing: 'border-box',
  262. border: '1px solid #ddd',
  263. outline: 'none',
  264. borderRadius: '8px',
  265. marginBottom: '10px'
  266. };
  267.  
  268. // 创建同步域名关键词·默认当前域名
  269. const domainEle = createEle('textarea', {
  270. style: inputStyles,
  271. placeholder: '同步域名关键词·一行一个',
  272. rows: 3,
  273. });
  274.  
  275. // 创建输入框 服务器地址
  276. const urlEle = createEle('input', {
  277. style: inputStyles,
  278. type: 'text',
  279. placeholder: '服务器地址',
  280. });
  281.  
  282. // 创建输入框 端对端加密密码
  283. const pwdEle = createEle('input', {
  284. style: inputStyles,
  285. type: 'text',
  286. placeholder: '输入框 端对端加密密码',
  287. });
  288.  
  289. // 创建输入框 用户KEY · UUID
  290. const uuieEle = createEle('input', {
  291. style: inputStyles,
  292. type: 'text',
  293. placeholder: '用户KEY · UUID',
  294. });
  295.  
  296. // 创建保存按钮
  297. const saveButton = createEle('button', {
  298. style: {
  299. width: '100%',
  300. padding: '10px',
  301. backgroundColor: '#87CEEB',
  302. border: 'none',
  303. color: '#fff',
  304. cursor: 'pointer',
  305. fontSize: '16px',
  306. borderRadius: '4px',
  307. },
  308. type: 'submit',
  309. innerText: '保存',
  310. });
  311.  
  312. const config = GM_getValue(configStoreKey);
  313. if (config) {
  314. const {url, uuid, password, domain = ''} = JSON.parse(config);
  315. urlEle.value = url;
  316. pwdEle.value = password;
  317. uuieEle.value = uuid;
  318. domainEle.value = domain;
  319. };
  320.  
  321. saveButton.onclick = function () {
  322. const configStr = JSON.stringify({
  323. url: urlEle.value,
  324. password: pwdEle.value,
  325. uuid: uuieEle.value,
  326. domain: domainEle.value
  327. });
  328. GM_setValue(configStoreKey, configStr);
  329. overlay.remove();
  330. }
  331. modal.onclick = function (event) {
  332. event.stopPropagation();
  333. }
  334. overlay.onclick = function () {
  335. overlay.remove();
  336. }
  337. // 将输入框和保存按钮添加到表单
  338. form.appendChild(domainEle);
  339. form.appendChild(urlEle);
  340. form.appendChild(pwdEle);
  341. form.appendChild(uuieEle);
  342. form.appendChild(saveButton);
  343.  
  344. // 将表单添加到弹框中
  345. modal.appendChild(form);
  346. overlay.appendChild(modal);
  347. return overlay;
  348. }
  349.  
  350. // 用aes对cookie进行加密
  351. function cookie_encrypt( uuid, password, cookies ) {
  352. const the_key = CryptoJS.MD5(uuid+'-'+password).toString().substring(0,16);
  353. const data_to_encrypt = JSON.stringify({"cookie_data":cookies,"update_time":new Date()});
  354. return CryptoJS.AES.encrypt(data_to_encrypt, the_key).toString();
  355. }
  356.  
  357. async function getCookie(domain) {
  358. const domains = domain?.trim().length > 0 ? domain?.trim().split("\n") : [];
  359. return new Promise((res, rej) => {
  360. GM_cookie.list({}, function(cookies, error) {
  361. console.log('cookies:', cookies)
  362. if (!error) {
  363. const ret_cookies = {};
  364. if( Array.isArray(domains) && domains.length > 0 ) {
  365. console.log("domains", domains);
  366. for( const domain of domains ) {
  367. ret_cookies[domain] = [];
  368. for( const cookie of cookies ) {
  369. if( cookie.domain?.includes(domain) ) {
  370. ret_cookies[domain].push( cookie );
  371. }
  372. }
  373. }
  374. }
  375. console.log('ret_cookies:', ret_cookies)
  376. res(ret_cookies);
  377. } else {
  378. console.error(error);
  379. rej(error)
  380. }
  381. });
  382. })
  383. }
  384. // 上传cookie
  385. async function syncCookie(url, body) {
  386. return new Promise((res, rej) => {
  387. GM_xmlhttpRequest({
  388. method: 'POST',
  389. url: url+'/update',
  390. data: JSON.stringify(body),
  391. headers: {
  392. 'Content-Type': 'application/json',
  393. },
  394. onload: function(response) {
  395. console.log('Response:', response.responseText);
  396. res(response);
  397. },
  398. onerror: function(error) {
  399. console.error('Error:', error);
  400. rej(error);
  401. }
  402. });
  403. })
  404. }
  405.  
  406. window.addEventListener('load', init);
  407. })();