Caixin Auto Login

Automatic login script for Caixin.com with credential management

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name                  Caixin Auto Login
// @name:zh-CN            财新网自动登录
// @license               MIT
// @namespace             https://www.caixin.com/
// @version               1.4
// @description           Automatic login script for Caixin.com with credential management
// @description:zh-CN     自动登录财新网账号
// @author                https://github.com/hxueh
// @match                 *://*.caixin.com/*
// @grant                 GM_setValue
// @grant                 GM_getValue
// @grant                 GM_xmlhttpRequest
// @grant                 GM_registerMenuCommand
// @grant                 GM_addStyle
// @require               https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js
// @connect               gateway.caixin.com
// @icon                  https://www.caixin.com/favicon.ico
// ==/UserScript==

(function () {
  "use strict";

  // Configuration constants
  const CONFIG = {
    API: {
      LOGIN: "https://gateway.caixin.com/api/ucenter/user/v1/loginJsonp",
      USER_INFO: "https://gateway.caixin.com/api/ucenter/userinfo/get",
    },
    ENCRYPTION: {
      KEY: "G3JH98Y8MY9GWKWG",
      MODE: CryptoJS.mode.ECB,
      PADDING: CryptoJS.pad.Pkcs7,
    },
    COOKIE: {
      DOMAIN: ".caixin.com",
      MAX_AGE: 604800, // 7 days in seconds
    },
    LOGIN_PARAMS: {
      DEVICE_TYPE: getDeviceType(),
      UNIT: "1",
      AREA_CODE: "+86",
    },
  };

  /**
   * Determines the device type based on user agent
   * @returns {string} - "3" for mobile devices, "5" for desktop
   */
  function getDeviceType() {
    const userAgent = navigator.userAgent.toLowerCase();
    const isMobile = /android|iphone|ipad|ipod|webos|windows phone/i.test(
      userAgent
    );
    return isMobile ? "3" : "5";
  }

  /**
   * Encrypts the password using AES encryption
   * @param {string} password - The password to encrypt
   * @returns {string} - URL encoded encrypted password
   */
  function encrypt(password) {
    const keyWordArray = CryptoJS.enc.Utf8.parse(CONFIG.ENCRYPTION.KEY);
    const encrypted = CryptoJS.AES.encrypt(password, keyWordArray, {
      mode: CONFIG.ENCRYPTION.MODE,
      padding: CONFIG.ENCRYPTION.PADDING,
    });
    return encodeURIComponent(encrypted.toString());
  }

  /**
   * Sets a cookie with standard Caixin parameters
   * @param {string} name - Cookie name
   * @param {string} value - Cookie value
   */
  function setCaixinCookie(name, value) {
    const cookieOptions = [
      `${name}=${value}`,
      `Path=/`,
      `Domain=${CONFIG.COOKIE.DOMAIN}`,
      "Secure=true",
      `max-age=${CONFIG.COOKIE.MAX_AGE}`,
    ].join("; ");

    document.cookie = cookieOptions;
  }

  /**
   * Checks if the user is currently logged in
   * @returns {Promise<boolean>} - True if logged in, false otherwise
   */
  async function isLogin() {
    try {
      const response = await makeRequest({
        method: "GET",
        url: CONFIG.API.USER_INFO,
      });
      return response.code === 0;
    } catch (error) {
      console.error("Login check failed:", error);
      return false;
    }
  }

  /**
   * Makes an XMLHttpRequest using GM_xmlhttpRequest
   * @param {Object} options - Request options
   * @returns {Promise} - Resolves with parsed JSON response
   */
  function makeRequest(options) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        ...options,
        onload: (response) => resolve(JSON.parse(response.responseText)),
        onerror: reject,
      });
    });
  }

  /**
   * Performs the login process using stored credentials
   * @returns {Promise<void>}
   */
  async function performLogin() {
    const credentials = {
      phoneNumber: GM_getValue("phoneNumber"),
      password: GM_getValue("password"),
    };

    if (!credentials.phoneNumber || !credentials.password) {
      console.log(
        "Credentials not found. Please set phone number and password."
      );
      return;
    }

    const loginUrl = new URL(CONFIG.API.LOGIN);
    const params = {
      account: credentials.phoneNumber,
      password: encrypt(credentials.password),
      deviceType: CONFIG.LOGIN_PARAMS.DEVICE_TYPE,
      unit: CONFIG.LOGIN_PARAMS.UNIT,
      areaCode: CONFIG.LOGIN_PARAMS.AREA_CODE,
    };

    Object.entries(params).forEach(([key, value]) => {
      loginUrl.searchParams.append(key, value);
    });

    try {
      const response = await makeRequest({
        method: "GET",
        url: loginUrl.toString(),
      });

      if (response.code !== 0) {
        throw new Error(`Login failed with code: ${response.code}`);
      }

      // Set authentication cookies
      const { uid, code, deviceType, unit, userAuth } = response.data;
      const cookies = {
        SA_USER_auth: userAuth,
        SA_USER_DEVICE_TYPE: deviceType,
        SA_USER_UID: uid,
        SA_USER_UNIT: unit,
        USER_LOGIN_CODE: code,
      };

      Object.entries(cookies).forEach(([name, value]) => {
        setCaixinCookie(name, value);
      });

      location.reload();
    } catch (error) {
      console.error("Login process failed:", error);
    }
  }

  /**
   * Creates and displays the settings window UI
   */
  function showSettingsWindow() {
    const styles = `
        .caixin-settings-overlay {
          position: fixed;
          top: 0;
          left: 0;
          right: 0;
          bottom: 0;
          background: rgba(0, 0, 0, 0.5);
          z-index: 999999;
          display: flex;
          justify-content: center;
          align-items: center;
        }
  
        .caixin-settings-window {
          background: white;
          padding: 20px;
          border-radius: 8px;
          box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
          width: 300px;
        }
  
        .caixin-settings-window h2 {
          margin: 0 0 20px 0;
          font-size: 18px;
          color: #333;
        }
  
        .caixin-settings-window .form-group {
          margin-bottom: 15px;
        }
  
        .caixin-settings-window label {
          display: block;
          margin-bottom: 5px;
          color: #666;
        }
  
        .caixin-settings-window input {
          width: 100%;
          padding: 8px;
          border: 1px solid #ddd;
          border-radius: 4px;
          box-sizing: border-box;
        }
  
        .caixin-settings-window .buttons {
          display: flex;
          justify-content: flex-end;
          gap: 10px;
          margin-top: 20px;
        }
  
        .caixin-settings-window button {
          padding: 8px 16px;
          border: none;
          border-radius: 4px;
          cursor: pointer;
        }
  
        .caixin-settings-window .save-btn {
          background: #4CAF50;
          color: white;
        }
  
        .caixin-settings-window .cancel-btn {
          background: #f5f5f5;
          color: #333;
        }
  
        .caixin-settings-window button:hover {
          opacity: 0.9;
        }
      `;

    GM_addStyle(styles);

    const overlay = document.createElement("div");
    overlay.className = "caixin-settings-overlay";

    const currentSettings = {
      phone: GM_getValue("phoneNumber", ""),
      password: GM_getValue("password", ""),
    };

    const window = document.createElement("div");
    window.className = "caixin-settings-window";
    window.innerHTML = `
        <h2>Caixin Login Settings</h2>
        <div class="form-group">
          <label for="caixin-phone">Phone Number:</label>
          <input type="text" id="caixin-phone" value="${currentSettings.phone}" placeholder="+86 Phone Number">
        </div>
        <div class="form-group">
          <label for="caixin-password">Password:</label>
          <input type="password" id="caixin-password" value="${currentSettings.password}" placeholder="Password">
        </div>
        <div class="buttons">
          <button class="cancel-btn">Cancel</button>
          <button class="save-btn">Save</button>
        </div>
      `;

    function closeSettings() {
      document.body.removeChild(overlay);
    }

    // Event Handlers
    window.querySelector(".save-btn").addEventListener("click", () => {
      const phone = window.querySelector("#caixin-phone").value;
      const password = window.querySelector("#caixin-password").value;

      if (phone && password) {
        GM_setValue("phoneNumber", phone);
        GM_setValue("password", password);
        closeSettings();
        performLogin();
      } else {
        console.log("Please fill in both fields.");
      }
    });

    window
      .querySelector(".cancel-btn")
      .addEventListener("click", closeSettings);
    overlay.addEventListener("click", (e) => {
      if (e.target === overlay) closeSettings();
    });

    overlay.appendChild(window);
    document.body.appendChild(overlay);
  }

  // Initialize the script
  async function init() {
    const loggedIn = await isLogin();
    if (!loggedIn) {
      console.log("Not logged in. Attempting login...");
      performLogin();
    }
  }

  // Register settings menu command
  GM_registerMenuCommand("⚙️ Caixin Login Settings", showSettingsWindow);

  // Start the script
  init();
})();