Greasy Fork 还支持 简体中文。

CookieCloud

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

目前為 2024-10-08 提交的版本,檢視 最新版本

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