packages feed

chez-grater-0.0.2: src/Chez/Grater/Scraper.hs

module Chez.Grater.Scraper where

import Chez.Grater.Internal.Prelude

import Chez.Grater.Scraper.Types
  ( IngredientScraper(..), ScrapeError(..), ScrapeMetaWrapper(..), ScrapedRecipeName(..)
  , Scrapers(..), SiteName(..), StepScraper(..), ScrapedIngredient, ScrapedStep, title
  )
import Network.HTTP.Client (Manager)
import Network.URI (URI, uriAuthority, uriRegName)
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Text as Text
import qualified Text.HTML.Scalpel as Scalpel

scrape
  :: (ScrapedRecipeName -> a)
  -> ([ScrapedIngredient] -> Either Text [b])
  -> ([ScrapedStep] -> Either Text [c])
  -> Scrapers -> Manager -> URI -> IO (a, [b], [c], ScrapeMetaWrapper)
scrape mkName runIngredientParser runStepParser scrapers manager uri = do
  let cfg = Scalpel.Config Scalpel.defaultDecoder (Just manager)
  tags <- Scalpel.fetchTagsWithConfig cfg (show uri)
  let domainMay = SiteName . Text.replace "www." "" . Text.pack . uriRegName <$> uriAuthority uri
      name = fromMaybe (ScrapedRecipeName "Untitled") $ Scalpel.scrape title tags

      runScraper :: forall a b. ([a] -> Either Text [b]) -> Scalpel.Scraper Text [a] -> Maybe [b]
      runScraper parser scraper = either (const Nothing) Just . parser =<< Scalpel.scrape scraper tags

      goIngredient IngredientScraper {..} = case Scalpel.scrape ingredientScraperTest tags of
        Just True -> (,ingredientScraperMeta) <$> runScraper runIngredientParser ingredientScraperRun
        _ -> Nothing
      goStep StepScraper {..} = case Scalpel.scrape stepScraperTest tags of
        Just True -> (,stepScraperMeta) <$> runScraper runStepParser stepScraperRun
        _ -> Nothing

  (ingredients, ingredientMeta) <- case flip HashMap.lookup (scrapersIngredientBySite scrapers) =<< domainMay of
    Just IngredientScraper {..} -> maybe (throwIO $ ScrapeError "Failed to scrape known URL") (pure . (,ingredientScraperMeta)) $
      runScraper runIngredientParser ingredientScraperRun
    Nothing -> maybe (throwIO $ ScrapeError "Failed to scrape URL from defaults") pure
     . lastMay
     . sortOn (length . fst)
     . mapMaybe goIngredient
     . scrapersIngredients
     $ scrapers
  stepsMay <- case flip HashMap.lookup (scrapersStepBySite scrapers) =<< domainMay of
    Just StepScraper {..} -> pure . fmap (,stepScraperMeta) . runScraper runStepParser $ stepScraperRun
    Nothing -> pure
      . lastMay
      . sortOn (length . fst)
      . mapMaybe goStep
      . scrapersSites
      $ scrapers
  case stepsMay of
    Just (steps, stepMeta) | not (null steps) -> pure (mkName name, ingredients, steps, ScrapeMetaWrapperIngredientAndStep ingredientMeta stepMeta)
    _ -> pure (mkName name, ingredients, [], ScrapeMetaWrapperIngredient ingredientMeta)