h-booru 0.1.0.0 → 0.2.0.0
raw patch · 11 files changed
+603/−337 lines, 11 filesdep +bytestringdep +http-conduitdep +utf8-stringnew-component:exe:h-booru
Dependencies added: bytestring, http-conduit, utf8-string, vinyl
Files
- h-booru.cabal +23/−4
- src/HBooru/Network.hs +67/−0
- src/HBooru/Parsers/Gelbooru.hs +49/−16
- src/HBooru/Parsers/GenericBooru.hs +0/−104
- src/HBooru/Parsers/GenericBooru/TH.hs +0/−90
- src/HBooru/Parsers/Ichijou.hs +54/−18
- src/HBooru/Parsers/Konachan.hs +67/−17
- src/HBooru/Parsers/Safebooru.hs +50/−17
- src/HBooru/Parsers/Yandere.hs +67/−17
- src/HBooru/Types.hs +189/−54
- src/Main.hs +37/−0
h-booru.cabal view
@@ -1,5 +1,5 @@ name: h-booru-version: 0.1.0.0+version: 0.2.0.0 synopsis: Haskell library for retrieving data from various booru image sites description: Haskell library for retrieving data from various booru image sites. By providing a common interface for such sites and some parsers for@@ -23,12 +23,31 @@ default-language: Haskell2010 exposed-modules: HBooru.Types+ HBooru.Network HBooru.Parsers.Gelbooru HBooru.Parsers.Ichijou HBooru.Parsers.Konachan HBooru.Parsers.Safebooru HBooru.Parsers.Yandere- HBooru.Parsers.GenericBooru- HBooru.Parsers.GenericBooru.TH- -- other-modules: build-depends: base >=4.6 && <5, hxt >= 9.3.1.2, template-haskell+ , http-conduit, utf8-string, bytestring, vinyl >= 0.4+++executable h-booru+ hs-source-dirs: src+ default-language: Haskell2010+ main-is: Main.hs+ other-modules:+ HBooru.Types+ HBooru.Network+ HBooru.Parsers.Gelbooru+ HBooru.Parsers.Ichijou+ HBooru.Parsers.Konachan+ HBooru.Parsers.Safebooru+ HBooru.Parsers.Yandere+ build-depends: base >=4.6 && <5, hxt >= 9.3.1.2, template-haskell+ , http-conduit, utf8-string, bytestring, vinyl >= 0.4++source-repository head+ type: git+ location: https://github.com/Fuuzetsu/h-booru.git
+ src/HBooru/Network.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE UnicodeSyntax #-}+-- |+-- Module : HBooru.Network+-- Copyright : (c) Mateusz Kowalczyk 2013-2014+-- License : GPL-3+--+-- Maintainer : fuuzetsu@fuuzetsu.co.uk+-- Stability : experimental+--+-- Module providing functions to interface with some booru sites.+-- Amongst other things, it should (semi-transparently) handle post count+-- limits. The user should simply be able to ask for all images with certain+-- rather than worrying about hard limits per page set by the sites &c.+module HBooru.Network where++import Control.Applicative ((<$>))+import HBooru.Types+import Network.HTTP.Conduit (simpleHttp)+import Data.ByteString.Lazy (toStrict)+import Data.ByteString.UTF8++-- | Given a 'Site', 'DataFormat' and a list of 'Tag's, naively fetch the first+-- page or so and parse it to the appropriate image type. Both the site and the+-- format need to together form an instance of 'Postable' and the data format+-- has to exist in an instance of 'CoerceResponse'. Uses 'fetchPostPage' to+-- fetch the data.+fetchTaggedPosts+ ∷ (Postable s d, CoerceResponse d r) ⇒ s → d → [Tag] → IO [ImageTy s d]+fetchTaggedPosts s d ts = parseResponse s <$> fetchPostPage s d ts++-- | As 'fetchTaggedPosts' but works with sites which allow indexing by page.+fetchTaggedPostsIndexed ∷ (CoerceResponse r a, PostablePaged s r)+ ⇒ s → r → [Tag] → Integer → IO [ImageTy s r]+fetchTaggedPostsIndexed s d ts i =+ parseResponse s <$> fetchPostPageIndexed s d ts i++-- | Given an instance of 'Postable', 'CoerceResponse', and a list of 'Tag's,+-- fetch the post page.+fetchPostPage ∷ (Postable s d, CoerceResponse d r) ⇒ s → d → [Tag] → IO r+fetchPostPage s d ts = fetchResponse (postUrl s d ts) d++-- | Given an instance of 'Postable', 'CoerceResponse', and a list of 'Tag's,+-- fetch the post page.+fetchPostPageIndexed ∷ (PostablePaged s d, CoerceResponse d r)+ ⇒ s → d → [Tag] → Integer → IO r+fetchPostPageIndexed s d ts i = fetchResponse (postUrlPaged s d ts i) d++-- | Given a URL and protocol, tries to fetch a response.+fetchResponse ∷ CoerceResponse r r' ⇒ String → r → IO r'+fetchResponse u r = toResponse r . toString . toStrict <$> simpleHttp u++-- | Uses 'fetchPostPage' to parse the number of posts available based on+-- provided 'Tag's.+fetchPostCount+ ∷ (Postable s r, Counted s r, CoerceResponse r a) ⇒ s → r → [Tag] → IO Integer+fetchPostCount s d ts = parseCount s <$> fetchPostPage s d ts++-- | Attemps to fetch all posts from a site, from all its pages. The+-- upper limit of images per page is used.+fetchAllTaggedPosts+ ∷ (CoerceResponse r a, PostablePaged s r) ⇒ s → r → [Tag] → IO [ImageTy s r]+fetchAllTaggedPosts s r ts = do+ count ← fromIntegral <$> fetchPostCount s r ts+ let pages = case hardLimit s r of+ NoLimit → 0+ Limit x → max 0 (ceiling $ (count / fromIntegral x) - 1)+ concat <$> mapM (fetchTaggedPostsIndexed s r ts) [0 .. pages]
src/HBooru/Parsers/Gelbooru.hs view
@@ -1,12 +1,12 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE UnicodeSyntax #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-} -- | -- Module : HBooru.Parsers.Gelbooru--- Copyright : (c) Mateusz Kowalczyk 2013+-- Copyright : (c) Mateusz Kowalczyk 2013-2014 -- License : GPL-3 -- -- Maintainer : fuuzetsu@fuuzetsu.co.uk@@ -15,33 +15,66 @@ -- Module for parsing content from <http://gelbooru.com/ Gelbooru>. module HBooru.Parsers.Gelbooru where -import Data.List-import qualified HBooru.Parsers.GenericBooru as G-import HBooru.Parsers.GenericBooru.TH (makePost)-import HBooru.Types-import Language.Haskell.TH (mkName)-import Text.XML.HXT.Core hiding (mkName)+import Data.List+import Data.Vinyl+import HBooru.Parsers.FieldParsers+import HBooru.Types+import Text.XML.HXT.Core hiding (mkName) --- | Data type for Gelbooru posts generated using 'makePost'.-$(makePost (mkName "GelbooruPost"))+-- | Record used for Gelbooru posts+type GelbooruPost = R+ '[ "height"+ , "score"+ , "file_url"+ , "parent_id"+ , "sample_url"+ , "sample_width"+ , "sample_height"+ , "preview_url"+ , "rating"+ , "tags"+ , "id"+ , "width"+ , "change"+ , "md5"+ , "creator_id"+ , "has_children"+ , "created_at"+ , "status"+ , "source"+ , "has_notes"+ , "has_comments"+ , "preview_width"+ , "preview_height"+ ] +-- | XML parser for Gelbooru used by "Postable Gelbooru XML" instance.+parsePost ∷ (Functor (cat XmlTree), ArrowXml cat) ⇒ cat XmlTree GelbooruPost+parsePost = hasName "post"+ >>> heightA <:+> scoreA <:+> file_urlA <:+> parent_idA <:+> sample_urlA+ <:+> sample_widthA <:+> sample_heightA <:+> preview_urlA <:+> ratingA+ <:+> tagsA <:+> idA <:+> widthA <:+> changeA <:+> md5A <:+> creator_idA+ <:+> has_childrenA <:+> created_atA <:+> statusA <:+> sourceA <:+> has_notesA+ <:+> has_commentsA <:+> preview_widthA <:+> preview_heightA+ -- | We use this type and its 'Site' instance to distinguish -- between various parsers. data Gelbooru = Gelbooru instance Postable Gelbooru XML where postUrl _ _ ts =- let tags = intercalate "+" ts+ let tags' = intercalate "+" ts in "http://gelbooru.com/index.php?page=dapi&s=post&q=index&limit=100&tags="- ++ tags ++ "&pid=0"- hardLimit _ = Limit 100+ ++ tags'+ hardLimit _ _ = Limit 100 +instance PostablePaged Gelbooru XML+ instance Site Gelbooru where instance PostParser Gelbooru XML where type ImageTy Gelbooru XML = GelbooruPost- parseResponse _ = map (`betweenPosts` GelbooruPost)- . runLA (xreadDoc /> G.parsePost) . getResponse+ parseResponse _ = runLA (xreadDoc /> parsePost) . getResponse instance Counted Gelbooru XML where parseCount _ = read . head . runLA (xreadDoc >>> hasName "posts"
− src/HBooru/Parsers/GenericBooru.hs
@@ -1,104 +0,0 @@-{-# LANGUAGE UnicodeSyntax #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE Arrows #-}-{-# LANGUAGE TemplateHaskell #-}--- |--- Module : HBooru.Parsers.GenericBooru--- Copyright : (c) Mateusz Kowalczyk 2013--- License : GPL-3------ Maintainer : fuuzetsu@fuuzetsu.co.uk--- Stability : experimental------ | Many booru sites use the same engine (Gelbooru engine) so instead of having--- identical parsers across many modules, we simply alias our 'GenericPost' to--- whatever we need.-module HBooru.Parsers.GenericBooru where--import Prelude hiding (id)-import Data.String-import HBooru.Types-import HBooru.Parsers.GenericBooru.TH-import Text.Read (readMaybe)-import Text.XML.HXT.Core hiding (mkName)-import Language.Haskell.TH (mkName)---- | A post we might expect from many of the sites as a lot of them seem to be--- based on the Gelbooru engine.-$(makePost (mkName "GenericPost"))---- | Fairly naïve parser for all attributes in sites running vanilla--- Gelbooru engine. While it catches all attributes in a typical XML post--- result, we trust that they are consistent and use 'read' to deserialise some--- values. For sites with non-default replies, custom parsers have to be written.-parsePost ∷ ArrowXml cat ⇒ cat XmlTree GenericPost-parsePost = hasName "post" >>> proc x → do- height <- getAttrValue "height" -< x- score <- getAttrValue "score" -< x- file_url <- getAttrValue "file_url" -< x- parent_id <- getAttrValue "parent_id" -< x- sample_url <- getAttrValue "sample_url" -< x- sample_width <- getAttrValue "sample_width" -< x- sample_height <- getAttrValue "sample_height" -< x- preview_url <- getAttrValue "preview_url" -< x- rating <- getAttrValue "rating" -< x- tags <- getAttrValue "tags" -< x- id <- getAttrValue "id" -< x- width <- getAttrValue "width" -< x- change <- getAttrValue "change" -< x- md5 <- getAttrValue "md5" -< x- creator_id <- getAttrValue "creator_id" -< x- has_children <- getAttrValue "has_children" -< x- created_at <- getAttrValue "created_at" -< x- status <- getAttrValue "status" -< x- source <- getAttrValue "source" -< x- has_notes <- getAttrValue "has_notes" -< x- has_comments <- getAttrValue "has_comments" -< x- preview_width <- getAttrValue "preview_width" -< x- preview_height <- getAttrValue "preview_height" -< x- returnA -< GenericPost- { heightT = read height- , scoreT = read score- , file_urlT = file_url- , parent_idT = readMaybe parent_id- , sample_urlT = sample_url- , sample_widthT = read sample_width- , sample_heightT = read sample_height- , preview_urlT = preview_url- , ratingT = parseRating rating- , tagsT = parseTags tags- , idT = read id- , widthT = read width- , changeT = change- , md5T = md5- , creator_idT = read creator_id- , has_childrenT = parseBool has_children- , created_atT = created_at- , statusT = status- , sourceT = source- , has_notesT = parseBool has_notes- , has_commentsT = parseBool has_comments- , preview_widthT = read preview_width- , preview_heightT = read preview_height- }---- | Parses a string returned from a Gelbooru-like site into--- one of the commonly used 'Rating's. Note that this is a partial function--- so you should make sure that the site in question only ever returns the--- values in a format specified in the function-parseRating :: String -> Rating-parseRating "e" = Explicit-parseRating "s" = HBooru.Types.Safe-parseRating "q" = Questionable---- | Splits returned tag string into separate 'Tag's. For Gelbooru-like--- sites, this is just the question of splitting on whitespace.-parseTags :: String -> [Tag]-parseTags = words---- | Reads a lowercase 'Bool' string representation into its Haskell type. If we--- can't parse the boolean, return 'Nothing'.-parseBool :: String -> Maybe Bool-parseBool "false" = Just False-parseBool "true" = Just True-parseBool _ = Nothing
− src/HBooru/Parsers/GenericBooru/TH.hs
@@ -1,90 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}--- |--- Module : HBooru.Parsers.GenericBooru.TH--- Copyright : (c) Mateusz Kowalczyk 2013--- License : GPL-3------ Maintainer : fuuzetsu@fuuzetsu.co.uk--- Stability : experimental------ | Helper module for "HBooru.Parsers.GenericBooru" due to Template Haskell--- limitation of being unable to splice inside of the same module of--- definitions.-module HBooru.Parsers.GenericBooru.TH where--import Control.Applicative-import HBooru.Types-import Language.Haskell.TH-import Language.Haskell.TH.Syntax---- | A TH helper which makes an instance along with the data type using--- 'makePost'' and 'makePostInstance'.-makePost n = liftA2 (++) (makePost' n) (makePostInstance n)---- | Template Haskell function which is able to generate 'GenericPost'-alike--- type declarations for cases where we want to use this format but need a--- different data type. It can be used by using @TemplateHaskell@ extension and--- calling @$(makePost ('mkName' \"YourTypeName\"))@ at the top level. Hopefully--- a temporary measure until the author thinks of a better way to provide--- generic Gelbooru-like post parsing while casting out to different data types--- that's OK to write.-makePost' :: Name -> Q [Dec]-makePost' n =- fmap (:[]) $ dataD (cxt []) n []- [ recC n- [ varStrictType (mkName "heightT") $ strictType notStrict [t| Integer |]- , varStrictType (mkName "scoreT") $ strictType notStrict [t| Integer |]- , varStrictType (mkName "file_urlT") $ strictType notStrict [t| String |]- , varStrictType (mkName "parent_idT") $ strictType notStrict- [t| Maybe Integer |]- , varStrictType (mkName "sample_urlT") $ strictType notStrict [t| String |]- , varStrictType (mkName "sample_widthT") $ strictType notStrict- [t| Integer |]- , varStrictType (mkName "sample_heightT") $ strictType notStrict- [t| Integer |]- , varStrictType (mkName "preview_urlT") $ strictType notStrict [t| String |]- , varStrictType (mkName "ratingT") $ strictType notStrict [t| Rating |]- , varStrictType (mkName "tagsT") $ strictType notStrict [t| [String] |]- , varStrictType (mkName "idT") $ strictType notStrict [t| Integer |]- , varStrictType (mkName "widthT") $ strictType notStrict [t| Integer |]- , varStrictType (mkName "changeT") $ strictType notStrict [t| String |]- , varStrictType (mkName "md5T") $ strictType notStrict [t| String |]- , varStrictType (mkName "creator_idT") $ strictType notStrict [t| Integer |]- , varStrictType (mkName "has_childrenT") $ strictType notStrict- [t| Maybe Bool |]- , varStrictType (mkName "created_atT") $ strictType notStrict [t| String |]- , varStrictType (mkName "statusT") $ strictType notStrict [t| String |]- , varStrictType (mkName "sourceT") $ strictType notStrict [t| String |]- , varStrictType (mkName "has_notesT") $ strictType notStrict- [t| Maybe Bool |]- , varStrictType (mkName "has_commentsT") $ strictType notStrict- [t| Maybe Bool |]- , varStrictType (mkName "preview_widthT") $ strictType notStrict- [t| Integer |]- , varStrictType (mkName "preview_heightT") $ strictType notStrict- [t| Integer |]- ]- ] [mkName "Show", mkName "Eq"]---- | Template Haskell function which creates 'Post' instances for things made--- with 'makePost'.-makePostInstance :: Name -> Q [Dec]-makePostInstance n = do- return [InstanceD- []- (ConT (mkName "Post") `AppT` ConT n)- [ onG "height", onG "score", onG "file_url", onG "parent_id"- , onG "sample_url", onG "sample_width", onG "sample_height"- , onG "preview_url", onG "rating", onG "tags", onG "id"- , onG "width", onG "change", onG "md5", onG "creator_id"- , onG "has_children", onG "created_at", onG "status"- , onG "source", onG "has_notes", onG "has_comments"- , onG "preview_width", onG "preview_height"- ]- ]- where- onG n = FunD (mkName n)- [ Clause [(VarP (mkName "g"))]- (NormalB (AppE (VarE (mkName $ n ++ "T"))- (VarE (mkName "g")))) []- ]
src/HBooru/Parsers/Ichijou.hs view
@@ -1,46 +1,82 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE UnicodeSyntax #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE DataKinds #-} -- | -- Module : HBooru.Parsers.Ichijou--- Copyright : (c) Mateusz Kowalczyk 2013+-- Copyright : (c) Mateusz Kowalczyk 2013-2014 -- License : GPL-3 -- -- Maintainer : fuuzetsu@fuuzetsu.co.uk -- Stability : experimental ----- Module for parsing content from <http://safebooru.org/ safebooru>.+-- Module for parsing content from <http://ichijou.org/ Ichijou/vectorbooru>. module HBooru.Parsers.Ichijou where -import Data.List-import qualified HBooru.Parsers.GenericBooru as G-import HBooru.Parsers.GenericBooru.TH (makePost)-import HBooru.Types-import Language.Haskell.TH.Syntax (mkName)-import Text.XML.HXT.Core hiding (mkName)---- | Data type for safebooru posts generated using 'makePost'.-$(makePost (mkName "IchijouPost"))+import Data.List+import Data.Vinyl+import HBooru.Parsers.FieldParsers+import HBooru.Types+import Text.XML.HXT.Core hiding (mkName) -- | We use this type and its 'Site' instance to distinguish -- between various parsers. data Ichijou = Ichijou +-- | Ichijou post record alias+type IchijouPost = R+ '[ "creator_id"+ , "md5"+ , "status"+ , "preview_height"+ , "has_notes"+ , "author"+ , "source"+ , "score"+ , "file_size"+ , "sample_width"+ , "width"+ , "file_url"+ , "sample_height"+ , "has_children"+ , "tags"+ , "change"+ , "preview_url"+ , "has_comments"+ , "id"+ , "sample_url"+ , "rating"+ , "created_at"+ , "preview_width"+ , "parent_id"+ , "height"+ ]++-- | Parser arrow used for Ichijou.+parsePost ∷ (Functor (cat XmlTree), ArrowXml cat) ⇒ cat XmlTree IchijouPost+parsePost = hasName "post"+ >>> creator_idA <:+> md5A <:+> statusA <:+> preview_heightA <:+> has_notesA+ <:+> authorA <:+> sourceA <:+> scoreA <:+> file_sizeA <:+> sample_widthA+ <:+> widthA <:+> file_urlA <:+> sample_heightA <:+> has_childrenA <:+> tagsA+ <:+> changeA <:+> preview_urlA <:+> has_commentsA <:+> idA <:+> sample_urlA+ <:+> ratingA <:+> created_atA <:+> preview_widthA <:+> parent_idA <:+> heightA+ instance Postable Ichijou XML where postUrl _ _ ts =- let tags = intercalate "+" ts- in "http://ichijou.org/index.xml?tags=" ++ tags- hardLimit _ = Limit 100+ let tags' = intercalate "+" ts+ in "http://ichijou.org/post/index.xml?tags=" ++ tags'+ hardLimit _ _ = Limit 1000 +instance PostablePaged Ichijou XML where+ postUrlPaged s r ts i = postUrl s r ts ++ "&page=" ++ show (i + 1)+ instance Site Ichijou where instance PostParser Ichijou XML where type ImageTy Ichijou XML = IchijouPost- parseResponse _ = map (`betweenPosts` IchijouPost)- . runLA (xreadDoc /> G.parsePost) . getResponse+ parseResponse _ = runLA (xreadDoc /> parsePost) . getResponse instance Counted Ichijou XML where parseCount _ = read . head . runLA (xreadDoc >>> hasName "posts"
src/HBooru/Parsers/Konachan.hs view
@@ -1,12 +1,12 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE UnicodeSyntax #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-} -- | -- Module : HBooru.Parsers.Konachan--- Copyright : (c) Mateusz Kowalczyk 2013+-- Copyright : (c) Mateusz Kowalczyk 2013-2014 -- License : GPL-3 -- -- Maintainer : fuuzetsu@fuuzetsu.co.uk@@ -16,32 +16,82 @@ module HBooru.Parsers.Konachan where -import Data.List-import qualified HBooru.Parsers.GenericBooru as G-import HBooru.Parsers.GenericBooru.TH (makePost)-import HBooru.Types-import Language.Haskell.TH (mkName)-import Text.XML.HXT.Core hiding (mkName)---- | Data type for Konachan posts generated using 'makePost'.-$(makePost (mkName "KonachanPost"))+import Data.List+import Data.Vinyl+import HBooru.Parsers.FieldParsers+import HBooru.Types+import Text.XML.HXT.Core hiding (mkName) -- | We use this type and its 'Site' instance to distinguish -- between various parsers. data Konachan = Konachan +-- | Konachan post record+type KonachanPost = R+ '[ "actual_preview_height"+ , "actual_preview_width"+ , "author"+ , "change"+ , "created_at"+ , "file_size"+ , "file_url"+ , "frames"+ , "frames_pending"+ , "frames_pending_string"+ , "frames_string"+ , "has_children"+ , "height"+ , "id"+ , "is_held"+ , "is_shown_in_index"+ , "jpeg_file_size"+ , "jpeg_height"+ , "jpeg_url"+ , "jpeg_width"+ , "md5"+ , "preview_height"+ , "preview_url"+ , "preview_width"+ , "rating"+ , "sample_file_size"+ , "sample_height"+ , "sample_url"+ , "sample_width"+ , "score"+ , "source"+ , "status"+ , "tags"+ , "width"+ ]++-- | Parser arrow used for Konachan.+parsePost ∷ (Functor (cat XmlTree), ArrowXml cat) ⇒ cat XmlTree KonachanPost+parsePost = hasName "post"+ >>> actual_preview_heightA <:+> actual_preview_widthA <:+> authorA+ <:+> changeA <:+> created_atA <:+> file_sizeA <:+> file_urlA+ <:+> framesA <:+> frames_pendingA <:+> frames_pending_stringA+ <:+> frames_stringA <:+> has_childrenA+ <:+> heightA <:+> idA <:+> is_heldA <:+> is_shown_in_indexA+ <:+> jpeg_file_sizeA <:+> jpeg_heightA <:+> jpeg_urlA <:+> jpeg_widthA+ <:+> md5A <:+> preview_heightA <:+> preview_urlA <:+> preview_widthA+ <:+> ratingA <:+> sample_file_sizeA <:+> sample_heightA <:+> sample_urlA+ <:+> sample_widthA <:+> scoreA <:+> sourceA <:+> statusA <:+> tagsA+ <:+> widthA+ instance Postable Konachan XML where postUrl _ _ ts =- let tags = intercalate "+" ts- in "https://konachan.com/post/index.xml?tags=" ++ tags- hardLimit _ = Limit 100+ let tags' = intercalate "+" ts+ in "https://konachan.com/post/index.xml?tags=" ++ tags' ++ "&limit=1000"+ hardLimit _ _ = Limit 100 +instance PostablePaged Konachan XML where+ postUrlPaged s r ts i = postUrl s r ts ++ "&page=" ++ show (i + 1)+ instance Site Konachan where instance PostParser Konachan XML where type ImageTy Konachan XML = KonachanPost- parseResponse _ = map (`betweenPosts` KonachanPost)- . runLA (xreadDoc /> G.parsePost) . getResponse+ parseResponse _ = runLA (xreadDoc /> parsePost) . getResponse instance Counted Konachan XML where parseCount _ = read . head . runLA (xreadDoc >>> hasName "posts"
src/HBooru/Parsers/Safebooru.hs view
@@ -1,12 +1,12 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE UnicodeSyntax #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-} -- | -- Module : HBooru.Parsers.Safebooru--- Copyright : (c) Mateusz Kowalczyk 2013+-- Copyright : (c) Mateusz Kowalczyk 2013-2014 -- License : GPL-3 -- -- Maintainer : fuuzetsu@fuuzetsu.co.uk@@ -15,33 +15,66 @@ -- Module for parsing content from <http://safebooru.org/ safebooru>. module HBooru.Parsers.Safebooru where -import Data.List-import qualified HBooru.Parsers.GenericBooru as G-import HBooru.Parsers.GenericBooru.TH (makePost)-import HBooru.Types-import Language.Haskell.TH.Syntax (mkName)-import Text.XML.HXT.Core hiding (mkName)---- | Data type for safebooru posts generated using 'makePost'.-$(makePost (mkName "SafebooruPost"))+import Data.List+import Data.Vinyl+import HBooru.Parsers.FieldParsers+import HBooru.Types+import Text.XML.HXT.Core hiding (mkName) -- | We use this type and its 'Site' instance to distinguish -- between various parsers. data Safebooru = Safebooru +-- | Safebooru post record.+type SafebooruPost = R+ '[ "height"+ , "score"+ , "file_url"+ , "parent_id"+ , "sample_url"+ , "sample_width"+ , "sample_height"+ , "preview_url"+ , "rating"+ , "tags"+ , "id"+ , "width"+ , "change"+ , "md5"+ , "creator_id"+ , "has_children"+ , "created_at"+ , "status"+ , "source"+ , "has_notes"+ , "has_comments"+ , "preview_width"+ , "preview_height"+ ]++-- | Parser arrow used for Safebooru.+parsePost ∷ (Functor (cat XmlTree), ArrowXml cat) ⇒ cat XmlTree SafebooruPost+parsePost = hasName "post"+ >>> heightA <:+> scoreA <:+> file_urlA <:+> parent_idA <:+> sample_urlA+ <:+> sample_widthA <:+> sample_heightA <:+> preview_urlA <:+> ratingA+ <:+> tagsA <:+> idA <:+> widthA <:+> changeA <:+> md5A <:+> creator_idA+ <:+> has_childrenA <:+> created_atA <:+> statusA <:+> sourceA <:+> has_notesA+ <:+> has_commentsA <:+> preview_widthA <:+> preview_heightA+ instance Postable Safebooru XML where postUrl _ _ ts =- let tags = intercalate "+" ts+ let tags' = intercalate "+" ts in "http://safebooru.org/index.php?page=dapi&s=post&q=index&limit=100&tags="- ++ tags ++ "&pid=0"- hardLimit _ = Limit 100+ ++ tags'+ hardLimit _ _ = Limit 100 +instance PostablePaged Safebooru XML+ instance Site Safebooru where instance PostParser Safebooru XML where type ImageTy Safebooru XML = SafebooruPost- parseResponse _ = map (`betweenPosts` SafebooruPost)- . runLA (xreadDoc /> G.parsePost) . getResponse+ parseResponse _ = runLA (xreadDoc /> parsePost) . getResponse instance Counted Safebooru XML where parseCount _ = read . head . runLA (xreadDoc >>> hasName "posts"
src/HBooru/Parsers/Yandere.hs view
@@ -1,12 +1,12 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE UnicodeSyntax #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-} -- | -- Module : HBooru.Parsers.Yandere--- Copyright : (c) Mateusz Kowalczyk 2013+-- Copyright : (c) Mateusz Kowalczyk 2013-2014 -- License : GPL-3 -- -- Maintainer : fuuzetsu@fuuzetsu.co.uk@@ -16,32 +16,82 @@ -- known in the past as <http://mou.imouto.org MouImouto>. module HBooru.Parsers.Yandere where -import Data.List-import qualified HBooru.Parsers.GenericBooru as G-import HBooru.Parsers.GenericBooru.TH (makePost)-import HBooru.Types-import Language.Haskell.TH (mkName)-import Text.XML.HXT.Core hiding (mkName)---- | Data type for Yandere posts generated using 'makePost'.-$(makePost (mkName "YanderePost"))+import Data.List+import Data.Vinyl+import HBooru.Parsers.FieldParsers+import HBooru.Types+import Text.XML.HXT.Core hiding (mkName) -- | We use this type and its 'Site' instance to distinguish -- between various parsers. data Yandere = Yandere +-- | Alias for a record representing typical Yandere post.+type YanderePost = R+ '[ "actual_preview_height"+ , "actual_preview_width"+ , "author"+ , "change"+ , "created_at"+ , "creator_id"+ , "file_size"+ , "file_url"+ , "frames"+ , "frames_pending"+ , "frames_pending_string"+ , "frames_string"+ , "has_children"+ , "height"+ , "id"+ , "is_held"+ , "is_shown_in_index"+ , "jpeg_file_size"+ , "jpeg_height"+ , "jpeg_url"+ , "jpeg_width"+ , "md5"+ , "preview_height"+ , "preview_url"+ , "preview_width"+ , "rating"+ , "sample_file_size"+ , "sample_height"+ , "sample_url"+ , "sample_width"+ , "score"+ , "source"+ , "status"+ , "tags"+ , "width"+ ]++-- | Parser arrow for XML Yandere posts.+parsePost ∷ (Functor (cat XmlTree), ArrowXml cat) ⇒ cat XmlTree YanderePost+parsePost = hasName "post"+ >>> actual_preview_heightA <:+> actual_preview_widthA <:+> authorA+ <:+> changeA <:+> created_atA <:+> creator_idA <:+> file_sizeA <:+> file_urlA+ <:+> framesA <:+> frames_pendingA <:+> frames_pending_stringA+ <:+> frames_stringA <:+> has_childrenA <:+> heightA <:+> idA <:+> is_heldA+ <:+> is_shown_in_indexA <:+> jpeg_file_sizeA <:+> jpeg_heightA <:+> jpeg_urlA+ <:+> jpeg_widthA <:+> md5A <:+> preview_heightA <:+> preview_urlA+ <:+> preview_widthA <:+> ratingA <:+> sample_file_sizeA <:+> sample_heightA+ <:+> sample_urlA <:+> sample_widthA <:+> scoreA <:+> sourceA <:+> statusA+ <:+> tagsA <:+> widthA+ instance Postable Yandere XML where postUrl _ _ ts =- let tags = intercalate "+" ts- in "https://yande.re/post/index.xml?tags=" ++ tags- hardLimit _ = Limit 100+ let tgs = intercalate "+" ts+ in "https://yande.re/post/index.xml?tags=" ++ tgs+ hardLimit _ _ = Limit 1000 +instance PostablePaged Yandere XML where+ postUrlPaged s r ts i = postUrl s r ts ++ "&page=" ++ show (i + 1)+ instance Site Yandere where instance PostParser Yandere XML where type ImageTy Yandere XML = YanderePost- parseResponse _ = map (`betweenPosts` YanderePost)- . runLA (xreadDoc /> G.parsePost) . getResponse+ parseResponse _ = runLA (xreadDoc /> parsePost) . getResponse instance Counted Yandere XML where parseCount _ = read . head . runLA (xreadDoc >>> hasName "posts"
src/HBooru/Types.hs view
@@ -1,12 +1,15 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE UnicodeSyntax #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_GHC -fno-warn-orphans #-} -- | -- Module : HBooru.Types--- Copyright : (c) Mateusz Kowalczyk 2013+-- Copyright : (c) Mateusz Kowalczyk 2013-2014 -- License : GPL-3 -- -- Maintainer : fuuzetsu@fuuzetsu.co.uk@@ -15,7 +18,12 @@ -- Module definining types used by the library. module HBooru.Types where +import Data.Proxy+import GHC.TypeLits (Symbol)+import Data.Vinyl+import Data.Vinyl.TH import Prelude hiding (id)+import Text.XML.HXT.Core hiding (mkName, (<+>)) -- | Tags used for searching in sites. No special escaping is done. -- Note that many sites would treat a tag like \"striped panties\"@@ -63,14 +71,14 @@ toResponse _ = JSONResponse fromResponse _ = JSON --- | Class specifying a parser that can fetch posts. A post usually consists of--- links to the image, samples, and some meta-data. See--- 'HBooru.Parsers.GenericBooru.GenericPost' for the kind of thing we usually--- get out. The reason for this class is that sometimes we might get different--- information based on the 'DataFormat' we use so we use type families to--- denote this rather than forcing the library user to make do with our best--- guess on what goes into the post. It also allows us to use different post--- types for sites that provide different information.+-- | Class specifying a parser that can fetch posts. A post usually+-- consists of links to the image, samples, and some meta-data. The+-- reason for this class is that sometimes we might get different+-- information based on the 'DataFormat' we use so we use type+-- families to denote this rather than forcing the library user to+-- make do with our best guess on what goes into the post. It also+-- allows us to use different post types for sites that provide+-- different information. class (Site s, DataFormat r) ⇒ PostParser s r where type ImageTy s r -- | Given a parser working with 'DataFormat' specified by an instance of@@ -97,11 +105,18 @@ -- allows us to get the information about total number of posts matching our -- query. Some sites don't provide this information. class (Site s, DataFormat r) ⇒ Counted s r where+ -- | Parses out the number of available images from a response. parseCount ∷ CoerceResponse r r' ⇒ s → r' → Integer +class (Counted s r, Postable s r) ⇒ PostablePaged s r where+ -- | Similar to 'postUrl' but requests images from specific page if+ -- the site allows it.+ postUrlPaged ∷ s → r → [Tag] → Integer → String+ postUrlPaged s r ts i = postUrl s r ts ++ "&pid=" ++ show i+ -- | If we can make an API request to 'Site' in a specific 'DataFormat', we can -- use instances of this class to pass in-class (Site s, DataFormat r) ⇒ Postable s r where+class PostParser s r ⇒ Postable s r where -- | Given a 'Site', a 'DataFormat' and a list of 'Tag's, an instance of this -- class should be able to return a 'String' at which we can find data in -- 'DataFormat' format that honours our tags. This is effectively a URL@@ -111,7 +126,7 @@ -- posts we can fetch from the site at once. The reason for this function here -- rather than in 'Site' is that we might be parsing data without an API we -- can post to at all and we're getting our data through other means.- hardLimit ∷ s → Limit+ hardLimit ∷ s → r → Limit -- | Describes a site for a parser. The reason why this isn't a simple data type -- is to allow us to write additional parsers in the future without modifying@@ -153,46 +168,166 @@ instance Response JSONResponse where getResponse (JSONResponse x) = x --- | Class representing a best-case scenario post. We use this--- to convert between different posts for each site while providing--- uniform access. The methods are just the attributes of posts seen--- on Gelbooru-like sites.-class Post a where- height ∷ a → Integer- score ∷ a → Integer- file_url ∷ a → String- parent_id ∷ a → Maybe Integer- sample_url ∷ a → String- sample_width ∷ a → Integer- sample_height ∷ a → Integer- preview_url ∷ a → String- rating ∷ a → Rating- tags ∷ a → [String]- id ∷ a → Integer- width ∷ a → Integer- change ∷ a → String- md5 ∷ a → String- creator_id ∷ a → Integer- has_children ∷ a → Maybe Bool- created_at ∷ a → String- status ∷ a → String- source ∷ a → String- has_notes ∷ a → Maybe Bool- has_comments ∷ a → Maybe Bool- preview_width ∷ a → Integer- preview_height ∷ a → Integer- betweenPosts ∷ Post b ⇒ a → PostConstructor b → b- betweenPosts g c =- c (height g) (score g) (file_url g) (parent_id g) (sample_url g)- (sample_width g) (sample_height g) (preview_url g) (rating g)- (tags g) (id g) (width g) (change g) (md5 g) (creator_id g)- (has_children g) (created_at g) (status g) (source g) (has_notes g)- (has_comments g) (preview_width g) (preview_height g) +instance Functor (LA XmlTree) where+ fmap f (LA g) = LA $ fmap fmap fmap f g --- | A cludge for use with 'betweenPosts'-type PostConstructor b =- Integer -> Integer -> String -> Maybe Integer -> String -> Integer -> Integer- -> String -> Rating -> [String] -> Integer -> Integer -> String -> String- -> Integer -> Maybe Bool -> String -> String -> String -> Maybe Bool- -> Maybe Bool -> Integer -> Integer -> b+makeUniverse' ''Symbol "ElF"+semantics ''ElF [ [t| "height" |] :~> [t| Integer |]+ , [t| "score" |] :~> [t| Integer |]+ , [t| "file_url" |] :~> [t| String |]+ , [t| "parent_id" |] :~> [t| Maybe Integer |]+ , [t| "sample_url" |] :~> [t| String |]+ , [t| "sample_width" |] :~> [t| Integer |]+ , [t| "sample_height" |] :~> [t| Integer |]+ , [t| "preview_url" |] :~> [t| String |]+ , [t| "rating" |] :~> [t| Rating |]+ , [t| "tags" |] :~> [t| [Tag] |]+ , [t| "id" |] :~> [t| Integer |]+ , [t| "width" |] :~> [t| String |]+ , [t| "change" |] :~> [t| String |]+ , [t| "md5" |] :~> [t| String |]+ , [t| "creator_id" |] :~> [t| Integer |]+ , [t| "has_children" |] :~> [t| Bool |]+ , [t| "created_at" |] :~> [t| String |]+ , [t| "status" |] :~> [t| String |]+ , [t| "source" |] :~> [t| String |]+ , [t| "has_notes" |] :~> [t| Maybe Bool |]+ , [t| "has_comments" |] :~> [t| Maybe Bool |]+ , [t| "preview_width" |] :~> [t| Integer |]+ , [t| "preview_height" |] :~> [t| Integer |]+ , [t| "author" |] :~> [t| String |]+ , [t| "frames" |] :~> [t| String |]+ , [t| "frames_pending" |] :~> [t| String |]+ , [t| "frames_pending_string" |] :~> [t| String |]+ , [t| "frames_string" |] :~> [t| String |]+ , [t| "is_held" |] :~> [t| Bool |]+ , [t| "is_shown_in_index" |] :~> [t| Bool |]+ , [t| "jpeg_file_size" |] :~> [t| Integer |]+ , [t| "jpeg_height" |] :~> [t| Integer |]+ , [t| "jpeg_url" |] :~> [t| String |]+ , [t| "jpeg_width" |] :~> [t| Integer |]+ , [t| "sample_file_size" |] :~> [t| Integer |]+ , [t| "actual_preview_height" |] :~> [t| Integer |]+ , [t| "actual_preview_width" |] :~> [t| Integer |]+ , [t| "file_size" |] :~> [t| Integer |]+ ]++-- | Handy synonym hiding 'ElF'.+type R a = PlainRec ElF a++-- * Commonly used fields++height ∷ Proxy "height"+height = Proxy++score ∷ Proxy "score"+score = Proxy++file_url ∷ Proxy "file_url"+file_url = Proxy++parent_id ∷ Proxy "parent_id"+parent_id = Proxy++sample_url ∷ Proxy "sample_url"+sample_url = Proxy++sample_width ∷ Proxy "sample_width"+sample_width = Proxy++sample_height ∷ Proxy "sample_height"+sample_height = Proxy++preview_url ∷ Proxy "preview_url"+preview_url = Proxy++rating ∷ Proxy "rating"+rating = Proxy++tags ∷ Proxy "tags"+tags = Proxy++id ∷ Proxy "id"+id = Proxy++width ∷ Proxy "width"+width = Proxy++change ∷ Proxy "change"+change = Proxy++md5 ∷ Proxy "md5"+md5 = Proxy++creator_id ∷ Proxy "creator_id"+creator_id = Proxy++has_children ∷ Proxy "has_children"+has_children = Proxy++created_at ∷ Proxy "created_at"+created_at = Proxy++status ∷ Proxy "status"+status = Proxy++source ∷ Proxy "source"+source = Proxy++has_notes ∷ Proxy "has_notes"+has_notes = Proxy++has_comments ∷ Proxy "has_comments"+has_comments = Proxy++preview_width ∷ Proxy "preview_width"+preview_width = Proxy++preview_height ∷ Proxy "preview_height"+preview_height = Proxy++author ∷ Proxy "author"+author = Proxy++frames ∷ Proxy "frames"+frames = Proxy++frames_pending ∷ Proxy "frames_pending"+frames_pending = Proxy++frames_pending_string ∷ Proxy "frames_pending_string"+frames_pending_string = Proxy++frames_string ∷ Proxy "frames_string"+frames_string = Proxy++is_held ∷ Proxy "is_held"+is_held = Proxy++is_shown_in_index ∷ Proxy "is_shown_in_index"+is_shown_in_index = Proxy++jpeg_file_size ∷ Proxy "jpeg_file_size"+jpeg_file_size = Proxy++jpeg_height ∷ Proxy "jpeg_height"+jpeg_height = Proxy++jpeg_url ∷ Proxy "jpeg_url"+jpeg_url = Proxy++jpeg_width ∷ Proxy "jpeg_width"+jpeg_width = Proxy++sample_file_size ∷ Proxy "sample_file_size"+sample_file_size = Proxy++actual_preview_height ∷ Proxy "actual_preview_height"+actual_preview_height = Proxy++actual_preview_width ∷ Proxy "actual_preview_width"+actual_preview_width = Proxy++file_size ∷ Proxy "file_size"+file_size = Proxy
+ src/Main.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE UnicodeSyntax #-}+{-# LANGUAGE LambdaCase #-}+module Main where++import Control.Applicative ((<$>))+import Data.Vinyl+import HBooru.Network+import HBooru.Parsers.Gelbooru+import HBooru.Parsers.Ichijou+import HBooru.Parsers.Konachan+import HBooru.Parsers.Safebooru+import HBooru.Parsers.Yandere+import HBooru.Types+import System.Environment (getArgs)++main ∷ IO ()+main = getArgs >>= \case+ [] → putStrLn help+ xs → fetchImageLinks xs >>= mapM_ putStrLn++fetchImageLinks ∷ [Tag] → IO [String]+fetchImageLinks xs = do+ let f p = map (file_url `rGet`) <$> fetchAllTaggedPosts p XML xs+ g ← f Gelbooru+ i ← f Ichijou+ k ← f Konachan+ s ← f Safebooru+ y ← f Yandere+ let ls = g ++ i ++ k ++ s ++ y+ return $ length ls `seq` ls++help ∷ String+help = unlines $+ [ "Usage: h-booru tag1 [tag2] … [tagn]"+ , ""+ , "Prints a list of links matching the tags"+ ]