packages feed

indieweb-algorithms-0.1.1: library/Data/IndieWeb/Authorship.hs

{-# LANGUAGE OverloadedStrings, UnicodeSyntax, CPP, FlexibleContexts #-}

module Data.IndieWeb.Authorship where

#if !MIN_VERSION_base(4,8,0)
import           Control.Applicative
#endif
import           Control.Monad
import           Control.Monad.Trans.Maybe
import           Control.Lens
import qualified Data.Text as T
import qualified Data.Vector as V
import           Data.Foldable (asum, find)
import           Data.Maybe
import           Data.Aeson
import           Data.Aeson.Lens
import           Data.Microformats2.Parser
import           Data.IndieWeb.MicroformatsUtil
import           Text.XML (Document)
import           Network.URI
import           Safe (headMay)

-- | Finds the authors of an h-entry, discovering its authorship <http://indiewebcamp.com/authorship> using the HTTP fetcher function from arguments.
entryAuthors ∷ Monad μ ⇒ Mf2ParserSettings
                       → (URI → μ (Maybe Document)) -- ^ the URI fetcher function
                       → URI -- ^ the URI of the page the entry was extracted from
                       → Value -- ^ the full Microformats 2 parse of the page as extracted by 'Data.Microformats2.Parser.parseMf2'
                       → (Value, [Value]) -- ^ (the h-entry, [parent microformats]) as extracted by 'Data.IndieWeb.MicroformatsUtil.allMicroformatsOfType'
                       → μ (Maybe [Value])
entryAuthors mfSettings fetch entryUri mfRoot (entry, parents) = runMaybeT $ asum $ map MaybeT [ embeddedCards, relCards ]
  where fetchIfLink v
          | isMf "h-card" v = return $ Just v
          | otherwise = case (T.unpack <$> v ^? _String) >>= parseURIReference of
                          Nothing → return $ Just v
                          Just uri → cardFromUri uri
        embeddedCards = case asum [ entryAuthor, feedAuthor ] of
                          Nothing → return Nothing
                          Just authors → liftM (Just . catMaybes) $ mapM fetchIfLink authors
        relCards = case V.toList <$> mfRoot ^? key "rels" . key "author" . _Array of
                     Nothing → return Nothing
                     Just rels → liftM (Just . catMaybes) $ mapM fetchIfLink rels
        entryAuthor = getAuthorProp entry
        feedAuthor = getAuthorProp =<< (headMay $ filter (isMf "h-feed") parents)
        getAuthorProp = (V.toList <$>) . (^? key "properties" . key "author" . _Array)
        cardFromUri uri = do
          -- TODO: only allow http(s)
          let uri' = uri `relativeTo` entryUri
          html ← fetch uri'
          let mfSettings' = mfSettings { baseUri = Just uri' }
          return $ representativeCard uri' =<< parseMf2 mfSettings' <$> documentRoot <$> html

representativeCard ∷ URI → Value → Maybe Value
representativeCard uri mfRoot = asum [ matchingUidUrl, matchingRelMeUrl, matchingOnlyUrl ]
  where matchingUidUrl = find (\c → any (== uri) (uris "url" c) && any (== uri) (uris "uid" c)) cards
        matchingRelMeUrl = find (any (`elem` relMeUris) . uris "url") cards
        matchingOnlyUrl = if length cards /= 1 then Nothing else find (any (== uri) . uris "url") cards
        relMeUris = mapMaybe (parseURI . T.unpack) $ mfRoot ^.. key "rels" . key "me" . values . _String
        uris k c  = mapMaybe (parseURI . T.unpack) $ c ^.. key "properties" . key k . values . _String
        cards = fmap fst $ fromMaybe [] $ allMicroformatsOfType "h-card" mfRoot