// ==UserScript== // @name Tinder Deblur // @namespace Violentmonkey Scripts // @match https://tinder.com/* // @grant none // @version 5.0 // @author Tajnymag // @downloadURL https://raw.githubusercontent.com/tajnymag/tinder-deblur/main/tinder.user.js // @description Simple script using the official Tinder API to get clean photos of the users who liked you // ==/UserScript== // enable type checking // @ts-check // @filename: types/tampermonkey.d.ts class UserCacheItem { /** * @param {string} userId * @param {object} user */ constructor(userId, user) { this.userId = userId; this.user = user; this.hidden = !!localStorage.getItem('hiddenUsers')?.includes(userId); this.photoIndex = 0; } /** * @returns {string | null} */ getPreviousPhoto() { if (!this.user) return null; this.photoIndex = this.photoIndex - 1; if (this.photoIndex < 0) this.photoIndex = this.user.photos.length - 1; return this.user.photos[this.photoIndex].url; } /** * @returns {string | null} */ getNextPhoto() { if (!this.user) return null; this.photoIndex = (this.photoIndex + 1) % this.user.photos.length; return this.user.photos[this.photoIndex].url; } /** * @returns {number} */ getAge() { if (!this.user || !this.user.birth_date) return 0; const currentDate = new Date(); const birthDate = Date.parse(this.user.birth_date); const currentYear = currentDate.getFullYear(); const currentMonth = currentDate.getMonth(); const currentDay = currentDate.getDay(); const birthYear = birthDate.getFullYear(); const birthMonth = birthDate.getMonth(); const birthDay = birthDate.getDay(); let age = currentYear - birthYear; if (currentMonth < birthMonth) age--; else if (currentDay < birthDay) age--; return age; } /** * @returns {boolean} */ isHidden() { return this.hidden; } /** @param {boolean} hidden */ setHidden(hidden) { this.hidden = hidden; } } class UserCache { constructor() { /** @type {Map} */ this.cache = new Map(); } /** * @param {string} userId * @param {object} user * @returns {UserCacheItem} */ add(userId, user) { this.delete(userId); const newItem = new UserCacheItem(userId, user); this.cache.set(userId, newItem); return newItem; } /** * @param {string} userId */ has(userId) { return this.cache.has(userId); } /** * @param {string} userId * @returns UserCacheItem | undefined */ get(userId) { return this.cache.get(userId); } /** * @param {string} userId */ delete(userId) { const existingUser = this.cache.get(userId); if (!existingUser) return; this.cache.delete(userId); } clear() { for (const userItem of this.cache.values()) { this.cache.delete(userItem.userId); } } } /** * Holds a persistent cache of fetched users and intervals for updating their photos */ const cache = new UserCache(); /** * Original function of the script */ async function unblur() { /** @type {HTMLElement | null} */ const likesGridContainerEl = document.querySelector('main div.Expand > div[role="grid"]'); if (!likesGridContainerEl) return; if (!likesGridContainerEl.dataset.loadingTextAdded) { likesGridContainerEl.dataset.loadingTextAdded = 'true'; likesGridContainerEl.style.position = 'relative'; const loadingContainer = document.createElement('DIV'); loadingContainer.classList.add('loading-container'); loadingContainer.setAttribute( 'style', 'align-items: center; background-color: black; display: flex; height: 100%; justify-content: center; left: 0; position: absolute; text-align: center; top: 0; width: 100%; z-index: 50;' ); likesGridContainerEl.insertBefore(loadingContainer, likesGridContainerEl.firstChild); const loadingText = document.createElement('H4'); loadingText.setAttribute( 'style', 'color: #d2d2d3; font-size: 40px; letter-spacing: 2px; text-transform: uppercase;' ); loadingText.innerText = 'Loading'; loadingContainer.appendChild(loadingText); } const [failedToFetchTeasersError, teasers] = await safeAwait(fetchTeasers()); if (failedToFetchTeasersError) { console.error(`Could not load teasers: ${failedToFetchTeasersError.name}`); return; } /** @type {NodeListOf} */ const teaserEls = document.querySelectorAll('.Expand.enterAnimationContainer > div:nth-child(1)'); for (let i = 0; i < teaserEls.length; ++i) { const teaserUser = teasers[i].user; const teaserEl = teaserEls[i]; const teaserImage = teaserUser.photos[0].url; if (!teaserEl) continue; const likeEl = teaserEl.parentElement?.parentElement; if (!likeEl) continue; if (!likeEl.classList.contains('like')) likeEl.classList.add('like'); const likeElContent = likeEl.querySelector('.enterAnimationContainer.Expand'); if (!likeElContent) continue; if (teaserImage.includes('unknown') || !teaserImage.includes('images-ssl')) { if (likeEl.dataset.invalid) continue; likeEl.dataset.invalid = 'true'; likeElContent.style.opacity = '0.5'; likeEl.innerHTML += `
Unable to deblur This is not a bug!
Not all likes can be unblurred.
`; continue; } if (likeEl.dataset.invalid) { delete likeEl.dataset.invalid; likeEl.querySelector('.invalid-text').remove(); likeElContent.style.opacity = '1'; } const userId = teaserImage.slice(32, 56); if (cache.has(userId)) continue; try { // only update teaser once if (likeEl.dataset.userId) continue; const infoContainerEl = teaserEl.parentElement?.lastElementChild; if (!infoContainerEl) { console.error(`Could not find info container for '${userId}'`); return; } infoContainerEl.outerHTML = `
`; likeEl.classList.add('like-item'); likeEl.dataset.userId = userId; teaserEl.id = 'teaser-' + userId; teaserEl.classList.add('teaser', 'like-action-button', 'like-action-next-photo'); teaserEl.style.backgroundSize = 'cover'; fetchUser(userId).then((user) => { // save user to cache const userItem = cache.add(userId, user ?? null); // hide the like if it was passed before if (userItem.isHidden()) { likeEl.remove(); return; } if (!user) { teaserEl.style.backgroundImage = `url('https://preview.gotinder.com/${teaserUser._id}/original_${teaserUser.photos[0].id}.jpeg')`; infoContainerEl.remove(); console.error(`Could not load user '${userId}'`); return; } // log user name + bio console.debug(`${user.name} (${user.bio})`); const likeUserInfo = infoContainerEl.querySelector('like-user-info')?.firstChild; if (!likeUserInfo) return; likeUserInfo.innerHTML = `
${user.distance_mi} miles away
`; teaserEl.style.backgroundImage = `url(${user.photos[0].url})`; }); } catch (err) { if (err.name != 'SyntaxError') console.error(`Failed to load user '${userId}': ${err.name}`); } } } /** * Remove Tinder Gold ads */ function removeGoldAds() { // hide special offer advertisement const advertisementLogo = document.querySelector('div[aria-label="Tinder Gold"]'); if (advertisementLogo) { const addContainer = advertisementLogo.parentElement?.parentElement; if (addContainer) addContainer.style.display = 'none'; } // remove 'Tinder Gold' advertisement for (const advertisementEl of document.getElementsByTagName('div')) { if (advertisementEl.children.length > 0) continue; if (advertisementEl.innerText.toLowerCase().includes('gold')) advertisementEl.remove(); } // remove gold button /** @type {HTMLButtonElement | null} */ const goldButtonEl = document.querySelector('div.CenterAlign button[type="button"]'); if (goldButtonEl != null) goldButtonEl.remove(); } function updateUserInfos() { /** @type {HTMLElement | null} */ const likesGridContainerEl = document.querySelector('main div.Expand > div[role="grid"]'); if (!likesGridContainerEl) return; // fix scrolling if (likesGridContainerEl.parentElement) likesGridContainerEl.parentElement.style.overflow = 'hidden'; if (!likesGridContainerEl.dataset.eventsInterrupted) { likesGridContainerEl.dataset.eventsInterrupted = 'true'; likesGridContainerEl.addEventListener('scroll', (event) => event.stopImmediatePropagation(), true); likesGridContainerEl.style.justifyContent = 'flex-start'; } // update the likes grid const likesGridEl = likesGridContainerEl.lastElementChild; if (!likesGridEl.dataset.stylesUpdated) { likesGridEl.dataset.stylesUpdated = 'true'; likesGridEl.classList.add('D(f)'); likesGridEl.style.removeProperty('height'); likesGridEl.style.flexWrap = 'wrap'; likesGridEl.style.flex = 'unset'; likesGridEl.style.gap = '10px'; likesGridEl.style.justifyContent = 'flex-start'; } // update the like elements for (const likeEl of likesGridEl.children) { // don't update the element if it is invisible if (likeEl.style.display === 'none') continue; likeEl.classList.remove('Cur(p)'); likeEl.style.removeProperty('transform'); likeEl.style.position = 'relative'; likeEl.style.backgroundColor = 'black'; likeEl.style.borderRadius = '8px'; likeEl.style.marginTop = '0'; likeEl.style.marginBottom = '0'; const userId = likeEl.dataset.userId; // only update if user was loaded if (!userId) continue; const userItem = cache.get(userId); if (!userItem) continue; // only update the container once if (likeEl.dataset.infoSet) continue; likeEl.dataset.infoSet = 'true'; /** @type {HTMLElement | null} */ const infoContainerEl = likeEl.querySelector('.like-user-info'); if (!infoContainerEl) continue; // add action buttons likeEl.innerHTML += ` `; // handle like element click likeEl.addEventListener( 'click', (event) => { /** @type {HTMLElement | null} */ let currentParent = event.target; if (!currentParent) return; while (!currentParent?.classList.contains('like-action-button')) { if (!currentParent?.parentElement) break; currentParent = currentParent.parentElement; } event.stopImmediatePropagation(); if (!currentParent) return; if (currentParent.classList.contains('like-action-pass')) { pass(userItem); } else if (currentParent.classList.contains('like-action-like')) { like(userItem); } else { if (!userItem.user) return; if (currentParent.classList.contains('like-action-photo')) { const index = parseInt(currentParent.dataset.photoIndex ?? '0'); showPhoto(likeEl, userItem.photoIndex, index, userItem.user.photos[index].url); userItem.photoIndex = index; } else if (currentParent.classList.contains('like-action-next-photo')) { const oldIndex = userItem.photoIndex; const photoUrl = event.offsetX < currentParent.clientWidth / 2 ? userItem.getPreviousPhoto() : userItem.getNextPhoto(); showPhoto(likeEl, oldIndex, userItem.photoIndex, photoUrl); } return; } likeEl.remove(); }, true ); /** @type {HTMLElement | null} */ const userNameEl = infoContainerEl.querySelector('.like-user-name'); /** @type {HTMLElement | null} */ const userBioEl = infoContainerEl.querySelector('.like-user-bio'); if (!userNameEl || !userBioEl) continue; const user = userItem.user; // update info container const userBioElHeight = userBioEl.getBoundingClientRect().height; userNameEl.style.transform = `translateY(-${ userBioElHeight + 20 /* distance height */ + 20 /* name height */ + 20 /* action buttons */ }px)`; infoContainerEl.style.opacity = `1`; // add photo selector const photoSelectorContainer = document.createElement('div'); photoSelectorContainer.setAttribute( 'class', 'photo-selectors CenterAlign D(f) Fxd(r) W(100%) Px(8px) Pos(a) Iso(i)' ); photoSelectorContainer.style.top = '5px'; likeEl.appendChild(photoSelectorContainer); for (let i = 0; i < user.photos.length; i++) { const photoButton = document.createElement('button'); photoButton.setAttribute( 'class', 'like-action-button like-action-photo bullet D(ib) Va(m) Cnt($blank)::a D(b)::a Cur(p) H(4px)::a W(100%)::a Py(4px) Px(2px) W(100%) Bdrs(100px)::a focus-background-style ' + (i == 0 ? 'Bgc($c-ds-background-tappy-indicator-active)::a bullet--active' : 'Bgc($c-ds-background-tappy-indicator-inactive)::a') ); photoButton.dataset.photoIndex = i.toString(); photoSelectorContainer.appendChild(photoButton); } } const totalLikesCount = likesGridEl?.childElementCount ?? 0; if (totalLikesCount == 0) { if (!likesGridContainerEl.dataset.noLikes) { likesGridContainerEl.dataset.noLikes = 'true'; if (likesGridContainerEl.dataset.loadingTextAdded) likesGridContainerEl.querySelector('.loading-container')?.remove(); const noLikesContainer = document.createElement('DIV'); noLikesContainer.classList.add('no-likes-container'); noLikesContainer.setAttribute( 'style', 'align-items: center; background-color: black; display: flex; height: 100%; justify-content: center; left: 0; position: absolute; text-align: center; top: 0; width: 100%; z-index: 50;' ); likesGridContainerEl.insertBefore(noLikesContainer, likesGridContainerEl.firstChild); const noLikesText = document.createElement('H4'); noLikesText.setAttribute( 'style', 'color: #d2d2d3; font-size: 40px; letter-spacing: 2px; text-transform: uppercase;' ); noLikesText.innerText = 'No likes available'; noLikesContainer.appendChild(noLikesText); } } else if ( document.querySelectorAll('div[data-info-set]').length > 0 || document.querySelectorAll('div[data-invalid]').length == totalLikesCount ) { if (!likesGridContainerEl.dataset.loadingComplete) { likesGridContainerEl.dataset.loadingComplete = 'true'; if (likesGridContainerEl.dataset.noLikes) { delete likesGridContainerEl.dataset.noLikes; likesGridContainerEl.querySelector('.no-likes-container')?.remove(); } const loadingContainer = likesGridContainerEl.querySelector('.loading-container'); if (!loadingContainer) return; loadingContainer.style.transition = 'opacity 0.4s 0.2s ease-out'; loadingContainer.style.opacity = '0'; setTimeout(() => loadingContainer.remove(), 600); } } } /** * Updates the photo * @param {HTMLElement} likeEl * @param {number} oldIndex * @param {number} index * @param {string} photoUrl */ function showPhoto(likeEl, oldIndex, index, photoUrl) { /** @type {HTMLElement | null} */ const teaserEl = likeEl.querySelector('.teaser'); const photoSelectorContainer = likeEl.querySelector('.photo-selectors'); if (!photoSelectorContainer) return; const oldPhotoButton = photoSelectorContainer.children[oldIndex]; oldPhotoButton.classList.remove('Bgc($c-ds-background-tappy-indicator-active)::a'); oldPhotoButton.classList.remove('bullet--active'); oldPhotoButton.classList.add('Bgc($c-ds-background-tappy-indicator-inactive)::a'); if (!teaserEl) return; teaserEl.style.backgroundImage = `url('${photoUrl}')`; const newPhotoButton = photoSelectorContainer.children[index]; newPhotoButton.classList.remove('Bgc($c-ds-background-tappy-indicator-inactive)::a'); newPhotoButton.classList.add('Bgc($c-ds-background-tappy-indicator-active)::a'); newPhotoButton.classList.add('bullet--active'); } /** * Hides a user from the likes section * @param {UserCacheItem} userItem */ function hide(userItem) { const hiddenUsers = localStorage.getItem('hiddenUsers')?.split(';') ?? []; if (!hiddenUsers.includes(userItem.userId)) hiddenUsers.push(userItem.userId); localStorage.setItem('hiddenUsers', hiddenUsers.join(';')); userItem.hidden = true; } /** * Adds user filtering */ function updateUserFiltering() { /** @type {HTMLDivElement | null} */ const filterButtonEl = document.querySelector('div[role="grid"] div[role="option"]:nth-of-type(1)'); if (filterButtonEl != null) { if (!filterButtonEl.dataset.eventsInterrupted) { filterButtonEl.dataset.eventsInterrupted = 'true'; filterButtonEl.addEventListener('click', () => { setTimeout(() => { // remove "show all" button for (const element of document.querySelectorAll( 'div[role="dialog"] .menuItem__contents > div > div[role="button"]' )) { element.remove(); } const applyContainer = document.querySelector( 'div[role="dialog"] > div:not(.menuItem):not(.CenterAlign)' ); if (applyContainer != null) { applyContainer.innerHTML = ''; applyContainer.className = ''; applyContainer.setAttribute( 'style', 'align-items: center; display: flex; flex-shrink: 0; font-size: 20px; height: 50px; justify-content: center; width: 100%;' ); const applyButtonEl = document.createElement('button'); applyButtonEl.innerText = 'Apply'; applyButtonEl.style.textTransform = 'uppercase'; applyButtonEl.style.fontWeight = '600'; applyButtonEl.style.color = 'var(--color--text-brand-normal)'; applyContainer.appendChild(applyButtonEl); applyButtonEl.addEventListener( 'click', (event) => { event.stopImmediatePropagation(); const dialogMenuItemContents = document.querySelectorAll( 'div[role="dialog"] > .menuItem > .menuItem__contents > div:nth-of-type(2)' ); // max distance const maxDistanceElement = dialogMenuItemContents[0].querySelector('div[style]'); if (!maxDistanceElement) return; let maxDistance = Math.floor( (maxDistanceElement.clientWidth / (maxDistanceElement.parentElement?.clientWidth ?? 1)) * (161 - 2) + 2 ); if (maxDistance == 161) maxDistance = Number.MAX_SAFE_INTEGER; // age range const ageRangeElement = dialogMenuItemContents[1].querySelector('div[style]'); if (!ageRangeElement) return; const ageRangeStart = Math.round( (parseFloat(getComputedStyle(ageRangeElement).left.replace('px', '')) / (ageRangeElement.parentElement?.clientWidth ?? 1)) * (100 - 18) + 18 ); let ageRangeEnd = ageRangeStart + Math.round( (ageRangeElement.clientWidth / (ageRangeElement.parentElement?.clientWidth ?? 1)) * (100 - 18) ); if (ageRangeEnd == 100) ageRangeEnd = Number.MAX_SAFE_INTEGER; // minimum photos amount let minimumPhotosAmount = 0; /** @type {NodeListOf} */ const photosOptions = dialogMenuItemContents[2].querySelectorAll('div[role="option"]'); for (const minimumPhotosOption of photosOptions) { if ( minimumPhotosOption .getAttribute('class') ?.includes('c-ds-border-passions-shared') ) { minimumPhotosAmount = parseInt(minimumPhotosOption.innerText); break; } } // interests const interests = []; /** @type {NodeListOf} */ const interestOptions = dialogMenuItemContents[3].querySelectorAll('div[role="option"]'); for (const interestOption of interestOptions) { if (interestOption.getAttribute('class')?.includes('c-ds-border-passions-shared')) interests.push(interestOption.innerText); } /** @type {NodeListOf} */ const dialogMenuSelects = document.querySelectorAll( 'div[role="dialog"] > .menuItem > .menuItem__contents .menuItem__select input' ); // verified const verifiedRequired = dialogMenuSelects[0].checked; // has bio const bioRequired = dialogMenuSelects[1].checked; // apply filter /** @type {NodeListOf} */ const likeItems = document.querySelectorAll('.like-item'); for (const likeElement of likeItems) { if (!likeElement.dataset.userId) continue; const userItem = cache.get(likeElement.dataset.userId); if (!userItem) continue; const user = userItem.user; if (!user) continue; const userInterests = Array.from(user.user_interests ?? []).map( (interest) => interest.name ); let matches = true; // check radius if (!user.hide_distance && user.distance_mi > maxDistance) matches = false; // check age range else if ( !user.hide_age && (userItem.getAge() < ageRangeStart || userItem.getAge() > ageRangeEnd) ) matches = false; // check photos amount else if (user.photos.length < minimumPhotosAmount) matches = false; // check verified else if (!user.is_tinder_u && verifiedRequired) matches = false; // check bio else if (user.bio.length == 0 && bioRequired) matches = false; // check interests else { for (const interest of interests) { if (!userInterests.includes(interest)) matches = false; } } likeElement.style.display = matches ? 'flex' : 'none'; } // close dialog /** @type {Element | null | undefined} */ const applyButton = document.querySelector('div[role="dialog"]')?.parentElement?.firstElementChild; applyButton?.click(); setTimeout(removeGoldAds, 250); }, true ); } }, 200); }); } if (!filterButtonEl.parentElement) return; /** @type {NodeListOf} */ const optionEls = filterButtonEl.parentElement.querySelectorAll('div[role="option"]'); for (const optionEl of optionEls) { if (!optionEl.dataset.eventsInterrupted) optionEl.remove(); } if (!filterButtonEl.parentElement.parentElement) return; /** @type {HTMLElement} */ const filterButtonContainer = filterButtonEl.parentElement.parentElement; filterButtonContainer.style.maxWidth = 'calc(100% - 36.5px * 2 + 12px * 2)'; } } /** * Creates a message status icon + text */ function createMessageStatusElement(parentNode, read) { if (parentNode == null) return; const status = document.createElement('div'); status.setAttribute( 'class', 'Pos(r) Fz($2xs) My(8px) Mx(4px) Mih(16px) C($c-ds-text-secondary) D(f) Ai(c) Jc(fe) Fxd(r)' ); status.innerHTML = `
${read ? 'Read' : 'Sent'} `; parentNode.appendChild(status); } /** * Displays read status below sent messages */ async function updateMessageInfos(matchId) { /** @type {HTMLDivElement | null} */ const lastMessageStatus = document.querySelector('.msg__status'); if (!lastMessageStatus) return; lastMessageStatus.remove(); fetchMatches().then((matches) => { if (matches == null) return; const filteredMatches = matches.filter((match) => match.id === matchId); if (filteredMatches.length == 0) return; const match = filteredMatches[0]; const lastReadMesssageId = match.seen.last_seen_msg_id; if (!lastReadMesssageId) return; // get message content from last read message fetchMessages(matchId).then((messages) => { if (messages == null) return; const filteredMessages = messages.filter((message) => message._id === lastReadMesssageId); if (filteredMessages.length == 0) return; let lastReadMessage = filteredMessages[0]; let currentMessageIndex = messages.indexOf(lastReadMessage); while (lastReadMessage.from === match.person._id && currentMessageIndex < messages.length - 1) { lastReadMessage = messages[currentMessageIndex++]; } // only the matched person sent a message if (!lastReadMessage) return; const messageContent = lastReadMessage.message; /** @type {NodeListOf} */ const messageElements = document.querySelectorAll('.msg'); if (messageElements.length == 0) return; for (let i = messageElements.length - 1; i >= 0; i--) { const messageElement = messageElements[i]; const messageContainer = messageElement.parentElement?.parentElement; if (!messageContainer) continue; const isRead = messageElement.innerText === messageContent; // only add info to messages sent by the user of this script if (messageContainer.classList.contains('Ta(e)')) createMessageStatusElement(messageContainer, isRead); if (isRead) break; } }); }); } /** * Passes a user and hides it from the likes section afterwards * @param {UserCacheItem} userItem */ async function pass(userItem) { const response = await fetch( `https://api.gotinder.com/pass/${userItem.userId}?s_number=${userItem.user?.s_number ?? 0}`, { headers: { 'X-Auth-Token': localStorage.getItem('TinderWeb/APIToken') ?? '', platform: 'android', }, method: 'GET', } ); hide(userItem); } /** * Likes a user and hides it from the likes section afterwards * @param {UserCacheItem} userItem */ async function like(userItem) { const response = await fetch(`https://api.gotinder.com/like/${userItem.userId}`, { headers: { 'X-Auth-Token': localStorage.getItem('TinderWeb/APIToken') ?? '', platform: 'android', 'Content-Type': 'application/json', }, method: 'POST', body: JSON.stringify( userItem.user ? { liked_content_id: userItem.user.photos[0].id, liked_content_type: 'photo', s_number: userItem.user.s_number, } : { s_number: 0, } ), }); hide(userItem); } /** * Fetches all messages in a conversation using Tinder API * @param {string} matchId * @returns {Promise} */ async function fetchMessages(matchId) { return fetch(`https://api.gotinder.com/v2/matches/${matchId}/messages?locale=en&count=100`, { headers: { 'X-Auth-Token': localStorage.getItem('TinderWeb/APIToken') ?? '', }, }) .then((res) => res.json()) .then((res) => res.data.messages); } /** * Fetches matches using Tinder API * @returns {Promise} */ async function fetchMatches() { return fetch('https://api.gotinder.com/v2/matches?locale=en&count=60&message=1', { headers: { 'X-Auth-Token': localStorage.getItem('TinderWeb/APIToken') ?? '', }, }) .then((res) => res.json()) .then((res) => res.data.matches); } /** * Fetches teaser cards using Tinder API * @returns {Promise} */ async function fetchTeasers() { return fetch('https://api.gotinder.com/v2/fast-match/teasers', { headers: { 'X-Auth-Token': localStorage.getItem('TinderWeb/APIToken') ?? '', platform: 'android', }, }) .then((res) => res.json()) .then((res) => res.data.results); } /** * Fetches information about specific user using Tinder API * @param {string} id * @returns {Promise} */ async function fetchUser(id) { /* disabled due to API changes, currently looking for a workaround! return fetch(`https://api.gotinder.com/user/${id}`, { headers: { 'X-Auth-Token': localStorage.getItem('TinderWeb/APIToken') ?? '', platform: 'android', }, }) .then((res) => res.json()) .then((res) => res.results);*/ return null; } /** * Awaits the first event of the specified listener * @param {EventTarget} target * @param {string} eventType * @returns {Promise} */ async function once(target, eventType) { return new Promise((resolve) => { const resolver = () => { target.removeEventListener(eventType, resolver); resolve(); }; target.addEventListener(eventType, resolver); }); } /** * Utility function to catch errors inline * @template T * @template {Error} U * @param {Promise} promise * @return {Promise<[null, T] | [U, undefined]>} */ async function safeAwait(promise) { try { const result = await promise; return [null, result]; } catch (err) { return [err, undefined]; } } /** * Awaits until the main app element is found in DOM and returns it * @returns {Promise} */ async function waitForApp() { const getAppEl = (parent) => parent.querySelector('.App'); let appEl = getAppEl(document.body); if (appEl) return appEl; return new Promise((resolve) => { new MutationObserver((_, me) => { appEl = getAppEl(document.body); if (appEl) { me.disconnect(); resolve(appEl); } }).observe(document.body, { subtree: true, childList: true }); }); } async function main() { // check if running as a userscript if (typeof GM_info === 'undefined') { console.warn( '[TINDER DEBLUR]: The only supported way of running this script is through a userscript management browser addons like Violentmonkey, Tampermonkey or Greasemonkey!' ); console.warn( '[TINDER DEBLUR]: Script was not terminated, but you should really look into the correct way of running it.' ); } // wait for a full page load await once(window, 'load'); const appEl = await waitForApp(); const pageCheckCallback = async () => { if (['/app/likes-you', '/app/gold-home'].includes(location.pathname)) { // check if any likes were loaded yet if (document.querySelectorAll('div.focus-button-style[style]').length > 0) { console.debug('[TINDER DEBLUR]: Removing Tinder Gold ads'); removeGoldAds(); console.debug('[TINDER DEBLUR]: Checking filters'); updateUserFiltering(); console.debug('[TINDER DEBLUR]: Deblurring likes'); await unblur(); } console.debug('[TINDER DEBLUR]: Updating user infos'); updateUserInfos(); } else { // clear the cache when not on likes page anymore cache.clear(); if (location.pathname.startsWith('/app/messages/')) { console.debug('[TINDER DEBLUR]: Updating message infos'); updateMessageInfos(location.pathname.substring(location.pathname.lastIndexOf('/') + 1)); } } // loop based observer (every 4s) setTimeout(pageCheckCallback, 4_000); }; pageCheckCallback(); } main().catch(console.error);