Foodmandu Scraper

A usercript to scrape Food Menu from Foodmandu!

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Foodmandu Scraper
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  A usercript to scrape Food Menu from Foodmandu!
// @author       kaley-bhai
// @match        https://foodmandu.com/Restaurant/Details/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=foodmandu.com
// @grant        none
// @require      https://code.jquery.com/jquery-3.6.4.min.js
// @license MIT
// ==/UserScript==

// This Userscript requires jquery to run, If you wanna run it as a Node JS application, you can use Cheerio

(function () {
  ("use strict");

  console.log("Foodmandu Scraper Script has started!");

  $(document).ready(function () {
    /** @type {Array} */
    const categoryItems = [];
    let dataDownloaded = false;
    /** @type {String} */
    const title = $("h1.text-white").text().trim().replace(/ /g, "_");
    /**
     * DOM Mutation Observer, With Angular and React,
     * Dynamic Content might be added after the page load/scroll, this tracks that
     *
     */
    observer = new MutationObserver(function (mutations) {
      clearTimeout(window.mutationTimeout);

      // Set a new timeout to wait for changes to settle
      window.mutationTimeout = setTimeout(function () {
        console.log("DOM changes settled");

        $(".list-header").each((index, element) => {
          const category = $(element).find("div:first-child").text().trim();

          $(element)
            .next("ul")
            .find("li")
            .each((i, e) => {
              const name = $(e).find(".small-title.ng-binding").text().trim();
              const price = $(e)
                .find(".menu__price span.ng-binding:not(.discount)")
                .text()
                .trim();
              const description = $(e).find(".small.dim").text().trim();

              categoryItems.push({
                category,
                name,
                description,
                price,
              });
            });
        });
        if (!dataDownloaded) {
          saveCategoryItems(categoryItems, title);
          dataDownloaded = true;
        }
        //downloadCSV(convertToCSV(categoryItems), 'foodmandu')
      }, 500);
    });
    //Log all the Menu Items
    console.log(categoryItems);

    // Observe changes to the entire document body
    observer.observe(document.body, { subtree: true, childList: true });
  });

  /**
   * Download the menu file to JSON
   *
   * @param {Array} data Menu Item Data to download
   * @param {String} name Name of the File to be saved as
   */
  function saveCategoryItems(data, name) {
    const jsonData = JSON.stringify(data, null, 2);
    const blob = new Blob([jsonData], { type: "application/json" });

    const a = document.createElement("a");
    a.href = URL.createObjectURL(blob);
    a.download = name + ".json";
    a.textContent = "Download Data";
    //Simulating a click to start download
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
  }

  /**
   * Download the menu file to CSV
   * Currently Bug with the Key-Value Spacing
   *
   * @param {*} data
   */
  function saveCategoryItemsCSV(data) {
    let csvContent =
      "data:text/csv;charset=utf-8," + "Category,Name,Description,Price\n";

    // Convert data to CSV format // Concating err when desc is empty
    data.forEach((item) => {
      csvContent += `${item.category},${item.name},${item.description},${item.price}\n`;
    });

    const encodedUri = encodeURI(csvContent);

    const a = document.createElement("a");
    a.href = encodedUri;
    a.download = "foodmandu_data.csv";
    a.textContent = "Download Data";

    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
  }

  /**
   * Function to convert the given Arrray of KV pair to CSV Format
   *
   * @param {Array} arr
   * @return {*}
   */
  function convertToCSV(arr) {
    const array = [Object.keys(arr[0])].concat(arr);

    return array
      .map((it) => {
        return Object.values(it).toString();
      })
      .join("\n");
  }
  /**
   * Function to download CVS, different from the fn: saveCategoryItemsCSV
   *
   * @param {*} data
   * @param {*} filename
   */
  function downloadCSV(data, filename) {
    const csvContent = convertToCSV(data);
    const blob = new Blob([csvContent], { type: "text/csv" });

    const a = document.createElement("a");
    a.href = URL.createObjectURL(blob);
    a.download = filename;

    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
  }
})();