ControlD Random Services Location

adds a "Random Location" option to the ControlD service redirect destinations picker

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

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

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.

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

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         ControlD Random Services Location
// @namespace    https://spin.rip/
// @version      1.1.1
// @description  adds a "Random Location" option to the ControlD service redirect destinations picker
// @author       spin
// @match        https://controld.com/*
// @grant        none
// @run-at       document-idle
// @icon         https://www.google.com/s2/favicons?sz=64&domain=controld.com
// @license      GPL-3.0-only
// ==/UserScript==

(function () {
  'use strict';

  const RANDOM_VIA = '?';
  const RANDOM_LABEL = 'Random Location';
  const RANDOM_TEST_ID = 'services-country-RandomLocation';
  const RANDOM_DEFAULT_TEST_ID = 'default-redirect-country-RandomLocation';

  // controld's own svg for the random/redirect icon, scaled to 16x16 to match flag size
  const RANDOM_SVG = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" style="display:block;">
    <path fill-rule="evenodd" clip-rule="evenodd" d="M23.834 10C22.882 4.325 17.946 0 12 0 5.373 0 0 5.373 0 12c0 3.073 1.155 5.877 3.056 8 .445.497.931.958 1.453 1.375A11.96 11.96 0 0 0 9 23.622V11l.02-.04c.044-1.157.162-2.25.337-3.252C10.207 7.9 11.092 8 12 8c.908 0 1.793-.1 2.643-.292.126.72.222 1.488.283 2.292h2.005a25.585 25.585 0 0 0-.365-2.899 11.99 11.99 0 0 0 2.925-1.726A9.969 9.969 0 0 1 21.8 10h2.034zm-16.4 6.9a11.991 11.991 0 0 0-2.925 1.725A9.96 9.96 0 0 1 2.049 13h4.968c.048 1.379.192 2.692.417 3.9zM6 20a9.997 9.997 0 0 1 1.903-1.124c.294 1.01.652 1.905 1.059 2.654A9.97 9.97 0 0 1 5.999 20zM12 6c.756 0 1.492-.084 2.2-.242a13.568 13.568 0 0 0-.51-1.474c-.393-.941-.809-1.572-1.169-1.937a1.533 1.533 0 0 0-.395-.308A.286.286 0 0 0 12 2c-.01 0-.048 0-.126.039-.086.042-.221.13-.395.308-.36.365-.776.996-1.168 1.937-.186.445-.357.938-.51 1.474C10.507 5.916 11.243 6 12 6zm3.039-3.53c.407.749.765 1.645 1.06 2.655A9.993 9.993 0 0 0 18 4a9.969 9.969 0 0 0-2.962-1.53zm-6.078 0c-.407.75-.765 1.645-1.06 2.655A9.994 9.994 0 0 1 6 4 9.97 9.97 0 0 1 8.96 2.47zM7.017 11c.048-1.379.192-2.692.417-3.899A11.992 11.992 0 0 1 4.51 5.375 9.96 9.96 0 0 0 2.049 11h4.968z" fill="currentColor"/>
    <path fill-rule="evenodd" clip-rule="evenodd" d="M13 12a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2h-9zm7.942 1.058a.625.625 0 0 0-.884.884l.458.458c-1.338.14-2.56.85-3.342 1.964l-.384.548-.627-.892A3.865 3.865 0 0 0 13 14.375a.625.625 0 1 0 0 1.25c.852 0 1.65.415 2.14 1.113L16.026 18l-.886 1.262A2.615 2.615 0 0 1 13 20.375a.625.625 0 1 0 0 1.25c1.26 0 2.44-.614 3.163-1.645l.627-.892.384.548a4.675 4.675 0 0 0 3.342 1.964l-.458.458a.625.625 0 0 0 .884.884L22.884 21l-1.942-1.942a.625.625 0 0 0-.884.884l.388.388a3.425 3.425 0 0 1-2.249-1.412L17.553 18l.644-.918a3.425 3.425 0 0 1 2.249-1.412l-.388.388a.625.625 0 0 0 .884.884L22.884 15l-1.942-1.942z" fill="currentColor"/>
  </svg>`;

  // checkmark svg matching controld's style
  const CHECKMARK_SVG = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" style="color: rgb(29, 191, 115);">
    <path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"/>
  </svg>`;

  let observer = null;

  // figure out which type of modal we're looking at
  function detectModalContext(scrollList) {
    if (scrollList.querySelector('button[data-testid^="services-country-"]')) {
      return 'services';
    }
    if (scrollList.querySelector('button[data-testid^="default-redirect-country-"]')) {
      return 'default';
    }
    return null;
  }

  function getCountryBtnSelector(context) {
    if (context === 'default') return 'button[data-testid^="default-redirect-country-"]';
    return 'button[data-testid^="services-country-"]';
  }

  function getRandomTestId(context) {
    if (context === 'default') return RANDOM_DEFAULT_TEST_ID;
    return RANDOM_TEST_ID;
  }

  // watches for the destinations modal to appear and injects the random option
  function startObserver() {
    if (observer) return;
    observer = new MutationObserver(() => {
      const modal = document.querySelector('.modal-dialog');
      if (!modal) return;

      const scrollList = modal.querySelector('.show-scrollbar');
      if (!scrollList) return;

      const context = detectModalContext(scrollList);
      if (!context) return;

      const selector = getCountryBtnSelector(context);
      const randomTestId = getRandomTestId(context);

      if (scrollList.querySelector(`[data-testid="${randomTestId}"]`)) return;

      const allCountryBtns = scrollList.querySelectorAll(selector);
      if (!allCountryBtns.length) return;

      // prefer cloning a non-selected button so we don't inherit active/expanded
      // styles (green text, missing chevron, etc). the selected button often has a
      // different css class than the rest.
      const firstBtn = allCountryBtns[0];
      let referenceBtn = firstBtn;
      if (allCountryBtns.length > 1) {
        const firstClass = firstBtn.className;
        const secondClass = allCountryBtns[1].className;
        if (firstClass !== secondClass) {
          referenceBtn = allCountryBtns[1];
        }
      }

      injectRandomOption(scrollList, referenceBtn, firstBtn, context);
    });
    observer.observe(document.body, { childList: true, subtree: true });
  }

  function injectRandomOption(scrollList, referenceBtn, firstBtn, context) {
    const randomTestId = getRandomTestId(context);

    // clone a real button to inherit all css classes and structure
    const clone = referenceBtn.cloneNode(true);

    clone.setAttribute('data-testid', randomTestId);
    clone.setAttribute('aria-label', `select ${RANDOM_LABEL}`);

    // swap the flag <img> for the controld random svg icon
    const img = clone.querySelector('img');
    if (img) {
      const iconWrapper = document.createElement('span');
      iconWrapper.innerHTML = RANDOM_SVG;
      // match the native flag color
      iconWrapper.style.cssText = 'color: rgba(232, 239, 255, 0.6); display: flex; align-items: center;';
      img.replaceWith(iconWrapper);
    }

    // remove dropdown chevron (img on services page, svg on profile options page)
    clone.querySelectorAll('img').forEach(el => el.remove());
    const chevronSvg = clone.querySelector('[data-testid="proxy-country-close"]');
    if (chevronSvg) chevronSvg.remove();

    // replace the country name text
    const nameSpan = clone.querySelector('span[aria-label*="show tooltip"]');
    if (nameSpan) {
      nameSpan.textContent = RANDOM_LABEL;
      nameSpan.setAttribute('aria-label', `show tooltip: ${RANDOM_LABEL}`);
    }

    // update the proxy-country data-testid on the inner div
    const proxyDiv = clone.querySelector('[data-testid^="proxy-country-"]');
    if (proxyDiv) {
      proxyDiv.setAttribute('data-testid', 'proxy-country-RandomLocation');
    }

    // remove any existing checkmark
    const checkmark = clone.querySelector('.right-svg');
    if (checkmark) checkmark.remove();

    const container = firstBtn.parentElement;

    // deep clone to strip inherited react event handlers
    const freshClone = clone.cloneNode(true);

    freshClone.addEventListener('click', (e) => {
      e.preventDefault();
      e.stopPropagation();
      handleRandomSelect(freshClone, context);
    });

    container.insertBefore(freshClone, container.firstChild);

    // the native list uses border-top on every item except the first to create
    // separators. since we inserted above the first item, that item (now second)
    // needs a border-top to maintain the separator pattern.
    const firstBtnProxyDiv = firstBtn.querySelector('[data-testid^="proxy-country-"]');
    if (firstBtnProxyDiv) {
      firstBtnProxyDiv.style.borderTop = '1px solid var(--theme-ui-colors-white15, rgba(255, 255, 255, 0.15))';
    }

    // show checkmark if already set to random
    checkCurrentVia(freshClone, context);
  }

  function getApiUrl(context) {
    const profileId = getProfileId();
    if (!profileId) return null;

    if (context === 'default') {
      return `https://api.controld.com/profiles/${profileId}/default`;
    }
    const serviceName = getServiceName();
    if (!serviceName) return null;
    return `https://api.controld.com/profiles/${profileId}/services/${serviceName}`;
  }

  async function checkCurrentVia(btnElement, context) {
    const url = getApiUrl(context);
    if (!url) return;

    const token = getSessionToken();
    if (!token) return;

    try {
      const resp = await fetch(url, {
        headers: { 'Authorization': 'Bearer ' + token }
      });
      const data = await resp.json();

      let isRandom = false;

      if (context === 'default') {
        isRandom = data?.body?.default?.via === RANDOM_VIA;
      } else if (data?.body?.services) {
        const serviceName = getServiceName();
        isRandom = data.body.services.some(s => {
          const pk = s.PK || '';
          const name = (typeof s.name === 'string' ? s.name : '').toLowerCase();
          const via = s.via || s.action?.via || '';
          return (pk === serviceName || name === serviceName) && via === RANDOM_VIA;
        });
      }

      if (isRandom) {
        addCheckmark(btnElement);
      }
    } catch (err) {
      // silently fail
    }
  }

  function addCheckmark(btnElement) {
    // avoid duplicates
    if (btnElement.querySelector('.right-svg')) return;

    // find the inner row div (same level as the flag+name container)
    const proxyDiv = btnElement.querySelector('[data-testid^="proxy-country-"]');
    if (!proxyDiv) return;

    const check = document.createElement('div');
    check.className = 'right-svg';
    check.innerHTML = CHECKMARK_SVG;
    proxyDiv.appendChild(check);
  }

  async function handleRandomSelect(btnElement, context) {
    const url = getApiUrl(context);
    if (!url) {
      console.warn('[ControlD Random] could not determine api url from context');
      return;
    }

    const token = getSessionToken();
    if (!token) {
      console.warn('[ControlD Random] no session token found');
      return;
    }

    try {
      const resp = await fetch(url, {
        method: 'PUT',
        headers: {
          'Authorization': 'Bearer ' + token,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ status: 1, do: 3, via: RANDOM_VIA })
      });
      const data = await resp.json();

      if (data.success) {
        // remove checkmarks from all other items in the list
        const modal = document.querySelector('.modal-dialog');
        if (modal) {
          modal.querySelectorAll('.right-svg').forEach(el => el.remove());
        }

        // add checkmark to our button
        addCheckmark(btnElement);

        // close modal
        const closeBtn = document.querySelector(
          '.modal-dialog button[data-testid="dialog-close-button"], ' +
          '.modal-dialog button[data-testid="close-button"], ' +
          '.modal-dialog button[aria-label*="Close"], ' +
          '.modal-dialog button[aria-label*="close"]'
        );
        if (closeBtn) closeBtn.click();

        // reload to sync the ui state
        setTimeout(() => location.reload(), 300);
      } else {
        console.error('[ControlD Random] api error:', data.error?.message);
      }
    } catch (err) {
      console.error('[ControlD Random] request failed:', err);
    }
  }

  function getProfileId() {
    const urlMatch = window.location.pathname.match(/profiles\/([^/]+)/);
    return urlMatch?.[1] || null;
  }

  function getServiceName() {
    let serviceName = window.__controld_random_serviceName || null;
    if (!serviceName) {
      serviceName = findServiceFromReact();
    }
    return serviceName;
  }

  function findServiceFromReact() {
    const modal = document.querySelector('.modal-dialog');
    if (!modal) return null;

    const fiberKey = Object.keys(modal).find(k =>
      k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$')
    );
    if (!fiberKey) return null;

    let fiber = modal[fiberKey];
    let depth = 0;
    while (fiber && depth < 50) {
      const props = fiber.memoizedProps || fiber.pendingProps;
      if (props) {
        if (props.servicePK || props.service?.PK || props.serviceId) {
          return props.servicePK || props.service?.PK || props.serviceId;
        }
        if (typeof props.serviceName === 'string') {
          return props.serviceName;
        }
      }
      let state = fiber.memoizedState;
      let stateDepth = 0;
      while (state && stateDepth < 10) {
        if (state.memoizedState && typeof state.memoizedState === 'object') {
          const s = state.memoizedState;
          if (s.servicePK || s.PK) return s.servicePK || s.PK;
        }
        state = state.next;
        stateDepth++;
      }
      fiber = fiber.return;
      depth++;
    }
    return null;
  }

  function getSessionToken() {
    try {
      const session = JSON.parse(localStorage.getItem('persist:session') || '{}');
      return JSON.parse(session.sessionToken || '""') || null;
    } catch {
      return null;
    }
  }

  // capture which service card was clicked before the modal opens
  function interceptGlobeClicks() {
    document.addEventListener('click', (e) => {
      const btn = e.target.closest(
        'button[data-testid^="proxy-list-button"], button[aria-label*="Open Proxy List"]'
      );
      if (btn) {
        const card = btn.closest('[data-testid^="service-list-item-"]');
        if (card) {
          const name = card.getAttribute('data-testid').replace('service-list-item-', '');
          if (name) window.__controld_random_serviceName = name;
        }
      }
    }, true);
  }

  // also capture service name from fetch calls the app makes when opening the modal
  function interceptServiceFetch() {
    const origFetch = window.fetch;
    window.fetch = function (...args) {
      const url = typeof args[0] === 'string' ? args[0] : args[0]?.url;
      if (url) {
        const match = url.match(/\/profiles\/[^/]+\/services\/([^/?]+)$/);
        if (match && match[1] !== 'categories') {
          window.__controld_random_serviceName = match[1];
        }
      }
      return origFetch.apply(this, args);
    };
  }

  // handle spa navigation by watching for url changes
  function watchNavigation() {
    // patch pushState and replaceState so we can react to route changes
    const origPush = history.pushState;
    const origReplace = history.replaceState;

    history.pushState = function () {
      origPush.apply(this, arguments);
      window.dispatchEvent(new Event('controld-nav'));
    };
    history.replaceState = function () {
      origReplace.apply(this, arguments);
      window.dispatchEvent(new Event('controld-nav'));
    };

    // also catch back/forward
    window.addEventListener('popstate', () => {
      window.dispatchEvent(new Event('controld-nav'));
    });

    // on any nav event, make sure the observer is running
    window.addEventListener('controld-nav', () => {
      // clear stale service name when navigating away
      if (!window.location.pathname.includes('/services')) {
        window.__controld_random_serviceName = null;
      }
      // (re-)start observer just in case
      startObserver();
    });
  }

  // init
  interceptServiceFetch();
  interceptGlobeClicks();
  watchNavigation();
  startObserver();
})();