Greasy Fork

Greasy Fork is available in English.

财新网自动登录

自动登录财新网账号

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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