cuzi / Swiggy & Zomato: Non Veg dishes only

// ==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)
  }
})()