Swiggy & Zomato: Non Veg dishes only

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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/[email protected]/umd/string-similarity.min.js
// @resource     thumbUp https://cdn.jsdelivr.net/npm/[email protected]/color/svg/1F44D.svg
// @resource     thumbDown https://cdn.jsdelivr.net/npm/[email protected]/color/svg/1F44E.svg
// @resource     star https://cdn.jsdelivr.net/npm/[email protected]/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)
  }
})()