Tinder Tools

Block button, chat search and pin chat features for Tinder.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

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

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name         Tinder Tools
// @name:pt      Tinder Tools
// @name:pt-BR   Tinder Tools

// @description  Block button, chat search and pin chat features for Tinder.
// @description:pt Botão de bloqueio rápido, busca de conversa e fixar chats no Tinder.
// @description:pt-BR Botão de bloqueio rápido, busca de conversa e fixar chats no Tinder.

// @version      1.1.0
// @namespace    https://greasyfork.org/users/1416065
// @author       Nox
// @license      MIT

// @match        https://tinder.com/*
// @compatible   chrome
// @compatible   firefox
// @grant        none
// @icon         https://www.google.com/s2/favicons?sz=64&domain=tinder.com
// ==/UserScript==

(function () {
  'use strict';

  // === i18n ===
  let uiLang = localStorage.getItem('tinderToolsLang') || localStorage.getItem('autoswipeLang') || 'pt';

  const T = {
    pt: {
      blockButton: 'Bloquear Usuário',
      blockingUser: 'Bloqueando...',
      blockSuccess: 'bloqueado com sucesso!',
      blockError: 'Erro ao bloquear. Tente novamente.',
      blockNotFound: 'Botão de bloqueio não encontrado.',
      searchChats: 'Buscar conversa...',
      pinChat: 'Fixar no topo',
      unpinChat: 'Desafixar',
      pinnedLabel: 'Fixados',
    },
    en: {
      blockButton: 'Block User',
      blockingUser: 'Blocking...',
      blockSuccess: 'blocked successfully!',
      blockError: 'Error blocking. Try again.',
      blockNotFound: 'Block button not found.',
      searchChats: 'Search chat...',
      pinChat: 'Pin to top',
      unpinChat: 'Unpin',
      pinnedLabel: 'Pinned',
    },
  };

  function t(key) {
    return T[uiLang]?.[key] ?? T.pt[key] ?? key;
  }

  // === Toast ===
  function showToast(message, color) {
    color = color || '#4caf50';
    const toast = document.createElement('div');
    toast.style.cssText = [
      'position:fixed',
      'bottom:30px',
      'left:50%',
      'transform:translateX(-50%)',
      'z-index:99999',
      `background-color:${color}`,
      'color:white',
      'padding:14px 28px',
      'border-radius:8px',
      'font-family:Arial,sans-serif',
      'font-size:14px',
      'font-weight:bold',
      'box-shadow:0 4px 12px rgba(0,0,0,0.5)',
      'transition:opacity 0.5s',
      'opacity:1',
      'pointer-events:none',
      'white-space:nowrap',
    ].join(';');
    toast.textContent = message;
    document.body.appendChild(toast);
    setTimeout(function () {
      toast.style.opacity = '0';
      setTimeout(function () {
        if (document.body.contains(toast)) document.body.removeChild(toast);
      }, 500);
    }, 3500);
  }

  // ============================================================
  // === Bloquear Usuário (página /app/messages/:id) ===
  // ============================================================

  function injectBlockButton() {
    if (document.getElementById('tindertools-block-btn')) return;

    const photoContainer = document.querySelector('.react-aspect-ratio-placeholder');
    if (!photoContainer) return;

    const tinderNativeBtn = Array.from(document.querySelectorAll('button')).find(function (b) {
      return /^Bloquear\s+\S/.test(b.textContent.trim());
    });
    const name = tinderNativeBtn
      ? tinderNativeBtn.textContent.trim().replace(/^Bloquear\s+/, '')
      : '';

    const btn = document.createElement('button');
    btn.id = 'tindertools-block-btn';
    btn.textContent = 'Bloquear ' + name;
    btn.style.cssText =
      'display:block;width:100%;padding:14px 24px;background:#b71c1c;color:#fff;border:none;font-weight:bold;font-size:15px;cursor:pointer;font-family:Arial,sans-serif;transition:background 0.2s;';
    btn.addEventListener('mouseenter', function () { btn.style.background = '#d32f2f'; });
    btn.addEventListener('mouseleave', function () { btn.style.background = '#b71c1c'; });
    btn.addEventListener('click', function () { blockCurrentUser(btn); });

    photoContainer.insertAdjacentElement('afterend', btn);
  }

  async function blockCurrentUser(btn) {
    const originalText = btn.textContent;
    btn.textContent = t('blockingUser');
    btn.disabled = true;

    try {
      const tinderBlockBtn = Array.from(document.querySelectorAll('button')).find(function (b) {
        return /^Bloquear\s+\S/.test(b.textContent.trim());
      });

      if (!tinderBlockBtn) {
        showToast(t('blockNotFound'), '#f44336');
        btn.textContent = originalText;
        btn.disabled = false;
        return;
      }

      const userName = tinderBlockBtn.textContent.trim().replace(/^Bloquear\s+/, '');
      tinderBlockBtn.click();

      let confirmBtn = null;
      for (let i = 0; i < 20; i++) {
        confirmBtn = Array.from(document.querySelectorAll('button')).find(function (b) {
          return b.textContent.trim().includes('Sim, bloquear');
        });
        if (confirmBtn) break;
        await new Promise(function (r) { setTimeout(r, 150); });
      }

      if (!confirmBtn) {
        showToast(t('blockError'), '#f44336');
        btn.textContent = originalText;
        btn.disabled = false;
        return;
      }

      confirmBtn.click();

      let valeuBtn = null;
      for (let i = 0; i < 20; i++) {
        valeuBtn = Array.from(document.querySelectorAll('button')).find(function (b) {
          return b.textContent.trim() === 'Valeu';
        });
        if (valeuBtn) break;
        await new Promise(function (r) { setTimeout(r, 150); });
      }

      if (valeuBtn) valeuBtn.click();

      showToast(userName + ' ' + t('blockSuccess'));
    } catch (err) {
      console.error('[TinderTools] Erro ao bloquear:', err);
      showToast(t('blockError'), '#f44336');
    } finally {
      btn.textContent = originalText;
      btn.disabled = false;
    }
  }

  (function watchForBlockTarget() {
    let lastPath = location.pathname;
    function checkPath() {
      const path = location.pathname;
      if (path !== lastPath) {
        lastPath = path;
        const old = document.getElementById('tindertools-block-btn');
        if (old) old.remove();
      }
      if (path.includes('/app/messages/')) {
        injectBlockButton();
      }
    }
    setInterval(checkPath, 500);
  })();

  // ============================================================
  // === Busca e Fixar Chats (página /app/messages) ===
  // ============================================================

  const PINNED_CHATS_KEY = 'tinderToolsPinnedChats';
  // Migrar pinned chats da chave antiga do autoswipe (se existir)
  let pinnedChats = JSON.parse(localStorage.getItem(PINNED_CHATS_KEY) || '[]');
  if (pinnedChats.length === 0) {
    const legacy = localStorage.getItem('autoswipePinnedChats');
    if (legacy) {
      pinnedChats = JSON.parse(legacy);
      localStorage.setItem(PINNED_CHATS_KEY, JSON.stringify(pinnedChats));
      localStorage.removeItem('autoswipePinnedChats');
    }
  }

  function savePinnedChats() {
    localStorage.setItem(PINNED_CHATS_KEY, JSON.stringify(pinnedChats));
  }

  function getChatId(li) {
    const a = li.querySelector('a[href*="/app/messages/"]');
    if (!a) return null;
    const m = (a.getAttribute('href') || '').match(/\/app\/messages\/([^/?#]+)/);
    return m ? m[1] : null;
  }

  function getChatName(li) {
    const span = li.querySelector('.messageListItem__name');
    if (span) return span.textContent.trim();
    const a = li.querySelector('a[aria-label]');
    return a ? (a.getAttribute('aria-label') || '') : '';
  }

  let _chatListObserver = null;

  function applyChatListState(ul, searchValue) {
    if (_chatListObserver) _chatListObserver.disconnect();

    ul.querySelectorAll('.tindertools-chat-sep').forEach(el => el.remove());

    const items = Array.from(ul.querySelectorAll(':scope > li'));
    const pinnedItems = [];
    const unpinnedItems = [];
    items.forEach(li => {
      const id = getChatId(li);
      if (id && pinnedChats.includes(id)) pinnedItems.push(li);
      else unpinnedItems.push(li);
    });

    pinnedItems.sort((a, b) => pinnedChats.indexOf(getChatId(a)) - pinnedChats.indexOf(getChatId(b)));

    const frag = document.createDocumentFragment();
    if (pinnedItems.length > 0) {
      const sep = document.createElement('li');
      sep.className = 'tindertools-chat-sep';
      sep.style.cssText =
        'padding:6px 20px;font-size:11px;font-weight:600;color:#888;text-transform:uppercase;letter-spacing:0.08em;pointer-events:none;user-select:none;';
      sep.textContent = t('pinnedLabel');
      frag.appendChild(sep);
      pinnedItems.forEach(li => frag.appendChild(li));
    }
    unpinnedItems.forEach(li => frag.appendChild(li));
    ul.appendChild(frag);

    const query = searchValue.toLowerCase().trim();
    [...pinnedItems, ...unpinnedItems].forEach(li => {
      const name = getChatName(li).toLowerCase();
      li.style.display = !query || name.includes(query) ? '' : 'none';
    });

    if (_chatListObserver) {
      _chatListObserver.observe(ul, { childList: true, subtree: false });
    }
  }

  function addPinButton(li, ul, searchInput) {
    if (li.dataset.tindertoolsPin) return;
    const id = getChatId(li);
    if (!id) return;
    li.dataset.tindertoolsPin = '1';
    li.style.position = 'relative';

    const btn = document.createElement('button');
    btn.style.cssText =
      'position:absolute;right:8px;top:50%;transform:translateY(-50%);background:rgba(20,20,20,0.88);border:none;border-radius:50%;width:30px;height:30px;cursor:pointer;font-size:15px;display:flex;align-items:center;justify-content:center;transition:opacity 0.15s;z-index:10;padding:0;';

    const refresh = () => {
      const pinned = pinnedChats.includes(id);
      btn.textContent = pinned ? '📌' : '📍';
      btn.title = pinned ? t('unpinChat') : t('pinChat');
      btn.style.opacity = pinned ? '1' : '0';
    };
    refresh();

    li.addEventListener('mouseenter', () => { btn.style.opacity = '1'; });
    li.addEventListener('mouseleave', () => {
      if (!pinnedChats.includes(id)) btn.style.opacity = '0';
    });

    btn.addEventListener('click', e => {
      e.preventDefault();
      e.stopPropagation();
      const idx = pinnedChats.indexOf(id);
      if (idx === -1) pinnedChats.unshift(id);
      else pinnedChats.splice(idx, 1);
      savePinnedChats();
      refresh();
      applyChatListState(ul, searchInput.value);
      Array.from(ul.querySelectorAll(':scope > li')).forEach(item =>
        addPinButton(item, ul, searchInput)
      );
    });

    li.appendChild(btn);
  }

  function injectMessageListUI(messageListEl) {
    if (messageListEl.dataset.tindertoolsInit) return;
    messageListEl.dataset.tindertoolsInit = '1';

    const ul = messageListEl.querySelector('ul');
    if (!ul) return;

    const wrapper = document.createElement('div');
    wrapper.style.cssText =
      'padding:8px 12px;position:sticky;top:0;z-index:100;background:var(--color--background-surface-page,#111);border-bottom:1px solid rgba(255,255,255,0.06);';

    const input = document.createElement('input');
    input.type = 'text';
    input.placeholder = t('searchChats');
    input.style.cssText =
      'width:100%;box-sizing:border-box;background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.15);border-radius:20px;color:#fff;padding:8px 16px;font-size:14px;outline:none;font-family:inherit;';
    input.addEventListener('focus', () => { input.style.borderColor = 'rgba(255,255,255,0.4)'; });
    input.addEventListener('blur', () => { input.style.borderColor = 'rgba(255,255,255,0.15)'; });
    input.addEventListener('input', () => applyChatListState(ul, input.value));

    wrapper.appendChild(input);
    messageListEl.insertBefore(wrapper, ul);

    Array.from(ul.querySelectorAll(':scope > li')).forEach(li =>
      addPinButton(li, ul, input)
    );
    applyChatListState(ul, '');

    _chatListObserver = new MutationObserver(() => {
      Array.from(ul.querySelectorAll(':scope > li')).forEach(li =>
        addPinButton(li, ul, input)
      );
      applyChatListState(ul, input.value);
    });
    _chatListObserver.observe(ul, { childList: true, subtree: false });
  }

  function watchForMessageList() {
    const tryInit = () => {
      const el = document.querySelector('.messageList');
      if (el && !el.dataset.tindertoolsInit) injectMessageListUI(el);
    };
    tryInit();
    const obs = new MutationObserver(tryInit);
    obs.observe(document.body, { childList: true, subtree: true });
  }

  watchForMessageList();
  console.log('[TinderTools] iniciado — block button + chat search + pin chat');
})();