Caixin Auto Login

Automatic login script for Caixin.com with credential management

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==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();
})();