packages feed

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

{-# LANGUAGE OverloadedStrings, UnicodeSyntax #-}

module Data.IndieWeb.MicroformatsToAtom (
  feedToAtom
, atomForEntry
, asForEntry
, el
, elA
, atom
, empty
) where

import           Control.Lens hiding (element)
import           Control.Monad
import           Control.Applicative ((<|>))
import           Data.Maybe
import           Data.Text as T (Text, isPrefixOf, drop)
import           Data.List (sortOn)
import           Data.Aeson
import           Data.Aeson.Lens
import           Text.XML.Writer
import           Text.XML
import           Data.IndieWeb.MicroformatsUtil

atom ∷ Text → Name
atom x = Name x (Just "http://www.w3.org/2005/Atom") Nothing

el ∷ ToXML a ⇒ Text → a → XML
el x = element (atom x)

elA ∷ ToXML a ⇒ Text → [(Name, Text)] → a → XML
elA x = elementA (atom x)

firstStr ∷ AsValue s ⇒ Text → s → Maybe Text
firstStr k = (^? key "properties" . key k . nth 0 . _String)

prop ∷ (t → XML) → Maybe t → XML
prop f (Just x) = f x
prop _ _ = empty


-- | Converts a givent Microformats 2 parse to an Atom feed.
feedToAtom ∷ XML → Value → Document
feedToAtom metaXml mfRoot =
  document (atom "feed") $ do
    metaXml

    let isAuthorOfFeed (_, p:_) = isMf "h-feed" p
        isAuthorOfFeed _ = False
        author = take 1 $ reverse $ sortOn isAuthorOfFeed $ fromMaybe [] $ allMicroformatsOfType "h-card" mfRoot
    forM_ author $ \(card, _) → do
      atomForCard card
      prop (el "logo" . content) $ firstStr "photo" card

    let entries = fromMaybe [] $ allMicroformatsOfType "h-entry" mfRoot
    forM_ entries $ \(entry, path) →
      el "entry" $ atomForEntry entry


atomForCard ∷ Value → XML
atomForCard card = el "author" $ do
  prop (el "name" . content) $ firstStr "name" card
  prop (el "uri" . content) $ firstStr "url" card
  prop (el "email" . content) $ (\x → if "mailto:" `T.isPrefixOf` x then T.drop 7 x else x) <$> firstStr "email" card
  prop (\u → elA "link" [("rel", "alternate"), ("type", "text/html"), ("href", u)] empty) $ firstStr "url" card
  prop (\u → elA "link" [("rel", "avatar"), ("href", u)] empty) $ firstStr "photo" card
  asType "person"

atomForEntry ∷ Value → XML
atomForEntry entry = do
  prop (el "id" . content) $ firstStr "uid" entry <|> firstStr "url" entry
  el "title" $ content $ if detectEntryType entry == Note then "" else (fromMaybe "" $ firstStr "name" entry)
  prop (\u → elA "link" [("rel", "alternate"), ("type", "text/html"), ("href", u)] empty) $ firstStr "url" entry
  prop (el "published" . content) $ firstStr "published" entry
  prop (el "updated" . content) $ firstStr "updated" entry
  prop (elA "content" [("type", "html")] . content) $ entry ^? key "properties" . key "content" . nth 0 . key "html" . _String
  mapM_ atomForCard (entry ^.. key "properties" . key "author")
  asForEntry entry

asType, asVerb ∷ Text → XML
asType x = element (Name "object-type" (Just "http://activitystrea.ms/spec/1.0/") Nothing) $ content $ "http://activitystrea.ms/schema/1.0/" <> x
asVerb x = element (Name "verb" (Just "http://activitystrea.ms/spec/1.0/") Nothing) $ content x


thrInReplyTo ∷ Text → XML
thrInReplyTo x = elementA (Name "in-reply-to" (Just "http://purl.org/syndication/thread/1.0") Nothing) [("ref", x), ("href", x)] empty

asForEntry ∷ Value → XML
asForEntry entry = asForEntry' (detectEntryType entry) entry
  where asForEntry' Like e = asVerb "like" >> nest e "like-of"
        asForEntry' RSVP e = fromMaybe empty (asVerb . ("rsvp-" <>) <$> e ^? key "properties" . key "rsvp" . nth 0 . _String) >> asType "event"
        asForEntry' Checkin _ = asVerb "checkin" >> asType "place"
        asForEntry' Repost e = asVerb "share" >> nest e "repost-of"
        asForEntry' Reply e = asVerb "post" >> asType "comment" >> mapM_ thrInReplyTo (refUrls e "in-reply-to")
        asForEntry' Bookmark _ = asVerb "post" >> asType "bookmark"
        asForEntry' Article _ = asVerb "post" >> asType "article"
        asForEntry' _ _ = asVerb "post" >> asType "note"
        refUrls e k = e ^.. key "properties" . key k . values . key "properties" . key "url" . values . _String
        nest e k = forM_ (e ^.. key "properties" . key k . values) $ \ee → do
          asType "activity"
          element (Name "object" (Just "http://activitystrea.ms/spec/1.0/") Nothing) $ atomForEntry ee