Auto Swipe para Tinder (PTBR)

Script para auto like/deslike no Tinder com base em palavras proibidas, sliders para controle de intervalo e melhorias visuais no painel.

目前为 2024-12-26 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Auto Swipe para Tinder (PTBR)
  3. // @version 1.0
  4. // @description Script para auto like/deslike no Tinder com base em palavras proibidas, sliders para controle de intervalo e melhorias visuais no painel.
  5. // @author Nox
  6. // @match https://tinder.com/app/recs
  7. // @grant none
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=tinder.com
  9. // @license MIT
  10. // @namespace https://greasyfork.org/users/1416065
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15.  
  16. // Configurações iniciais
  17. let interval = 3000; // Intervalo entre ações (ms)
  18. let profileOpenWait = 3000; // Tempo de espera ao abrir o perfil (ms)
  19. let forbiddenWords = ['exemplo', 'louca', 'proibido']; // Palavras proibidas padrão
  20. let likesCount = 0;
  21. let dislikesCount = 0;
  22. let isPaused = false;
  23.  
  24. // Carregar valores armazenados no localStorage, se existirem
  25. interval = parseInt(localStorage.getItem('interval')) || interval;
  26. profileOpenWait =
  27. parseInt(localStorage.getItem('profileOpenWait')) || profileOpenWait;
  28.  
  29. // Criação do painel de controle
  30. const container = document.createElement('div');
  31. container.style.position = 'fixed';
  32. container.style.top = '10px';
  33. container.style.right = '10px';
  34. container.style.zIndex = '1000';
  35. container.style.width = '250px';
  36. container.style.backgroundColor = 'rgba(0, 0, 0, 0.9)';
  37. container.style.color = 'white';
  38. container.style.padding = '15px';
  39. container.style.borderRadius = '10px';
  40. container.style.fontFamily = 'Arial, sans-serif';
  41. container.style.fontSize = '14px';
  42. container.style.display = 'flex';
  43. container.style.flexDirection = 'column';
  44. container.style.gap = '10px';
  45. container.style.opacity = '0.2';
  46. container.style.transition = 'opacity 0.3s';
  47. document.body.appendChild(container);
  48. container.addEventListener('mouseenter', () => {
  49. container.style.opacity = '1';
  50. });
  51. container.addEventListener('mouseleave', () => {
  52. container.style.opacity = '0.2';
  53. });
  54.  
  55. const statsContainer = document.createElement('div');
  56. statsContainer.style.display = 'flex';
  57. statsContainer.style.justifyContent = 'space-between';
  58. statsContainer.style.marginBottom = '10px';
  59.  
  60. const likeCounter = document.createElement('div');
  61. likeCounter.textContent = `Likes: ${likesCount}`;
  62. statsContainer.appendChild(likeCounter);
  63.  
  64. const dislikeCounter = document.createElement('div');
  65. dislikeCounter.textContent = `Dislikes: ${dislikesCount}`;
  66. statsContainer.appendChild(dislikeCounter);
  67.  
  68. container.appendChild(statsContainer);
  69.  
  70. forbiddenWords =
  71. JSON.parse(localStorage.getItem('forbiddenWords')) || forbiddenWords;
  72.  
  73. const forbiddenWordsInput = document.createElement('textarea');
  74. forbiddenWordsInput.value = forbiddenWords.join(', ');
  75. forbiddenWordsInput.style.width = '100%';
  76. forbiddenWordsInput.style.height = '50px';
  77. forbiddenWordsInput.style.borderRadius = '8px';
  78. forbiddenWordsInput.style.padding = '5px';
  79. forbiddenWordsInput.style.marginTop = '5px';
  80.  
  81. const forbiddenWordsLabel = document.createElement('label');
  82. forbiddenWordsLabel.textContent =
  83. 'Palavras proibidas (separadas por vírgula)';
  84. container.appendChild(forbiddenWordsLabel);
  85. container.appendChild(forbiddenWordsInput);
  86.  
  87. forbiddenWordsInput.addEventListener('input', () => {
  88. forbiddenWords = forbiddenWordsInput.value
  89. .split(',')
  90. .map((word) => word.trim())
  91. .filter((word) => word.length > 0);
  92. localStorage.setItem('forbiddenWords', JSON.stringify(forbiddenWords)); // Salvar no localStorage
  93. });
  94.  
  95. const pauseButton = document.createElement('button');
  96. pauseButton.textContent = 'Pausar';
  97. pauseButton.style.padding = '10px';
  98. pauseButton.style.borderRadius = '8px';
  99. pauseButton.style.cursor = 'pointer';
  100. container.appendChild(pauseButton);
  101.  
  102. const createSlider = (
  103. labelText,
  104. min,
  105. max,
  106. step,
  107. initialValue,
  108. nameInterval,
  109. onChange
  110. ) => {
  111. const sliderContainer = document.createElement('div');
  112. sliderContainer.style.display = 'flex';
  113. sliderContainer.style.flexDirection = 'column';
  114.  
  115. const label = document.createElement('label');
  116. label.textContent = labelText;
  117. label.style.marginBottom = '5px';
  118. sliderContainer.appendChild(label);
  119.  
  120. const valueDisplay = document.createElement('div');
  121. valueDisplay.style.textAlign = 'right';
  122. valueDisplay.textContent = `${(initialValue / 1000).toFixed(1)}s`;
  123. sliderContainer.appendChild(valueDisplay);
  124.  
  125. const slider = document.createElement('input');
  126. slider.type = 'range';
  127. slider.min = min;
  128. slider.max = max;
  129. slider.step = step;
  130. slider.value = initialValue;
  131. slider.style.width = '100%';
  132. slider.addEventListener('input', (e) => {
  133. const value = parseFloat(e.target.value);
  134. valueDisplay.textContent = `${(value / 1000).toFixed(1)}s`;
  135. onChange(value);
  136. localStorage.setItem(nameInterval, value);
  137. });
  138.  
  139. sliderContainer.appendChild(slider);
  140. return sliderContainer;
  141. };
  142.  
  143. const intervalSlider = createSlider(
  144. 'Intervalo entre ações (segundos)',
  145. 100,
  146. 10000,
  147. 100,
  148. interval,
  149. 'interval',
  150. (value) => {
  151. interval = value;
  152. }
  153. );
  154. container.appendChild(intervalSlider);
  155.  
  156. const profileWaitSlider = createSlider(
  157. 'Espera ao abrir perfil (segundos)',
  158. 100,
  159. 10000,
  160. 100,
  161. profileOpenWait,
  162. 'profileOpenWait',
  163. (value) => {
  164. profileOpenWait = value;
  165. }
  166. );
  167. container.appendChild(profileWaitSlider);
  168.  
  169. pauseButton.addEventListener('click', () => {
  170. isPaused = !isPaused;
  171. pauseButton.textContent = isPaused ? 'Continuar' : 'Pausar';
  172. });
  173.  
  174. // Criação do contêiner de informações do perfil
  175. const profileInfoContainer = document.createElement('div');
  176. profileInfoContainer.style.padding = '10px';
  177. profileInfoContainer.style.backgroundColor = '#1c1c1c';
  178. profileInfoContainer.style.borderRadius = '8px';
  179. profileInfoContainer.style.color = '#ffcc00';
  180. profileInfoContainer.style.marginTop = '10px';
  181. profileInfoContainer.style.display = 'flex';
  182. profileInfoContainer.style.flexDirection = 'column';
  183. profileInfoContainer.style.gap = '5px';
  184. container.appendChild(profileInfoContainer);
  185.  
  186. // Função para criar cada linha de informação
  187. function createInfoRow(label, value) {
  188. const row = document.createElement('div');
  189. row.style.display = 'flex';
  190. row.style.justifyContent = 'space-between';
  191.  
  192. const labelSpan = document.createElement('span');
  193. labelSpan.textContent = label + ':';
  194. labelSpan.style.fontWeight = 'bold';
  195.  
  196. const valueSpan = document.createElement('span');
  197. valueSpan.textContent = value;
  198.  
  199. row.appendChild(labelSpan);
  200. row.appendChild(valueSpan);
  201.  
  202. return row;
  203. }
  204.  
  205. // Função de extração das informações
  206. function extractProfileInfo() {
  207. const profileContainer = document.querySelector(
  208. '.Bgc\\(--color--background-sparks-profile\\)'
  209. );
  210.  
  211. if (!profileContainer) {
  212. console.log('Perfil não encontrado.');
  213. return null;
  214. }
  215.  
  216. const sections = profileContainer.querySelectorAll('.D\\(f\\).Ai\\(c\\)');
  217. const profileInfo = {};
  218.  
  219. sections.forEach((section) => {
  220. const svg = section.querySelector('svg path');
  221. const textElement = section.querySelector(
  222. '.Typs\\(body-1-regular\\), .Typs\\(body-1-strong\\)'
  223. );
  224.  
  225. if (!svg || !textElement) return;
  226.  
  227. const svgPath = svg.getAttribute('d').trim();
  228. const textContent = textElement.textContent.trim();
  229.  
  230. if (svgPath.includes('M12.301')) {
  231. profileInfo.distance = textContent;
  232. } else if (svgPath.includes('M16.95')) {
  233. profileInfo.height = textContent;
  234. } else if (svgPath.includes('M16.995')) {
  235. profileInfo.profession = textContent;
  236. } else if (svgPath.includes('M11.171')) {
  237. profileInfo.university = textContent;
  238. } else if (svgPath.includes('M2.25')) {
  239. profileInfo.location = textContent;
  240. } else if (svgPath.includes('M12.225')) {
  241. profileInfo.genderPronoun = textContent;
  242. } else if (svgPath.includes('M12 21.994')) {
  243. profileInfo.genderIdentity = textContent;
  244. } else if (svgPath.includes('M22.757')) {
  245. profileInfo.languages = textContent;
  246. } else {
  247. profileInfo.other = textContent; // Caso algum ícone não mapeado apareça
  248. }
  249. });
  250.  
  251. return profileInfo;
  252. }
  253.  
  254. const profileInfo = document.createElement('div');
  255. profileInfo.style.padding = '10px';
  256. profileInfo.style.backgroundColor = '#1c1c1c';
  257. profileInfo.style.borderRadius = '8px';
  258. profileInfo.style.color = '#ffcc00';
  259. profileInfo.textContent = 'Sobre mim: Não disponível';
  260. container.appendChild(profileInfo);
  261.  
  262. const forbiddenWordReason = document.createElement('div');
  263. forbiddenWordReason.style.padding = '10px';
  264. forbiddenWordReason.style.backgroundColor = '#8b0000';
  265. forbiddenWordReason.style.color = 'white';
  266. forbiddenWordReason.style.borderRadius = '8px';
  267. forbiddenWordReason.style.display = 'none';
  268. forbiddenWordReason.textContent = 'Motivo do deslike: Nenhum';
  269. container.appendChild(forbiddenWordReason);
  270.  
  271. function updateLikeCounter() {
  272. likeCounter.textContent = `Likes: ${likesCount}`;
  273. }
  274.  
  275. function updateDislikeCounter() {
  276. dislikeCounter.textContent = `Dislikes: ${dislikesCount}`;
  277. }
  278.  
  279. function updateProfileInfo(text) {
  280. const profileInfo = extractProfileInfo();
  281.  
  282. if (!profileInfo) {
  283. console.log('Não foi possível extrair as informações do perfil.');
  284. return;
  285. }
  286.  
  287. const {
  288. distance = 'Não informado',
  289. height = 'Não informado',
  290. profession = 'Não informado',
  291. university = 'Não informado',
  292. location = 'Não informado',
  293. genderPronoun = 'Não informado',
  294. genderIdentity = 'Não informado',
  295. languages = 'Não informado',
  296. } = profileInfo;
  297.  
  298. console.log('Informações extraídas:', profileInfo);
  299.  
  300. // Limpa o contêiner antes de atualizar
  301. profileInfoContainer.innerHTML = '';
  302.  
  303. // Adiciona as informações capturadas
  304. profileInfoContainer.appendChild(createInfoRow('Distância', distance));
  305. profileInfoContainer.appendChild(createInfoRow('Altura', height));
  306. profileInfoContainer.appendChild(createInfoRow('Profissão', profession));
  307. profileInfoContainer.appendChild(createInfoRow('Universidade', university));
  308. profileInfoContainer.appendChild(createInfoRow('Localização', location));
  309. profileInfoContainer.appendChild(createInfoRow('Pronomes', genderPronoun));
  310. profileInfoContainer.appendChild(
  311. createInfoRow('Identidade de Gênero', genderIdentity)
  312. );
  313. profileInfoContainer.appendChild(createInfoRow('Idiomas', languages));
  314.  
  315. // Atualiza a seção "Sobre mim"
  316. profileInfo.textContent = `Sobre mim: ${text}`;
  317. }
  318.  
  319. function showForbiddenWordReason(reason) {
  320. forbiddenWordReason.textContent = `Motivo do deslike: ${reason}`;
  321. forbiddenWordReason.style.display = 'block';
  322. }
  323.  
  324. function findLikeButton() {
  325. return document.querySelector(
  326. '.gamepad-button-wrapper .button.Bgc\\(\\$c-ds-background-gamepad-sparks-like-default\\)'
  327. );
  328. }
  329.  
  330. function findDislikeButton() {
  331. return document.querySelector(
  332. '.gamepad-button-wrapper .button.Bgc\\(\\$c-ds-background-gamepad-sparks-nope-default\\)'
  333. );
  334. }
  335.  
  336. function findProfileButton() {
  337. return document.querySelector(
  338. 'button.P\\(0\\).Trsdu\\(\\$normal\\).Sq\\(28px\\)'
  339. );
  340. }
  341.  
  342. function findProfileInfo() {
  343. const aboutHeader = Array.from(document.querySelectorAll('div')).find(
  344. (div) => div.textContent.includes('Sobre mim')
  345. );
  346.  
  347. if (aboutHeader) {
  348. const content = aboutHeader.parentElement.querySelector(
  349. '.Typs\\(body-1-regular\\)'
  350. );
  351.  
  352. console.log(content);
  353.  
  354. if (content) return content;
  355. }
  356.  
  357. return 'Não disponível';
  358. }
  359.  
  360. async function autoAction() {
  361. if (isPaused) return;
  362.  
  363. const profileButton = findProfileButton();
  364. if (profileButton) {
  365. profileButton.click();
  366. console.log('Botão de abrir perfil clicado.');
  367.  
  368. await new Promise((resolve) => setTimeout(resolve, profileOpenWait)); // Esperar o perfil carregar
  369.  
  370. const aboutText = findProfileInfo();
  371. updateProfileInfo(aboutText);
  372. console.log(`Sobre mim: ${aboutText}`);
  373.  
  374. await new Promise((resolve) =>
  375. setTimeout(resolve, profileOpenWait + interval)
  376. );
  377. }
  378.  
  379. const profileContainer = document.querySelector(
  380. '.Bgc\\(--color--background-sparks-profile\\)'
  381. );
  382.  
  383. const profileText = profileContainer
  384. ? Array.from(profileContainer.querySelectorAll('*'))
  385. .map((element) => element.textContent.trim())
  386. .filter((text) => text.length > 0)
  387. .join('\n')
  388. : '';
  389.  
  390. console.log(`InfoProfile:\n${profileText}`);
  391.  
  392. const profileInfo = extractProfileInfo();
  393. console.log('Informações detalhadas do perfil:', profileInfo);
  394.  
  395. // Verifica palavras proibidas
  396. for (const word of forbiddenWords) {
  397. if (profileText.toLowerCase().includes(word.toLowerCase())) {
  398. const dislikeButton = findDislikeButton();
  399. if (dislikeButton) {
  400. dislikeButton.click();
  401. dislikesCount++;
  402. updateDislikeCounter();
  403. showForbiddenWordReason(word);
  404. console.log(`Deslike dado! Motivo: ${word}`);
  405. const delay = interval;
  406. await new Promise((resolve) => setTimeout(resolve, delay));
  407. return;
  408. }
  409. }
  410. }
  411.  
  412. // Ação de like se não houver palavras proibidas
  413. const likeButton = findLikeButton();
  414. if (likeButton) {
  415. likeButton.click();
  416. likesCount++;
  417. updateLikeCounter();
  418. console.log(`Like dado!`);
  419. }
  420.  
  421. await new Promise((resolve) => setTimeout(resolve, interval));
  422. }
  423.  
  424. async function main() {
  425. while (true) {
  426. if (!isPaused) {
  427. await autoAction();
  428. } else {
  429. await new Promise((resolve) => setTimeout(resolve, 100)); // Aguardar brevemente enquanto pausado
  430. }
  431. }
  432. }
  433.  
  434. main();
  435. })();