NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Swiggy & Zomato: Non Veg dishes only // @namespace http://tampermonkey.net/ // @version 1.2.5 // @description On Swiggy and Zomato you can select to show vegetarian dishes only, this script does the reverse: it allows you to hide vegetarian dishes. Rate individual dishes and keep a private history of what you like and what you hated // @author cuzi // @copyright 2021, cuzi (https://openuserjs.org/users/cuzi) // @license GPL-3.0-or-later // @match https://www.swiggy.com/* // @match https://www.zomato.com/* // @icon https://res.cloudinary.com/swiggy/image/upload/portal/c/icon-192x192.png // @grant GM.getValue // @grant GM.setValue // @grant GM_getResourceText // @require https://cdn.jsdelivr.net/npm/string-similarity@4.0.4/umd/string-similarity.min.js // @resource thumbUp https://cdn.jsdelivr.net/npm/openmoji@14.0.0/color/svg/1F44D.svg // @resource thumbDown https://cdn.jsdelivr.net/npm/openmoji@14.0.0/color/svg/1F44E.svg // @resource star https://cdn.jsdelivr.net/npm/openmoji@14.0.0/color/svg/2B50.svg // ==/UserScript== /* Copyright (C) 2021, cuzi (https://openuserjs.org/users/cuzi) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ /* globals Node, GM, GM_getResourceText, stringSimilarity */ (function () { 'use strict' const DEFAULT_DATA = '{"restaurants": {}}' function timeSince (date) { // https://stackoverflow.com/a/72973090/ const MINUTE = 60 const HOUR = MINUTE * 60 const DAY = HOUR * 24 const WEEK = DAY * 7 const MONTH = DAY * 30 const YEAR = DAY * 365 const secondsAgo = Math.round((Date.now() - Number(date)) / 1000) if (secondsAgo < MINUTE) { return secondsAgo + ` second${secondsAgo !== 1 ? 's' : ''} ago` } let divisor let unit = '' if (secondsAgo < HOUR) { [divisor, unit] = [MINUTE, 'minute'] } else if (secondsAgo < DAY) { [divisor, unit] = [HOUR, 'hour'] } else if (secondsAgo < WEEK) { [divisor, unit] = [DAY, 'day'] } else if (secondsAgo < MONTH) { [divisor, unit] = [WEEK, 'week'] } else if (secondsAgo < YEAR) { [divisor, unit] = [MONTH, 'month'] } else { [divisor, unit] = [YEAR, 'year'] } const count = Math.floor(secondsAgo / divisor) return `${count} ${unit}${count > 1 ? 's' : ''} ago` } function symmetricDifference (setA, setB) { const _difference = new Set(setA) for (const elem of setB) { if (_difference.has(elem)) { _difference.delete(elem) } else { _difference.add(elem) } } return _difference } function compareNames (s0, s1) { let r = 0 s0 = s0.toLowerCase().trim() s1 = s1.toLowerCase().trim() if (s0 === s1) { return 2 } const set0 = new Set(s0.split(/\s+/)) const set1 = new Set(s1.split(/\s+/)) r -= symmetricDifference(set0, set1).size if (r < 0) { r += stringSimilarity.compareTwoStrings(s0, s1) } return r } function getThumbs (onUpClick, onDownClick) { const thumbs = document.createElement('div') thumbs.classList.add('thumbscontainer') const thumbUpSVG = document.createElement('div') thumbUpSVG.style.width = '40px' thumbUpSVG.style.height = '40px' thumbUpSVG.style.float = 'left' thumbUpSVG.style.cursor = 'pointer' thumbUpSVG.innerHTML = GM_getResourceText('thumbUp').replace('id="emoji"', 'id="thumbUp' + Math.random() + '"') thumbUpSVG.querySelector('#skin polygon').setAttribute('fill', '#cccccc') thumbUpSVG.addEventListener('click', onUpClick) thumbs.appendChild(thumbUpSVG) const thumbDownSVG = document.createElement('div') thumbDownSVG.style.width = '40px' thumbDownSVG.style.height = '40px' thumbDownSVG.style.float = 'left' thumbDownSVG.style.cursor = 'pointer' thumbDownSVG.innerHTML = GM_getResourceText('thumbDown').replace('id="emoji"', 'id="thumbDown' + Math.random() + '"') thumbDownSVG.querySelector('#skin polygon').setAttribute('fill', '#cccccc') thumbDownSVG.addEventListener('click', onDownClick) thumbs.appendChild(thumbDownSVG) thumbs.appendChild(document.createElement('div')).style.clear = 'left' return [thumbs, thumbUpSVG, thumbDownSVG] } function clearAllRatings () { const promises = [] for (const gmKey of ['swiggy', 'zomato']) { promises.push(GM.setValue(gmKey, DEFAULT_DATA)) } Promise.all(promises).then(() => { window.alert('All ratings cleared\n\nReload the page to see the changes') document.location.reload() }) } async function clearRestaurantRatings (node) { const gmKey = node.dataset.gmKey const restaurantId = node.dataset.restaurantId const restaurantName = node.dataset.restaurantName if (!gmKey || !restaurantId) { return false } if (!window.confirm('Clear all ratings for this restaurant?\n\n' + restaurantName + '\n\nThis cannot be undone!')) { return false } const data = JSON.parse(await GM.getValue(gmKey, DEFAULT_DATA)) if ((restaurantId in data.restaurants)) { delete data.restaurants[restaurantId] } await GM.setValue(gmKey, JSON.stringify(data)) return true } async function listRatings (mGmKey, selectedRestaurantId) { const showRestaurantDishes = function (data, listDiv, restaurantId, gmKey) { const info = data.restaurants[restaurantId].info const dishes = data.restaurants[restaurantId].dishes if (!dishes) { return } const restaDiv = listDiv.appendChild(document.createElement('div')) restaDiv.classList.add('restaurant_container') const metaDiv = restaDiv.appendChild(document.createElement('div')) metaDiv.classList.add('ratings_meta') const ra = metaDiv.appendChild(document.createElement('a')) ra.href = info.url const label = 'name' in info ? info.name : info.url ra.appendChild(document.createTextNode(label)) if ('location' in info && info.location && info.location.trim()) { const span = metaDiv.appendChild(document.createElement('span')) span.appendChild(document.createTextNode(` (${info.location})`)) } const lastOverallRatingSpan = metaDiv.appendChild(document.createElement('span')) const clearButton = metaDiv.appendChild(document.createElement('button')) clearButton.style.fontSize = 'small' clearButton.style.marginLeft = '3px' clearButton.addEventListener('click', function () { clearRestaurantRatings(this).then(function (cleared) { if (cleared) { document.location.reload() } }) }) clearButton.dataset.restaurantId = restaurantId clearButton.dataset.gmKey = gmKey clearButton.dataset.restaurantName = label clearButton.appendChild(document.createTextNode('Clear')) const listDivUp = restaDiv.appendChild(document.createElement('div')) const listDivDown = restaDiv.appendChild(document.createElement('div')) listDivUp.classList.add('ratings_list', 'up') listDivDown.classList.add('ratings_list', 'down') restaDiv.appendChild(document.createElement('div')).style.clear = 'left' let lastRating = null for (const dishName in dishes) { const dish = dishes[dishName] const div = dish.rating > 0 ? listDivUp : listDivDown const le = div.appendChild(document.createElement('div')) le.classList.add('ratings_item') le.appendChild(document.createTextNode(dishName)) if ('price' in dish && dish.price) { le.appendChild(document.createTextNode(` ₹${dish.price}`)) } if ('veg' in dish && dish.veg) { const span = le.appendChild(document.createElement('span')) if (dish.veg === 'veg') { span.classList.add('veggy_icon') span.appendChild(document.createTextNode('\u23FA')) } else { span.classList.add('nonveggy_icon') span.appendChild(document.createTextNode('\u2BC5')) } } const date = new Date(dish.lastRating) const dateStr = 'Rated: ' + date.toLocaleDateString() + ' ' + timeSince(date) le.setAttribute('title', dateStr) if (lastRating == null || date > lastRating) { lastRating = date } } if (lastRating) { const dateStr = ' ' + lastRating.toLocaleDateString() + ' ' + timeSince(lastRating) lastOverallRatingSpan.appendChild(document.createTextNode(dateStr)) } } let listDiv = document.getElementById('ratings_container') if (!listDiv) { createMainContainer(mGmKey, selectedRestaurantId) listDiv = document.getElementById('ratings_container') } listDiv.innerHTML = '' for (const gmKey of ['swiggy', 'zomato']) { const data = JSON.parse(await GM.getValue(gmKey, DEFAULT_DATA)) if (selectedRestaurantId && selectedRestaurantId in data.restaurants) { // Show current restaurant first showRestaurantDishes(data, listDiv, selectedRestaurantId, gmKey) } for (const restaurantId in data.restaurants) { if (!selectedRestaurantId || selectedRestaurantId !== restaurantId) { showRestaurantDishes(data, listDiv, restaurantId, gmKey) } } } } function crossCheckNames (name, data) { const results = [] for (const restaurantId in data.restaurants) { if (!('name' in data.restaurants[restaurantId].info)) { continue } const r = compareNames(data.restaurants[restaurantId].info.name, name) if (r > -2) { results.push([r, data.restaurants[restaurantId]]) } } return results.sort((a, b) => b[0] - a[0]).map(v => v[1]) } async function crossCheck (restaurantId, restaurantInfo, gmKey) { if (!('name' in restaurantInfo) || !restaurantInfo.name) { return } const data = JSON.parse(await GM.getValue(gmKey === 'swiggy' ? 'zomato' : 'swiggy', DEFAULT_DATA)) const results = crossCheckNames(restaurantInfo.name, data) showCrossCheckResults(gmKey, restaurantId, results) } function showCrossCheckResultsWide () { document.getElementById('cross_check_results').classList.add('fullscreen') try { this.remove() } catch (e) {} document.head.appendChild(document.createElement('style')).innerHTML = ` #cross_check_results.fullscreen { top: 5px; right:5px; height: 95%; width: 95%; max-width: 95%; max-height: 95%; } #cross_check_results.fullscreen .ratings_list { width:45%; float:left; } ` } function showCrossCheckResults (gmKey, restaurantId, results) { if (!results.length) { return } const div = createMainContainer(gmKey, restaurantId) const resultsHead = div.appendChild(document.createElement('div')) resultsHead.appendChild(document.createTextNode('Similar named restaurants you voted on ' + (gmKey === 'swiggy' ? 'Zomato' : 'Swiggy'))) resultsHead.style.fontWeight = 'bold' const resultsDiv = div.appendChild(document.createElement('div')) results.forEach(function (restaurant, i) { const restaurantDiv = resultsDiv.appendChild(document.createElement('div')) if (i % 2 === 0) { restaurantDiv.style.backgroundColor = '#ddd' } const restaurantName = restaurantDiv.appendChild(document.createElement('div')) restaurantName.appendChild(document.createTextNode(restaurant.info.name)) const restaurantLoc = restaurantDiv.appendChild(document.createElement('div')) restaurantLoc.appendChild(document.createTextNode(restaurant.info.location || '')) restaurantLoc.style.fontSize = '10pt' const restaurantLink = restaurantDiv.appendChild(document.createElement('a')) restaurantLink.appendChild(document.createTextNode(restaurant.info.url)) restaurantLink.setAttribute('href', restaurant.info.url) restaurantLink.style.fontSize = '7pt' }) } function createMainContainer (gmKey = 'swiggy', restaurantId = null, clear = false) { let div = document.getElementById('cross_check_results') if (!div) { div = document.body.appendChild(document.createElement('div')) div.setAttribute('id', 'cross_check_results') document.head.appendChild(document.createElement('style')).innerHTML = ` #cross_check_results { z-index:1200; position:fixed; top: 100px; right:5px; max-height: 70%; max-width: 20%; overflow: auto; border:2px solid #223075; background:white; font-size:12pt } #cross_check_results button { border: 1px solid #777; border-radius: 4px; background: #e0e0e0; } #cross_check_results button:hover { border: 1px solid #000; border-radius: 4px; background: #f0f0f0; } #cross_check_results a:link { text-decoration:underline; color:#06c; } #cross_check_results a:visited { text-decoration:underline; color:#06c; } #cross_check_results .restaurant_container { border-bottom: 2px solid #848484; } #cross_check_results .ratings_meta { background-color:#f4e9bc; background-image: linear-gradient(to right, white , #f4e9bc); margin-top:3px; } #cross_check_results .ratings_list { float:left; margin: 2px; } #cross_check_results .ratings_list.up { background-color:#e6ffe6; } #cross_check_results .ratings_list.down { background-color:#fbd5d5; margin-left: 5px; } #cross_check_results .ratings_item:nth-child(2n+2){ background-color:#0000000f; } #cross_check_results .veggy_icon { color: #0f8a65; border: 2px solid #0f8a65; font-size: 8px; height: 13px; display: inline-block; font-weight: 1000; width: 12px; vertical-align: middle; margin: 1px; } #cross_check_results .nonveggy_icon { color: #e43b4f; border: 2px solid #e43b4f; font-size: 8px; height: 13px; display: inline-block; font-weight: 1000; width: 12px; vertical-align: middle; margin: 1px; } #cross_check_results .ratings_meta span { color: #555; font-size: 10pt; } ` const controlsDiv = div.appendChild(document.createElement('div')) controlsDiv.setAttribute('id', 'controls_container') const closeButton = controlsDiv.appendChild(document.createElement('button')) closeButton.appendChild(document.createTextNode('Close')) closeButton.addEventListener('click', function () { removeMainContainer() showCrossCheckResults(gmKey, restaurantId, []) }) const clearButton = controlsDiv.appendChild(document.createElement('button')) clearButton.appendChild(document.createTextNode('Clear all')) clearButton.addEventListener('click', function () { if (window.confirm('Delete ratings for all restaurants?') && window.confirm('Delete ratings for ALL restaurants?\n\nAre you sure?')) { clearAllRatings() } }) const fullscreenButton = controlsDiv.appendChild(document.createElement('button')) fullscreenButton.appendChild(document.createTextNode('\u27F7')) fullscreenButton.addEventListener('click', showCrossCheckResultsWide) const listDiv = div.appendChild(document.createElement('div')) listDiv.setAttribute('id', 'ratings_container') const listButton = listDiv.appendChild(document.createElement('button')) listButton.appendChild(document.createTextNode('View ratings')) listButton.addEventListener('click', () => listRatings(gmKey, restaurantId)) } if (clear) { div.classList.remove('fullscreen') div.innerHTML = '' } div.style.display = 'block' return div } function removeMainContainer () { const div = document.getElementById('cross_check_results') if (div) { div.remove() } } if (document.location.hostname.endsWith('.swiggy.com')) { let crossCheckDone = false const getRestaurantInfo = function () { const results = {} const h1 = document.querySelector('[class*="RestaurantNameAddress_name"]') if (h1) { results.name = h1.textContent.trim() } try { results.location = document.querySelector('[class*="RestaurantNameAddress_area"]').textContent.trim() } catch (e) { console.log(e) } return results } const addRatingsButton = function () { if (document.getElementById('nav_rating_button')) { return } if (document.querySelector('.global-nav a[href*="/support"]')) { const orgLi = document.querySelector('.global-nav a[href*="/support"]').parentNode.parentNode const li = orgLi.cloneNode(true) orgLi.parentNode.appendChild(li) li.setAttribute('id', 'nav_rating_button') li.addEventListener('click', function (ev) { ev.preventDefault() listRatings('swiggy', null) }) li.querySelector('a').href = '#' const svg = li.querySelector('svg') const span = svg.parentNode span.parentNode.replaceChild(document.createTextNode('Ratings'), span.nextSibling) const starSVG = document.createElement('div') starSVG.style.width = '22px' starSVG.style.height = '22px' starSVG.style.cursor = 'pointer' starSVG.innerHTML = GM_getResourceText('star').replace('id="emoji"', 'id="starSVG' + Math.random() + '"') starSVG.querySelector('#color polygon').setAttribute('fill', '#ffffff') starSVG.querySelector('#line polygon').setAttribute('stroke', '#3d4152') starSVG.querySelector('#line polygon').setAttribute('stroke-width', '6') span.replaceChild(starSVG, svg) } else if (!document.getElementById('cross_check_results')) { createMainContainer('swiggy', null) } } const addRatings = async function () { const m = document.location.pathname.match(/\/restaurants\/[\w-]+-(\d+)/) if (!m) { return } const restaurantId = m[1] let data = JSON.parse(await GM.getValue('swiggy', DEFAULT_DATA)) if (!(restaurantId in data.restaurants)) { data.restaurants[restaurantId] = { dishes: {}, info: { id: restaurantId, url: document.location.href } } } if (!crossCheckDone) { crossCheckDone = true crossCheck(restaurantId, getRestaurantInfo(), 'swiggy') } document.querySelectorAll('[data-testid*="dish-item"]').forEach(function (menuItem) { if ('userscriptprocessed' in menuItem.dataset) { return } menuItem.dataset.userscriptprocessed = 1 const dishName = menuItem.querySelector('[class*=itemNameText]').textContent.trim() const saveRating = async function (rating) { let price = null try { price = parseInt(menuItem.querySelector('.rupee').textContent.trim()) } catch (e) { console.log(e) } let veg = null const icon = menuItem.querySelector('[class*=styles_icon]') if (icon && icon.className.match(/icon-?([a-z]+)/i)) { veg = icon.className.match(/icon-?([a-z]+)/i)[1].toLowerCase() // "veg", "nonveg" } data = JSON.parse(await GM.getValue('swiggy', DEFAULT_DATA)) if (!(restaurantId in data.restaurants)) { data.restaurants[restaurantId] = { dishes: {}, info: { id: restaurantId, url: document.location.href } } } if (!(dishName in data.restaurants[restaurantId].dishes)) { data.restaurants[restaurantId].dishes[dishName] = { name: dishName, price, veg, lastRating: new Date().toJSON().toString() } } data.restaurants[restaurantId].dishes[dishName].rating = rating data.restaurants[restaurantId].info = Object.assign(data.restaurants[restaurantId].info, getRestaurantInfo()) await GM.setValue('swiggy', JSON.stringify(data)) } const onUp = function () { saveRating(1).then(function () { thumbUp.querySelector('#skin polygon').setAttribute('fill', '#50c020') thumbDown.querySelector('#skin polygon').setAttribute('fill', '#cccccc') }) } const onDown = function () { saveRating(-1).then(function () { thumbUp.querySelector('#skin polygon').setAttribute('fill', '#cccccc') thumbDown.querySelector('#skin polygon').setAttribute('fill', '#e60000') }) } const [thumbs, thumbUp, thumbDown] = getThumbs(onUp, onDown) const parentContainer = menuItem.querySelector('[class*=itemImageContainer]') thumbs.style.position = 'relative' thumbs.style.zIndex = 1 if (parentContainer.className.indexOf('NoImage') === -1) { thumbs.style.marginTop = '20pt' } parentContainer.appendChild(thumbs) if (dishName in data.restaurants[restaurantId].dishes) { if (data.restaurants[restaurantId].dishes[dishName].rating > 0) { thumbUp.querySelector('#skin polygon').setAttribute('fill', '#50c020') } else if (data.restaurants[restaurantId].dishes[dishName].rating < 0) { thumbDown.querySelector('#skin polygon').setAttribute('fill', '#e60000') } const dateDiv = thumbs.appendChild(document.createElement('div')) const date = new Date(data.restaurants[restaurantId].dishes[dishName].lastRating) const dateStr = date.toLocaleDateString() + ' ' + timeSince(date) dateDiv.style.fontSize = '10px' dateDiv.appendChild(document.createTextNode(dateStr)) } }) } const addNonVegToggle = function () { let orgDiv let newDiv let isActive const orgClick = function () { if (isActive) { console.debug('orgClick: already non-veg, reset it') resetNonVeg() } } const resetNonVeg = function () { document.querySelectorAll('.hiddenbyscript').forEach(function (menuItem) { menuItem.classList.remove('hiddenbyscript') menuItem.style.display = '' }) isActive = false newDiv.querySelector('[class*="ToggleSwitch_toggleBar"]').style.backgroundColor = '' newDiv.querySelector('[class*="ToggleSwitch_toggleThumb"]').style.backgroundColor = '' newDiv.querySelector('[class*="ToggleSwitch_toggleThumb"]').style.transform = '' } const enableNonVeg = function (ev) { if (ev) { ev.preventDefault() ev.stopPropagation() } if (isActive) { console.debug('enableNonVeg: already non-veg, reset it') window.setTimeout(resetNonVeg, 100) return } if (orgDiv.querySelector('[class*="toggleThumbActive"]')) { console.debug('enableNonVeg: org checkbox is checked, click it and wait') orgDiv.querySelector('button').click() window.setTimeout(enableNonVeg, 500) newDiv.querySelector('[class*="ToggleSwitch_toggleBar"]').style.backgroundColor = '#87d' return } console.debug('enableNonVeg: hide menu items') document.querySelectorAll('[data-testid*="dish-item"]').forEach(function (menuItem) { const icon = menuItem.querySelector('[class*=styles_icon]') if (icon && icon.className.match(/icon-?veg/i)) { menuItem.classList.add('hiddenbyscript') menuItem.style.display = 'none' } }) isActive = true newDiv.querySelector('[class*="ToggleSwitch_toggleBar"]').style.backgroundColor = '#e43b4f' newDiv.querySelector('[class*="ToggleSwitch_toggleThumb"]').style.backgroundColor = '#e43b4f' newDiv.querySelector('[class*="ToggleSwitch_toggleThumb"]').style.transform = 'translate3d(18px,0,0)' } const labels = document.querySelectorAll('[data-testid*="filter-switch"]') labels.forEach(function (label) { if (label.className.indexOf('vegOnly') !== -1) { orgDiv = label orgDiv.parentNode.style.justifyContent = 'flex-start' newDiv = orgDiv.parentNode.appendChild(label.cloneNode(true)) newDiv.style.marginLeft = '1em' newDiv.setAttribute('id', 'nonVegToggle') newDiv.querySelector('[class*="Label_"]').textContent = 'Non veg' newDiv.querySelector('button').addEventListener('click', enableNonVeg) orgDiv.querySelector('button').addEventListener('click', orgClick) } }) } window.setInterval(function () { addRatingsButton() addRatings() if (!document.getElementById('nonVegToggle')) { addNonVegToggle() } }, 1000) } else if (document.location.hostname.endsWith('.zomato.com')) { let crossCheckDone = false const getRestaurantInfo = function () { const results = {} const h1 = document.querySelector('div#root main section>div>div>div>h1') if (h1) { results.name = h1.textContent.trim() } try { results.location = h1.parentNode.nextElementSibling.firstChild.nextElementSibling.textContent.trim() } catch (e) { console.log(e) } return results } const addRatingsButton = function () { if (document.getElementById('nav_rating_button')) { return } if (document.querySelector('ul[id*=navigation]')) { const orgLi = document.querySelector('ul[id*=navigation]').querySelector('li:last-child') const li = orgLi.cloneNode(true) orgLi.parentNode.appendChild(li) li.setAttribute('id', 'nav_rating_button') li.addEventListener('click', function (ev) { ev.preventDefault() listRatings('zomato', null) }) const a = li.querySelector('a') a.innerHTML = '' a.style.fontSize = '10px' const starSVG = document.createElement('div') starSVG.style.width = '22px' starSVG.style.height = '22px' starSVG.style.cursor = 'pointer' starSVG.style.margin = 'auto' starSVG.style.marginTop = '-35px' starSVG.innerHTML = GM_getResourceText('star').replace('id="emoji"', 'id="starSVG' + Math.random() + '"') starSVG.querySelector('#color polygon').setAttribute('fill', '#EF4F5F') a.appendChild(starSVG) a.appendChild(document.createTextNode('Ratings')) } else if (!document.getElementById('cross_check_results')) { createMainContainer('zomato', null) } } const addRatings = async function () { const m = document.location.pathname.match(/([\w-]+\/[\w-]+)\/order/) if (!m) { return } const restaurantId = m[1] let data = JSON.parse(await GM.getValue('zomato', DEFAULT_DATA)) if (!(restaurantId in data.restaurants)) { data.restaurants[restaurantId] = { dishes: {}, info: { id: restaurantId, url: document.location.href } } } if (!crossCheckDone) { crossCheckDone = true crossCheck(restaurantId, getRestaurantInfo(), 'zomato') } document.querySelectorAll('[type="veg"],[type="non-veg"]').forEach(function (symbol) { const menuItem = symbol.parentNode.parentNode.parentNode if ('userscriptprocessed' in menuItem.dataset) { return } menuItem.dataset.userscriptprocessed = 1 const dishName = menuItem.querySelector('h4').textContent.trim() const saveRating = async function (rating) { let price = null try { price = parseInt(menuItem.textContent.match(/₹\s*(\d+)/)[1]) } catch (e) { console.log(e) } const veg = symbol.getAttribute('type').toLowerCase().replace('-', '') // "veg", "nonveg" data = JSON.parse(await GM.getValue('zomato', DEFAULT_DATA)) if (!(restaurantId in data.restaurants)) { data.restaurants[restaurantId] = { dishes: {}, info: { id: restaurantId, url: document.location.href } } } if (!(dishName in data.restaurants[restaurantId].dishes)) { data.restaurants[restaurantId].dishes[dishName] = { name: dishName, price, veg, // "veg", "nonveg" lastRating: new Date().toJSON().toString() } } data.restaurants[restaurantId].dishes[dishName].rating = rating data.restaurants[restaurantId].info = Object.assign(data.restaurants[restaurantId].info, getRestaurantInfo()) await GM.setValue('zomato', JSON.stringify(data)) } const onUp = function () { saveRating(1).then(function () { thumbUp.querySelector('#skin polygon').setAttribute('fill', '#50c020') thumbDown.querySelector('#skin polygon').setAttribute('fill', '#cccccc') }) } const onDown = function () { saveRating(-1).then(function () { thumbUp.querySelector('#skin polygon').setAttribute('fill', '#cccccc') thumbDown.querySelector('#skin polygon').setAttribute('fill', '#e60000') }) } const [thumbs, thumbUp, thumbDown] = getThumbs(onUp, onDown) thumbs.style.marginTop = '20pt' menuItem.firstChild.appendChild(thumbs) if (dishName in data.restaurants[restaurantId].dishes) { if (data.restaurants[restaurantId].dishes[dishName].rating > 0) { thumbUp.querySelector('#skin polygon').setAttribute('fill', '#50c020') } else if (data.restaurants[restaurantId].dishes[dishName].rating < 0) { thumbDown.querySelector('#skin polygon').setAttribute('fill', '#e60000') } const dateDiv = thumbs.appendChild(document.createElement('div')) const date = new Date(data.restaurants[restaurantId].dishes[dishName].lastRating) const dateStr = date.toLocaleDateString() + ' ' + timeSince(date) dateDiv.style.fontSize = '10px' dateDiv.appendChild(document.createTextNode(dateStr)) } }) } const addNonVegToggle = function () { let label let orgDiv let newDiv let newCheckbox const orgClick = function () { if (newCheckbox.checked) { console.debug('orgClick: already non-veg, reset it') resetNonVeg() } } const resetNonVeg = function () { document.querySelectorAll('.hiddenbyscript').forEach(function (menuItem) { menuItem.classList.remove('hiddenbyscript') menuItem.style.display = '' }) newCheckbox.checked = false newCheckbox.style.backgroundColor = '' } const enableNonVeg = function (ev) { if (ev) { ev.preventDefault() ev.stopPropagation() } newCheckbox.style.backgroundColor = '#87d' window.setTimeout(function () { if (newCheckbox.checked) { console.debug('enableNonVeg: already non-veg, reset it') window.setTimeout(resetNonVeg, 200) return } if (orgDiv.checked) { console.debug('enableNonVeg: org checkbox is checked, click it and wait') orgDiv.click() window.setTimeout(enableNonVeg, 500) return } console.debug('enableNonVeg: hide menu items') document.querySelectorAll('[type="veg"]').forEach(function (symbol) { const menuItem = symbol.parentNode.parentNode.parentNode menuItem.classList.add('hiddenbyscript') menuItem.style.display = 'none' }) newCheckbox.checked = true newCheckbox.style.backgroundColor = '' }, 100) } const labels = document.querySelectorAll('label') labels.forEach(function (l) { if (l.textContent.toLowerCase().indexOf('veg') !== -1 && l.textContent.toLowerCase().indexOf('only') !== -1) { label = l orgDiv = label newDiv = orgDiv.cloneNode(true) label.parentNode.appendChild(newDiv) label.parentNode.style.width = (label.parentNode.clientWidth + newDiv.clientWidth + 17) + 'px' newCheckbox = newDiv.querySelector('input[type=checkbox]') newCheckbox.checked = false newDiv.setAttribute('id', 'nonVegToggle') newDiv.childNodes.forEach(function (c) { if (c.nodeType === Node.TEXT_NODE && c.textContent.toLowerCase().indexOf('veg') !== -1) { c.textContent = 'Non veg' } }) newDiv.addEventListener('click', enableNonVeg) orgDiv.addEventListener('click', orgClick) } }) } window.setInterval(function () { addRatingsButton() addRatings() if (!document.getElementById('nonVegToggle')) { addNonVegToggle() } }, 1000) } })()