TrixVPN

Global VPN Service Project

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

Bạn sẽ cần cài đặt một tiện ích mở rộng như Tampermonkey hoặc Violentmonkey để cài đặt kịch bản này.

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

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

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

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         TrixVPN
// @namespace    https://greasyfork.org/en/users/1490385-courtesycoil
// @version      0.06
// @description  Global VPN Service Project
// @author       Painsel
// @license      Copyright Painsel - All rights reserved
// @match        *://*/*
// @icon         data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="45" fill="%234CAF50"/><text x="50" y="65" font-size="40" fill="white" text-anchor="middle" font-weight="bold">VPN</text></svg>
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @grant        GM_notification
// @grant        GM_xmlhttpRequest
// @run-at       document-start
// ==/UserScript==

/*
  TrixVPN
  Copyright Painsel - All rights reserved
  https://greasyfork.org/en/users/1490385-courtesycoil
  
  Unauthorized copying, modification, or distribution of this script is prohibited.
*/

(function() {
  'use strict';

  // ==================== Configuration ====================
  // Copyright Painsel - All rights reserved
  const CONFIG = {
    // Mode: 'proxy' (free) or 'vpn' (subscription-based)
    mode: 'proxy', // Change to 'vpn' to use VPN service
    
    // Proxy Service Configuration (Free)
    proxyApiBaseUrl: 'https://proxylist.geonode.com/api/proxy-list',
    proxyApiRefreshInterval: 3600000, // 1 hour in milliseconds
    proxyProtocol: 'socks5', // 'http', 'socks4', 'socks5' - default protocol for new proxies
    
    // Default Proxy Filters
    proxyFilters: {
      uptime: 100,           // 0-100 percentage
      limit: 500,            // Number of proxies to fetch
      page: 1,
      sort_by: 'lastChecked',
      sort_type: 'desc',
      port: null,            // e.g., '8080', '3128'
      country: null,         // e.g., 'US', 'GB'
      anonymityLevel: null,  // 'elite', 'anonymous', 'transparent'
      protocol: null,        // 'http', 'socks4', 'socks5'
      speed: null,           // 'fast', 'medium', 'slow'
      google: false          // whether Google-compatible
    },
    
    // VPN Service Configuration (OpenVPN)
    openVpnApiUrl: 'https://your-openvpn-server.com:943/api', // Change to your OpenVPN server
    apiUsername: 'admin', // Change to your username
    apiPassword: 'admin', // Change to your password
    vpnActivationKey: 'ewogICJub25jZSIgOiAiMEE4OERGRTI2Nzg2NTZEQjcxNUM3RjExNTU5MjRFNTlDN0I4RTlEMjRBMDYwOTUxMDk5ODRDMDQ2RkUwQzQ5MiIsCiAgInN1YmtleSIgOiAiQVNVdExQU3BZR2VESWJwbHl0Rmh0YVZfQVN0QVpnZ0FHa09TVXNoSEpTYUdJb0NVaUNFQkJCRGRfYmNkYTJjNDIwODlmNWE2M2ZlODc0OTZmZjkyYWQ1ZjViYWM0YmYyOCIsCiAgImlhdCIgOiAxNzY0MjQ3NDAyCn0=',
    vpnSubscriptionId: 'AStAZggAGkOSUshHJSaGIoCUiCEBBBDd',
    
    // Local storage keys
    storageKey: 'trixVpnState',
    proxyListStorageKey: 'trixVpnProxyList',
    authTokenStorageKey: 'trixVpnAuthToken',
    
    // VPN settings
    defaultVpnProfile: 'default',
    autoConnectOnLoad: false
  };

  // ==================== Connection Manager (Proxy or VPN) ====================
  class ConnectionManager {
    constructor() {
      this.authToken = this.getAuthToken();
      this.isConnected = false;
      this.currentServer = 'direct';
      this.currentProtocol = GM_getValue('trixVpnProxyProtocol', CONFIG.proxyProtocol);
      this.mode = CONFIG.mode;
    }

    /**
     * Switch between Proxy and VPN modes
     */
    setMode(mode) {
      if (['proxy', 'vpn'].includes(mode)) {
        this.mode = mode;
        GM_setValue('trixVpnMode', mode);
        console.log(`[TrixVPN] Switched to ${mode} mode`);
        return true;
      }
      return false;
    }

    /**
     * Connect via selected mode (Proxy or VPN)
     */
    async connect(serverAddress) {
      if (this.mode === 'proxy') {
        return this.connectProxy(serverAddress);
      } else if (this.mode === 'vpn') {
        return this.connectVpn(serverAddress);
      }
      return false;
    }

    /**
     * Disconnect from selected mode
     */
    async disconnect() {
      if (this.mode === 'proxy') {
        return this.disconnectProxy();
      } else if (this.mode === 'vpn') {
        return this.disconnectVpn();
      }
      return false;
    }

    /**
     * Proxy Connection Methods
     */
    connectProxy(proxyAddress, protocol = CONFIG.proxyProtocol) {
      if (proxyAddress === 'direct') {
        console.log('[TrixVPN] Using direct connection (no proxy)');
        this.isConnected = true;
        this.currentServer = 'direct';
        this.currentProtocol = null;
        this.saveConnectionState();
        return true;
      }

      // Store proxy preference with protocol
      GM_setValue('trixVpnCurrentProxy', proxyAddress);
      GM_setValue('trixVpnProxyProtocol', protocol);
      
      this.currentProtocol = protocol;
      const proxyUrl = this.formatProxyUrl(proxyAddress, protocol);
      console.log(`[TrixVPN] Proxy preference set to: ${proxyUrl}`);
      console.log(`[TrixVPN] Protocol: ${protocol.toUpperCase()}`);
      console.log('[TrixVPN] Note: Actual proxy routing requires browser extension or system configuration');
      
      this.isConnected = true;
      this.currentServer = proxyAddress;
      this.saveConnectionState();
      return true;
    }

    formatProxyUrl(address, protocol) {
      return `${protocol}://${address}`;
    }

    disconnectProxy() {
      console.log('[TrixVPN] Reverting to direct connection');
      this.isConnected = false;
      this.currentServer = 'direct';
      this.currentProtocol = null;
      this.saveConnectionState();
      return true;
    }

    /**
     * VPN Connection Methods
     */
    async authenticateVpn() {
      try {
        const response = await this.makeRequest(
          `${CONFIG.openVpnApiUrl}/auth/login`,
          'POST',
          {
            username: CONFIG.apiUsername,
            password: CONFIG.apiPassword
          }
        );

        if (response && response.auth_token) {
          this.authToken = response.auth_token;
          this.saveAuthToken(response.auth_token);
          console.log('[TrixVPN] VPN: Successfully authenticated');
          return true;
        }
      } catch (e) {
        console.error('[TrixVPN] VPN: Authentication failed:', e);
      }
      return false;
    }

    async connectVpn(profileName = CONFIG.defaultVpnProfile) {
      if (!this.authToken) {
        const authenticated = await this.authenticateVpn();
        if (!authenticated) return false;
      }

      try {
        const response = await this.makeRequest(
          `${CONFIG.openVpnApiUrl}/auth/login`,
          'POST',
          {
            username: CONFIG.apiUsername,
            password: CONFIG.apiPassword
          }
        );

        if (response && response.auth_token) {
          this.isConnected = true;
          this.currentServer = profileName;
          this.saveConnectionState();
          console.log('[TrixVPN] VPN: Connection initiated');
          return true;
        }
      } catch (e) {
        console.error('[TrixVPN] VPN: Failed to connect:', e);
      }
      return false;
    }

    async disconnectVpn() {
      if (!this.authToken) {
        console.warn('[TrixVPN] VPN: No auth token, cannot disconnect');
        return false;
      }

      try {
        this.isConnected = false;
        this.currentServer = 'direct';
        this.saveConnectionState();
        console.log('[TrixVPN] VPN: Disconnected');
        return true;
      } catch (e) {
        console.error('[TrixVPN] VPN: Failed to disconnect:', e);
      }
      return false;
    }

    async getStatus() {
      if (this.mode === 'vpn' && this.authToken) {
        try {
          const response = await this.makeRequest(
            `${CONFIG.openVpnApiUrl}/vpn/status`,
            'GET',
            null,
            this.authToken
          );
          return {
            status: this.isConnected ? 'connected' : 'disconnected',
            ip: response?.ip || null,
            server: this.currentServer
          };
        } catch (e) {
          console.error('[TrixVPN] VPN: Failed to get status:', e);
        }
      }

      return {
        status: this.isConnected ? 'connected' : 'disconnected',
        ip: null,
        server: this.currentServer
      };
    }

    /**
     * Generic request method
     */
    makeRequest(url, method = 'GET', data = null, token = null) {
      return new Promise((resolve, reject) => {
        try {
          const headers = {};
          if (token) {
            headers['Authorization'] = `Bearer ${token}`;
          }
          if (method !== 'GET') {
            headers['Content-Type'] = 'application/json';
          }

          const options = {
            method: method,
            url: url,
            headers: headers,
            timeout: 15000,
            onload: (response) => {
              try {
                let responseData;
                try {
                  responseData = JSON.parse(response.responseText);
                } catch {
                  responseData = response.responseText;
                }
                resolve(responseData);
              } catch (e) {
                reject(e);
              }
            },
            onerror: (error) => {
              reject(new Error(`Request failed: ${error}`));
            },
            ontimeout: () => {
              reject(new Error('Request timeout'));
            }
          };

          if (data && method !== 'GET') {
            options.data = JSON.stringify(data);
          }

          GM_xmlhttpRequest(options);
        } catch (e) {
          reject(e);
        }
      });
    }

    saveAuthToken(token) {
      GM_setValue(CONFIG.authTokenStorageKey, token);
    }

    getAuthToken() {
      return GM_getValue(CONFIG.authTokenStorageKey, null);
    }

    saveConnectionState() {
      GM_setValue(CONFIG.storageKey, JSON.stringify({
        isConnected: this.isConnected,
        currentServer: this.currentServer,
        currentProtocol: this.currentProtocol,
        mode: this.mode,
        timestamp: Date.now()
      }));
    }

    loadConnectionState() {
      try {
        const saved = GM_getValue(CONFIG.storageKey, null);
        if (saved) {
          const state = JSON.parse(saved);
          this.isConnected = state.isConnected || false;
          this.currentServer = state.currentServer || 'direct';
          this.currentProtocol = state.currentProtocol || CONFIG.proxyProtocol;
          this.mode = state.mode || CONFIG.mode;
        }
      } catch (e) {
        console.error('[TrixVPN] Error loading state:', e);
      }
    }
  }

  // ==================== Proxy Fetcher ====================
  class ProxyFetcher {
    constructor() {
      this.proxies = [];
      this.proxysByProtocol = { 'http': [], 'socks4': [], 'socks5': [] };
      this.lastFetchTime = null;
      this.isFetching = false;
      this.currentFilters = this.loadFilters();
    }

    loadFilters() {
      const saved = GM_getValue('trixVpnProxyFilters', null);
      if (saved) {
        return JSON.parse(saved);
      }
      return { ...CONFIG.proxyFilters };
    }

    saveFilters(filters) {
      GM_setValue('trixVpnProxyFilters', JSON.stringify(filters));
      this.currentFilters = filters;
    }

    buildApiUrl(filters = this.currentFilters) {
      let url = CONFIG.proxyApiBaseUrl + '?';
      const params = new URLSearchParams();

      // Always include these
      params.append('filterUpTime', filters.uptime || 100);
      params.append('limit', filters.limit || 500);
      params.append('page', filters.page || 1);
      params.append('sort_by', filters.sort_by || 'lastChecked');
      params.append('sort_type', filters.sort_type || 'desc');

      // Optional filters
      if (filters.port) params.append('filterPort', filters.port);
      if (filters.country) params.append('country', filters.country);
      if (filters.anonymityLevel) params.append('anonymityLevel', filters.anonymityLevel);
      if (filters.protocol) params.append('protocols', filters.protocol);
      if (filters.speed) params.append('speed', filters.speed);
      if (filters.google !== undefined) params.append('google', filters.google);

      return url + params.toString();
    }

    async fetchProxies(filters = null) {
      if (this.isFetching) return;
      
      if (filters) {
        this.saveFilters(filters);
      }

      // Check if we have cached proxies and they're still fresh
      const cached = this.getCachedProxies();
      if (cached && cached.length > 0) {
        this.proxies = cached;
        this.categorizeProxiesByProtocol();
        return cached;
      }

      this.isFetching = true;
      try {
        const apiUrl = this.buildApiUrl();
        console.log('[TrixVPN] Fetching proxies with filters:', this.currentFilters);
        const response = await this.makeRequest(apiUrl);
        if (response && response.data && Array.isArray(response.data)) {
          this.proxies = response.data.map(proxy => ({
            name: `${proxy.country} - ${proxy.ip}:${proxy.port}`,
            address: `${proxy.ip}:${proxy.port}`,
            protocols: this.detectProxyProtocols(proxy),
            country: proxy.country,
            port: proxy.port,
            anonymity: proxy.anonymity,
            uptime: proxy.uptime,
            speed: proxy.speed
          })).slice(0, 500); // Limit to 500 proxies

          // Categorize by protocol
          this.categorizeProxiesByProtocol();

          // Cache the proxies
          this.cacheProxies(this.proxies);
          console.log(`[TrixVPN] Fetched ${this.proxies.length} proxies from Geonode API`);
          console.log(`  SOCKS5: ${this.proxysByProtocol.socks5.length}, SOCKS4: ${this.proxysByProtocol.socks4.length}, HTTP: ${this.proxysByProtocol.http.length}`);
          return this.proxies;
        }
      } catch (e) {
        console.error('[TrixVPN] Error fetching proxies:', e);
      } finally {
        this.isFetching = false;
      }

      return this.proxies;
    }

    detectProxyProtocols(proxy) {
      // Geonode API returns protocol info in some versions
      const protocols = [];
      if (proxy.protocols && Array.isArray(proxy.protocols)) {
        return proxy.protocols.map(p => p.toLowerCase()).filter(p => ['http', 'socks4', 'socks5'].includes(p));
      }
      // If no protocol info, assume common defaults based on proxy characteristics
      if (proxy.anonymity === 'elite' || proxy.anonymity === 'anonymous') {
        protocols.push('socks5', 'socks4');
      }
      if (proxy.protocol && proxy.protocol.toLowerCase() === 'socks5') {
        return ['socks5'];
      }
      if (proxy.protocol && proxy.protocol.toLowerCase() === 'socks4') {
        return ['socks4'];
      }
      // Default to HTTP if no specific protocol info
      return ['http'];
    }

    categorizeProxiesByProtocol() {
      this.proxysByProtocol = { 'http': [], 'socks4': [], 'socks5': [] };
      this.proxies.forEach(proxy => {
        const protocols = proxy.protocols || ['http'];
        protocols.forEach(protocol => {
          const key = protocol.toLowerCase();
          if (this.proxysByProtocol[key]) {
            this.proxysByProtocol[key].push(proxy);
          }
        });
      });
    }

    makeRequest(url) {
      return new Promise((resolve, reject) => {
        try {
          GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            timeout: 10000,
            onload: (response) => {
              try {
                const data = JSON.parse(response.responseText);
                resolve(data);
              } catch (e) {
                reject(e);
              }
            },
            onerror: (error) => {
              reject(error);
            },
            ontimeout: () => {
              reject(new Error('Request timeout'));
            }
          });
        } catch (e) {
          reject(e);
        }
      });
    }

    cacheProxies(proxies) {
      GM_setValue(CONFIG.proxyListStorageKey, JSON.stringify({
        proxies: proxies,
        timestamp: Date.now()
      }));
    }

    getCachedProxies() {
      try {
        const cached = GM_getValue(CONFIG.proxyListStorageKey, null);
        if (cached) {
          const data = JSON.parse(cached);
          const now = Date.now();
          // Use cache if less than 1 hour old
          if (now - data.timestamp < CONFIG.proxyApiRefreshInterval) {
            return data.proxies;
          }
        }
      } catch (e) {
        console.error('[TrixVPN] Error retrieving cached proxies:', e);
      }
      return null;
    }

    getProxies() {
      return this.proxies;
    }

    getProxiesByProtocol(protocol) {
      return this.proxysByProtocol[protocol] || [];
    }

    getAvailableProtocols() {
      return Object.keys(this.proxysByProtocol).filter(protocol => this.proxysByProtocol[protocol].length > 0);
    }

    setFilter(filterName, value) {
      this.currentFilters[filterName] = value;
      this.saveFilters(this.currentFilters);
    }

    getFilter(filterName) {
      return this.currentFilters[filterName];
    }

    getAllFilters() {
      return { ...this.currentFilters };
    }

    resetFilters() {
      this.currentFilters = { ...CONFIG.proxyFilters };
      this.saveFilters(this.currentFilters);
    }
  }

  // ==================== VPN State Management ====================
  class VPNStateManager {
    constructor(connectionManager) {
      this.connectionManager = connectionManager;
      this.connectionManager.loadConnectionState();
    }

    loadState() {
      return this.connectionManager.loadConnectionState();
    }

    setMode(mode) {
      return this.connectionManager.setMode(mode);
    }

    async toggleVPN() {
      const status = this.getStatus();
      if (status.connected) {
        return await this.connectionManager.disconnect();
      } else {
        return await this.connectionManager.connect(status.server);
      }
    }

    setServer(serverName) {
      this.connectionManager.currentServer = serverName;
      this.connectionManager.saveConnectionState();
    }

    getStatus() {
      return {
        connected: this.connectionManager.isConnected,
        server: this.connectionManager.currentServer,
        mode: this.connectionManager.mode
      };
    }
  }

  // ==================== UI Manager ====================
  class VPNUIManager {
    constructor(stateManager, proxyFetcher, connectionManager) {
      this.state = stateManager;
      this.proxyFetcher = proxyFetcher;
      this.connectionManager = connectionManager;
      this.container = null;
      this.statusIndicator = null;
      this.toggleButton = null;
      this.serverSelect = null;
      this.modeToggle = null;
    }

    injectStyles() {
      const styles = `
        #vpn-control-widget {
          position: fixed;
          top: 20px;
          right: 20px;
          z-index: 10000;
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
          border-radius: 12px;
          box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
          padding: 16px;
          min-width: 340px;
          max-width: 400px;
          color: white;
          backdrop-filter: blur(10px);
        }

        #vpn-control-widget * {
          box-sizing: border-box;
        }

        .vpn-widget-header {
          display: flex;
          align-items: center;
          justify-content: space-between;
          margin-bottom: 12px;
          border-bottom: 1px solid rgba(255, 255, 255, 0.2);
          padding-bottom: 12px;
        }

        .vpn-widget-title {
          font-size: 14px;
          font-weight: 600;
          letter-spacing: 0.5px;
          text-transform: uppercase;
        }

        .vpn-mode-indicator {
          font-size: 10px;
          background: rgba(255, 255, 255, 0.2);
          padding: 3px 8px;
          border-radius: 4px;
          margin-left: 8px;
        }

        .vpn-status-indicator {
          display: inline-block;
          width: 12px;
          height: 12px;
          border-radius: 50%;
          background-color: #ff4757;
          animation: pulse 2s infinite;
          margin-right: 8px;
        }

        .vpn-status-indicator.connected {
          background-color: #2ed573;
          animation: pulse-green 2s infinite;
        }

        @keyframes pulse {
          0%, 100% {
            opacity: 1;
            transform: scale(1);
          }
          50% {
            opacity: 0.6;
            transform: scale(1.1);
          }
        }

        @keyframes pulse-green {
          0%, 100% {
            opacity: 1;
            transform: scale(1);
          }
          50% {
            opacity: 0.7;
            transform: scale(1.1);
          }
        }

        .vpn-status-text {
          font-size: 11px;
          opacity: 0.9;
          margin-top: 4px;
        }

        .vpn-status-text.connected {
          color: #2ed573;
        }

        .vpn-status-text.disconnected {
          color: #ff4757;
        }

        .vpn-mode-tabs {
          display: flex;
          gap: 8px;
          margin: 12px 0;
          border-bottom: 1px solid rgba(255, 255, 255, 0.2);
          padding-bottom: 12px;
        }

        .vpn-mode-tab {
          flex: 1;
          padding: 8px 12px;
          background: rgba(255, 255, 255, 0.1);
          border: 1px solid rgba(255, 255, 255, 0.2);
          border-radius: 6px;
          color: white;
          font-size: 11px;
          font-weight: 600;
          cursor: pointer;
          transition: all 0.3s ease;
          text-align: center;
        }

        .vpn-mode-tab:hover {
          background: rgba(255, 255, 255, 0.2);
        }

        .vpn-mode-tab.active {
          background: rgba(255, 255, 255, 0.3);
          border-color: rgba(255, 255, 255, 0.5);
        }

        .vpn-toggle-button {
          width: 100%;
          padding: 10px 16px;
          margin: 12px 0;
          border: none;
          border-radius: 8px;
          font-size: 13px;
          font-weight: 600;
          cursor: pointer;
          transition: all 0.3s ease;
          text-transform: uppercase;
          letter-spacing: 0.5px;
          background-color: rgba(255, 255, 255, 0.2);
          color: white;
        }

        .vpn-toggle-button:hover {
          background-color: rgba(255, 255, 255, 0.3);
          transform: translateY(-2px);
        }

        .vpn-toggle-button:active {
          transform: translateY(0);
        }

        .vpn-toggle-button.connected {
          background-color: #2ed573;
          color: #1a1a1a;
        }

        .vpn-toggle-button.connected:hover {
          background-color: #26d063;
        }

        .vpn-server-select {
          width: 100%;
          padding: 8px 12px;
          margin: 8px 0;
          border: 1px solid rgba(255, 255, 255, 0.3);
          border-radius: 6px;
          background-color: rgba(255, 255, 255, 0.1);
          color: white;
          font-size: 12px;
          font-family: inherit;
          cursor: pointer;
          transition: all 0.3s ease;
        }

        .vpn-server-select:hover {
          background-color: rgba(255, 255, 255, 0.15);
          border-color: rgba(255, 255, 255, 0.5);
        }

        .vpn-server-select option {
          background-color: #333;
          color: white;
        }

        .vpn-protocol-filter {
          display: flex;
          gap: 4px;
          margin: 8px 0;
          flex-wrap: wrap;
        }

        .vpn-protocol-btn {
          padding: 4px 10px;
          font-size: 10px;
          font-weight: 600;
          border: 1px solid rgba(255, 255, 255, 0.3);
          border-radius: 4px;
          background: rgba(255, 255, 255, 0.1);
          color: white;
          cursor: pointer;
          transition: all 0.2s ease;
          text-transform: uppercase;
        }

        .vpn-protocol-btn:hover {
          background: rgba(255, 255, 255, 0.2);
        }

        .vpn-protocol-btn.active {
          background: rgba(76, 175, 80, 0.6);
          border-color: rgba(76, 175, 80, 1);
        }

        .vpn-protocol-label {
          font-size: 10px;
          opacity: 0.8;
          margin-top: 6px;
          margin-bottom: 2px;
        }

        .vpn-filter-section {
          margin-top: 12px;
          padding-top: 12px;
          border-top: 1px solid rgba(255, 255, 255, 0.2);
        }

        .vpn-filter-title {
          font-size: 10px;
          font-weight: 700;
          text-transform: uppercase;
          opacity: 0.9;
          margin-bottom: 8px;
          cursor: pointer;
          user-select: none;
        }

        .vpn-filter-title:hover {
          opacity: 1;
        }

        .vpn-filter-content {
          display: grid;
          grid-template-columns: 1fr 1fr;
          gap: 8px;
          max-height: 300px;
          overflow-y: auto;
          padding-right: 4px;
        }

        .vpn-filter-content::-webkit-scrollbar {
          width: 4px;
        }

        .vpn-filter-content::-webkit-scrollbar-track {
          background: rgba(0, 0, 0, 0.1);
          border-radius: 2px;
        }

        .vpn-filter-content::-webkit-scrollbar-thumb {
          background: rgba(255, 255, 255, 0.2);
          border-radius: 2px;
        }

        .vpn-filter-content.hidden {
          display: none;
        }

        .vpn-filter-input {
          padding: 6px 8px;
          font-size: 10px;
          border: 1px solid rgba(255, 255, 255, 0.2);
          border-radius: 4px;
          background: rgba(255, 255, 255, 0.08);
          color: white;
          font-family: inherit;
        }

        .vpn-filter-input::placeholder {
          opacity: 0.5;
        }

        .vpn-filter-input:focus {
          outline: none;
          background: rgba(255, 255, 255, 0.12);
          border-color: rgba(255, 255, 255, 0.4);
        }

        .vpn-filter-checkbox {
          display: flex;
          align-items: center;
          gap: 6px;
          font-size: 10px;
          cursor: pointer;
          padding: 4px;
          border-radius: 4px;
          transition: all 0.2s ease;
        }

        .vpn-filter-checkbox:hover {
          background: rgba(255, 255, 255, 0.1);
        }

        .vpn-filter-checkbox input[type="checkbox"] {
          cursor: pointer;
          width: 14px;
          height: 14px;
        }

        .vpn-filter-button {
          padding: 6px 10px;
          font-size: 10px;
          font-weight: 600;
          border: 1px solid rgba(255, 255, 255, 0.3);
          border-radius: 4px;
          background: rgba(255, 255, 255, 0.1);
          color: white;
          cursor: pointer;
          transition: all 0.2s ease;
          text-transform: uppercase;
        }

        .vpn-filter-button:hover {
          background: rgba(255, 255, 255, 0.15);
        }

        .vpn-filter-button.apply {
          background: rgba(76, 175, 80, 0.5);
          border-color: rgba(76, 175, 80, 0.8);
        }

        .vpn-filter-button.apply:hover {
          background: rgba(76, 175, 80, 0.7);
        }

        .vpn-filter-actions {
          display: flex;
          gap: 6px;
          margin-top: 8px;
        }

        .vpn-filter-actions button {
          flex: 1;
          padding: 6px 8px;
        }

        .vpn-server-info {
          font-size: 11px;
          opacity: 0.85;
          padding: 8px;
          background-color: rgba(0, 0, 0, 0.2);
          border-radius: 6px;
          margin-top: 8px;
          word-break: break-all;
        }

        .vpn-server-label {
          font-weight: 600;
          margin-bottom: 4px;
        }

        .vpn-widget-footer {
          margin-top: 12px;
          padding-top: 12px;
          border-top: 1px solid rgba(255, 255, 255, 0.2);
          font-size: 10px;
          opacity: 0.7;
          text-align: center;
        }

        .vpn-minimize-btn {
          background: none;
          border: none;
          color: white;
          font-size: 16px;
          cursor: pointer;
          padding: 0;
          width: 24px;
          height: 24px;
          display: flex;
          align-items: center;
          justify-content: center;
        }

        .vpn-minimize-btn:hover {
          opacity: 0.8;
        }

        #vpn-control-widget.minimized {
          min-width: auto;
          padding: 8px;
        }

        #vpn-control-widget.minimized .vpn-widget-content {
          display: none;
        }
      `;

      GM_addStyle(styles);
    }

    createWidget() {
      // Create main container
      this.container = document.createElement('div');
      this.container.id = 'vpn-control-widget';

      const status = this.state.getStatus();

      // Header with title and minimize button
      const header = document.createElement('div');
      header.className = 'vpn-widget-header';

      const titleContainer = document.createElement('div');
      titleContainer.style.display = 'flex';
      titleContainer.style.alignItems = 'center';

      const title = document.createElement('div');
      title.className = 'vpn-widget-title';
      title.textContent = '🛡️ TrixVPN';

      const modeIndicator = document.createElement('div');
      modeIndicator.className = 'vpn-mode-indicator';
      modeIndicator.textContent = status.mode.toUpperCase();

      titleContainer.appendChild(title);
      titleContainer.appendChild(modeIndicator);

      const minimizeBtn = document.createElement('button');
      minimizeBtn.className = 'vpn-minimize-btn';
      minimizeBtn.textContent = '−';
      minimizeBtn.title = 'Minimize widget';
      minimizeBtn.addEventListener('click', () => {
        this.container.classList.toggle('minimized');
      });

      header.appendChild(titleContainer);
      header.appendChild(minimizeBtn);

      // Content wrapper
      const content = document.createElement('div');
      content.className = 'vpn-widget-content';

      // Mode tabs
      const modeTabs = document.createElement('div');
      modeTabs.className = 'vpn-mode-tabs';

      const proxyTab = document.createElement('button');
      proxyTab.className = `vpn-mode-tab ${status.mode === 'proxy' ? 'active' : ''}`;
      proxyTab.textContent = '🌐 Proxy';
      proxyTab.addEventListener('click', () => {
        this.state.setMode('proxy');
        this.updateUI();
        modeIndicator.textContent = 'PROXY';
      });

      const vpnTab = document.createElement('button');
      vpnTab.className = `vpn-mode-tab ${status.mode === 'vpn' ? 'active' : ''}`;
      vpnTab.textContent = '🔐 VPN';
      vpnTab.addEventListener('click', () => {
        this.state.setMode('vpn');
        this.updateUI();
        modeIndicator.textContent = 'VPN';
      });

      modeTabs.appendChild(proxyTab);
      modeTabs.appendChild(vpnTab);

      // Status indicator and text
      const statusDiv = document.createElement('div');
      this.statusIndicator = document.createElement('span');
      this.statusIndicator.className = 'vpn-status-indicator' + (status.connected ? ' connected' : '');

      const statusTextSpan = document.createElement('span');
      statusTextSpan.className = 'vpn-status-text' + (status.connected ? ' connected' : ' disconnected');
      statusTextSpan.textContent = status.connected ? '🔒 Connected' : '🔓 Disconnected';

      statusDiv.appendChild(this.statusIndicator);
      statusDiv.appendChild(statusTextSpan);

      // Toggle button
      this.toggleButton = document.createElement('button');
      this.toggleButton.className = 'vpn-toggle-button' + (status.connected ? ' connected' : '');
      this.toggleButton.textContent = status.connected ? '⏸ Disconnect' : '▶ Connect';
      this.toggleButton.addEventListener('click', () => this.handleToggle());

      // Server selection
      const serverLabel = document.createElement('div');
      serverLabel.style.fontSize = '11px';
      serverLabel.style.fontWeight = '600';
      serverLabel.style.marginTop = '8px';
      serverLabel.style.marginBottom = '4px';
      serverLabel.textContent = 'Select Server:';

      // Protocol filter
      const protocolLabel = document.createElement('div');
      protocolLabel.className = 'vpn-protocol-label';
      protocolLabel.textContent = 'Protocol Filter:';

      const protocolFilter = document.createElement('div');
      protocolFilter.className = 'vpn-protocol-filter';
      
      const protocols = ['SOCKS5', 'SOCKS4', 'HTTP'];
      const protocolBtns = {};
      let selectedProtocol = GM_getValue('trixVpnSelectedProtocol', 'socks5');

      protocols.forEach(proto => {
        const btn = document.createElement('button');
        btn.className = `vpn-protocol-btn ${proto.toLowerCase() === selectedProtocol ? 'active' : ''}`;
        btn.textContent = proto;
        btn.addEventListener('click', () => {
          Object.values(protocolBtns).forEach(b => b.classList.remove('active'));
          btn.classList.add('active');
          selectedProtocol = proto.toLowerCase();
          GM_setValue('trixVpnSelectedProtocol', selectedProtocol);
          this.refreshServerOptions(selectedProtocol);
        });
        protocolFilter.appendChild(btn);
        protocolBtns[proto.toLowerCase()] = btn;
      });

      this.serverSelect = document.createElement('select');
      this.serverSelect.className = 'vpn-server-select';
      this.serverSelect.addEventListener('change', (e) => this.handleServerChange(e));

      // Add Direct option
      const directOption = document.createElement('option');
      directOption.value = 'direct';
      directOption.textContent = 'Direct (No Proxy/VPN)';
      if ('direct' === status.server) {
        directOption.selected = true;
      }
      this.serverSelect.appendChild(directOption);

      // Add proxies filtered by protocol
      const proxiesByProtocol = this.proxyFetcher.getProxiesByProtocol(selectedProtocol);
      if (proxiesByProtocol && proxiesByProtocol.length > 0) {
        proxiesByProtocol.forEach((server) => {
          const option = document.createElement('option');
          option.value = server.address;
          option.textContent = server.name;
          if (server.address === status.server) {
            option.selected = true;
          }
          this.serverSelect.appendChild(option);
        });
      } else {
        const loadingOption = document.createElement('option');
        loadingOption.value = '';
        loadingOption.textContent = '⏳ Loading proxies...';
        this.serverSelect.appendChild(loadingOption);
      }

      // Server info display
      const serverInfo = document.createElement('div');
      serverInfo.className = 'vpn-server-info';
      serverInfo.innerHTML = `<div class="vpn-server-label">Current:</div>${this.escapeHtml(status.server)}`;

      // Filter Section
      const filterSection = this.createFilterSection();

      // Footer
      const footer = document.createElement('div');
      footer.className = 'vpn-widget-footer';
      footer.textContent = 'TrixVPN v0.03 by Painsel';

      // Assemble widget
      content.appendChild(modeTabs);
      content.appendChild(statusDiv);
      content.appendChild(this.toggleButton);
      content.appendChild(serverLabel);
      content.appendChild(protocolLabel);
      content.appendChild(protocolFilter);
      content.appendChild(this.serverSelect);
      content.appendChild(serverInfo);
      content.appendChild(filterSection);

      this.container.appendChild(header);
      this.container.appendChild(content);
      this.container.appendChild(footer);

      return this.container;
    }

    handleToggle() {
      this.toggleButton.disabled = true;
      this.toggleButton.textContent = '⏳ Processing...';

      this.state.toggleVPN().then(() => {
        this.updateUI();
        const status = this.state.getStatus();
        this.showNotification(
          status.connected ? '✓ Connected' : '✗ Disconnected',
          status.connected ? `Connected via ${status.mode}` : 'Connection closed'
        );
      }).catch(e => {
        console.error('[TrixVPN] Toggle failed:', e);
        this.showNotification('❌ Error', 'Failed to toggle connection');
      }).finally(() => {
        this.toggleButton.disabled = false;
        this.updateUI();
      });
    }

    handleServerChange(event) {
      const selectedServer = event.target.value;
      const selectedProtocol = GM_getValue('trixVpnSelectedProtocol', 'socks5');
      this.state.setServer(selectedServer);
      this.connectionManager.currentProtocol = selectedProtocol;
      this.updateUI();
      this.showNotification(
        '🔄 Server Changed',
        `${event.target.options[event.target.selectedIndex].text} (${selectedProtocol.toUpperCase()})`
      );
    }

    updateUI() {
      const status = this.state.getStatus();

      if (this.statusIndicator) {
        if (status.connected) {
          this.statusIndicator.classList.add('connected');
        } else {
          this.statusIndicator.classList.remove('connected');
        }
      }

      if (this.toggleButton) {
        if (status.connected) {
          this.toggleButton.classList.add('connected');
          this.toggleButton.textContent = '⏸ Disconnect';
        } else {
          this.toggleButton.classList.remove('connected');
          this.toggleButton.textContent = '▶ Connect';
        }
      }

      if (this.serverSelect) {
        this.serverSelect.value = status.server;
      }

      const serverInfo = this.container?.querySelector('.vpn-server-info');
      if (serverInfo) {
        serverInfo.innerHTML = `<div class="vpn-server-label">Current:</div>${this.escapeHtml(status.server)}`;
      }
    }

    showNotification(title, message) {
      try {
        GM_notification({
          title: title,
          text: message,
          highlight: true,
          timeout: 5000
        });
      } catch (e) {
        console.log('[TrixVPN]', title, '-', message);
      }
    }

    escapeHtml(text) {
      const map = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#039;'
      };
      return text.replace(/[&<>"']/g, m => map[m]);
    }

    inject() {
      this.injectStyles();
      const widget = this.createWidget();
      document.documentElement.appendChild(widget);
      
      this.proxyFetcher.fetchProxies().then(() => {
        this.refreshServerOptions();
      }).catch(e => {
        console.error('[TrixVPN] Failed to fetch proxies:', e);
      });
    }

    refreshServerOptions(protocol = 'socks5') {
      if (!this.serverSelect) return;
      
      while (this.serverSelect.options.length > 1) {
        this.serverSelect.remove(this.serverSelect.options.length - 1);
      }

      const proxies = this.proxyFetcher.getProxiesByProtocol(protocol);
      if (proxies && proxies.length > 0) {
        proxies.forEach((server) => {
          const option = document.createElement('option');
          option.value = server.address;
          option.textContent = server.name;
          this.serverSelect.appendChild(option);
        });
        console.log(`[TrixVPN] Added ${proxies.length} ${protocol.toUpperCase()} proxies to server list`);
      }
    }

    createFilterSection() {
      const section = document.createElement('div');
      section.className = 'vpn-filter-section';

      const title = document.createElement('div');
      title.className = 'vpn-filter-title';
      title.textContent = '⚙️ Advanced Filters';
      title.style.cursor = 'pointer';

      const content = document.createElement('div');
      content.className = 'vpn-filter-content hidden';

      const filters = this.proxyFetcher.getAllFilters();

      // Uptime filter
      const uptimeDiv = document.createElement('div');
      const uptimeLabel = document.createElement('label');
      uptimeLabel.style.fontSize = '9px';
      uptimeLabel.textContent = 'Uptime (%)';
      const uptimeInput = document.createElement('input');
      uptimeInput.type = 'number';
      uptimeInput.className = 'vpn-filter-input';
      uptimeInput.min = '0';
      uptimeInput.max = '100';
      uptimeInput.value = filters.uptime || 100;
      uptimeInput.placeholder = '0-100';
      uptimeDiv.appendChild(uptimeLabel);
      uptimeDiv.appendChild(uptimeInput);

      // Port filter
      const portDiv = document.createElement('div');
      const portLabel = document.createElement('label');
      portLabel.style.fontSize = '9px';
      portLabel.textContent = 'Port';
      const portInput = document.createElement('input');
      portInput.type = 'text';
      portInput.className = 'vpn-filter-input';
      portInput.value = filters.port || '';
      portInput.placeholder = 'e.g., 8080';
      portDiv.appendChild(portLabel);
      portDiv.appendChild(portInput);

      // Country filter
      const countryDiv = document.createElement('div');
      const countryLabel = document.createElement('label');
      countryLabel.style.fontSize = '9px';
      countryLabel.textContent = 'Country';
      const countryInput = document.createElement('input');
      countryInput.type = 'text';
      countryInput.className = 'vpn-filter-input';
      countryInput.value = filters.country || '';
      countryInput.placeholder = 'e.g., US';
      countryDiv.appendChild(countryLabel);
      countryDiv.appendChild(countryInput);

      // Anonymity filter
      const anonDiv = document.createElement('div');
      const anonLabel = document.createElement('label');
      anonLabel.style.fontSize = '9px';
      anonLabel.textContent = 'Anonymity';
      const anonSelect = document.createElement('select');
      anonSelect.className = 'vpn-filter-input';
      const anonOptions = ['', 'elite', 'anonymous', 'transparent'];
      anonOptions.forEach(opt => {
        const optEl = document.createElement('option');
        optEl.value = opt;
        optEl.textContent = opt ? opt.charAt(0).toUpperCase() + opt.slice(1) : 'Any';
        if (opt === filters.anonymityLevel) optEl.selected = true;
        anonSelect.appendChild(optEl);
      });
      anonDiv.appendChild(anonLabel);
      anonDiv.appendChild(anonSelect);

      // Speed filter
      const speedDiv = document.createElement('div');
      const speedLabel = document.createElement('label');
      speedLabel.style.fontSize = '9px';
      speedLabel.textContent = 'Speed';
      const speedSelect = document.createElement('select');
      speedSelect.className = 'vpn-filter-input';
      const speedOptions = ['', 'fast', 'medium', 'slow'];
      speedOptions.forEach(opt => {
        const optEl = document.createElement('option');
        optEl.value = opt;
        optEl.textContent = opt ? opt.charAt(0).toUpperCase() + opt.slice(1) : 'Any';
        if (opt === filters.speed) optEl.selected = true;
        speedSelect.appendChild(optEl);
      });
      speedDiv.appendChild(speedLabel);
      speedDiv.appendChild(speedSelect);

      // Limit filter
      const limitDiv = document.createElement('div');
      const limitLabel = document.createElement('label');
      limitLabel.style.fontSize = '9px';
      limitLabel.textContent = 'Limit';
      const limitInput = document.createElement('input');
      limitInput.type = 'number';
      limitInput.className = 'vpn-filter-input';
      limitInput.min = '10';
      limitInput.max = '500';
      limitInput.value = filters.limit || 500;
      limitInput.placeholder = '10-500';
      limitDiv.appendChild(limitLabel);
      limitDiv.appendChild(limitInput);

      // Action buttons
      const actions = document.createElement('div');
      actions.className = 'vpn-filter-actions';

      const applyBtn = document.createElement('button');
      applyBtn.className = 'vpn-filter-button apply';
      applyBtn.textContent = 'Apply';
      applyBtn.addEventListener('click', async () => {
        const newFilters = {
          uptime: parseInt(uptimeInput.value) || 100,
          port: portInput.value || null,
          country: countryInput.value || null,
          anonymityLevel: anonSelect.value || null,
          speed: speedSelect.value || null,
          limit: parseInt(limitInput.value) || 500,
          page: filters.page,
          sort_by: filters.sort_by,
          sort_type: filters.sort_type,
          google: filters.google,
          protocol: filters.protocol
        };
        applyBtn.textContent = '⏳ Fetching...';
        applyBtn.disabled = true;
        await this.proxyFetcher.fetchProxies(newFilters);
        this.refreshServerOptions();
        applyBtn.textContent = 'Apply';
        applyBtn.disabled = false;
        this.showNotification('✓ Filters Applied', 'Proxy list updated');
      });

      const resetBtn = document.createElement('button');
      resetBtn.className = 'vpn-filter-button';
      resetBtn.textContent = 'Reset';
      resetBtn.addEventListener('click', () => {
        this.proxyFetcher.resetFilters();
        uptimeInput.value = 100;
        portInput.value = '';
        countryInput.value = '';
        anonSelect.value = '';
        speedSelect.value = '';
        limitInput.value = 500;
        this.showNotification('✓ Reset', 'Filters reset to defaults');
      });

      actions.appendChild(applyBtn);
      actions.appendChild(resetBtn);

      content.appendChild(uptimeDiv);
      content.appendChild(portDiv);
      content.appendChild(countryDiv);
      content.appendChild(anonDiv);
      content.appendChild(speedDiv);
      content.appendChild(limitDiv);
      content.appendChild(actions);

      // Toggle content visibility
      title.addEventListener('click', () => {
        content.classList.toggle('hidden');
        title.textContent = content.classList.contains('hidden') ? '⚙️ Advanced Filters' : '⬇️ Advanced Filters';
      });

      section.appendChild(title);
      section.appendChild(content);
      return section;
    }
  }

  // ==================== Main Initialization ====================
  function initialize() {
    try {
      const connectionManager = new ConnectionManager();
      const proxyFetcher = new ProxyFetcher();
      const stateManager = new VPNStateManager(connectionManager);
      const uiManager = new VPNUIManager(stateManager, proxyFetcher, connectionManager);

      // Wait for DOM to be ready
      if (document.documentElement) {
        uiManager.inject();
        console.log('[TrixVPN] Script initialized - Proxy and VPN modes available');
      } else {
        document.addEventListener('DOMContentLoaded', () => {
          uiManager.inject();
          console.log('[TrixVPN] Script initialized - Proxy and VPN modes available');
        });
      }
    } catch (e) {
      console.error('[TrixVPN] Initialization error:', e);
    }
  }

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