packages feed

exh 0.2.0 → 1.0.0

raw patch · 20 files changed

+970/−739 lines, 20 filesdep +in-other-wordsdep +language-javascriptdep +optics-coredep −exceptionsdep −http-conduitdep −lensdep ~http-client-tlsdep ~transformersPVP ok

version bump matches the API change (PVP)

Dependencies added: in-other-words, language-javascript, optics-core, optics-th, xml-optics

Dependencies removed: exceptions, http-conduit, lens, monad-control, monad-time, mtl, quickjs-hs, retry, transformers-base, xml-lens

Dependency ranges changed: http-client-tls, transformers

API changes (from Hackage documentation)

- Web.Exhentai.API.Auth: instance GHC.Generics.Generic Web.Exhentai.API.Auth.Credential
- Web.Exhentai.API.Gallery: instance GHC.Generics.Generic Web.Exhentai.API.Gallery.GalleryInfo
- Web.Exhentai.API.Gallery: instance GHC.Generics.Generic Web.Exhentai.API.Gallery.Visibility
- Web.Exhentai.API.MPV: DispatchResult :: Text -> Text -> Text -> Text -> Dim -> Dim -> Text -> Server -> DispatchResult
- Web.Exhentai.API.MPV: Vars :: {-# UNPACK #-}Int -> {-# UNPACK #-}Text -> {-# UNPACK #-}Text -> {-# UNPACK #-}Int -> [MpvImage] -> Vars
- Web.Exhentai.API.MPV: [$sel:apiUrl:Vars] :: Vars -> {-# UNPACK #-}Text
- Web.Exhentai.API.MPV: [$sel:dimension:DispatchResult] :: DispatchResult -> Text
- Web.Exhentai.API.MPV: [$sel:galleryPath:DispatchResult] :: DispatchResult -> Text
- Web.Exhentai.API.MPV: [$sel:gid:Vars] :: Vars -> {-# UNPACK #-}Int
- Web.Exhentai.API.MPV: [$sel:height:DispatchResult] :: DispatchResult -> Dim
- Web.Exhentai.API.MPV: [$sel:imageList:Vars] :: Vars -> [MpvImage]
- Web.Exhentai.API.MPV: [$sel:imgLink:DispatchResult] :: DispatchResult -> Text
- Web.Exhentai.API.MPV: [$sel:mpvkey:Vars] :: Vars -> {-# UNPACK #-}Text
- Web.Exhentai.API.MPV: [$sel:origImgPath:DispatchResult] :: DispatchResult -> Text
- Web.Exhentai.API.MPV: [$sel:pageCount:Vars] :: Vars -> {-# UNPACK #-}Int
- Web.Exhentai.API.MPV: [$sel:searchPath:DispatchResult] :: DispatchResult -> Text
- Web.Exhentai.API.MPV: [$sel:server:DispatchResult] :: DispatchResult -> Server
- Web.Exhentai.API.MPV: [$sel:width:DispatchResult] :: DispatchResult -> Dim
- Web.Exhentai.API.MPV: data DispatchResult
- Web.Exhentai.API.MPV: data Vars
- Web.Exhentai.API.MPV: fetchImage' :: (MonadHttpState m, MonadIO n) => DispatchRequest -> m (Response (ConduitT i ByteString n ()))
- Web.Exhentai.API.MPV: fetchMpv :: (MonadHttpState m, MonadIO m) => Gallery -> m Vars
- Web.Exhentai.API.MPV: imageDispatch :: MonadHttpState m => DispatchRequest -> m DispatchResult
- Web.Exhentai.API.MPV: instance GHC.Generics.Generic Web.Exhentai.API.MPV.DispatchRequest
- Web.Exhentai.API.MPV: instance GHC.Generics.Generic Web.Exhentai.API.MPV.DispatchResult
- Web.Exhentai.API.MPV: instance GHC.Generics.Generic Web.Exhentai.API.MPV.Server
- Web.Exhentai.API.MPV: toRequests :: Vars -> [DispatchRequest]
- Web.Exhentai.API.Search: fetchSearchPage' :: MonadHttpState m => Request -> m SearchResult
- Web.Exhentai.API.Search: instance GHC.Generics.Generic Web.Exhentai.API.Search.SearchQuery
- Web.Exhentai.API.Search: instance GHC.Generics.Generic Web.Exhentai.API.Search.SearchResult
- Web.Exhentai.API.Search: searchRecurResumable' :: MonadHttpState m => Text -> ConduitT i (Either Text Gallery) m ()
- Web.Exhentai.Errors: ExtractionFailure :: String -> ExhentaiError
- Web.Exhentai.Errors: JSONParseFailure :: String -> ExhentaiError
- Web.Exhentai.Errors: XMLParseFailure :: Text -> Text -> ExhentaiError
- Web.Exhentai.Errors: [$sel:reason:JSONParseFailure] :: ExhentaiError -> Text
- Web.Exhentai.Errors: [$sel:url:JSONParseFailure] :: ExhentaiError -> Text
- Web.Exhentai.Errors: data ExhentaiError
- Web.Exhentai.Errors: instance GHC.Classes.Eq Web.Exhentai.Errors.ExhentaiError
- Web.Exhentai.Errors: instance GHC.Exception.Type.Exception Web.Exhentai.Errors.ExhentaiError
- Web.Exhentai.Errors: instance GHC.Generics.Generic Web.Exhentai.Errors.ExhentaiError
- Web.Exhentai.Errors: instance GHC.Show.Show Web.Exhentai.Errors.ExhentaiError
- Web.Exhentai.Parsing.Gallery: Artist :: TagCategory
- Web.Exhentai.Parsing.Gallery: Character :: TagCategory
- Web.Exhentai.Parsing.Gallery: Female :: TagCategory
- Web.Exhentai.Parsing.Gallery: Group :: TagCategory
- Web.Exhentai.Parsing.Gallery: Language :: TagCategory
- Web.Exhentai.Parsing.Gallery: Male :: TagCategory
- Web.Exhentai.Parsing.Gallery: Misc :: TagCategory
- Web.Exhentai.Parsing.Gallery: Parody :: TagCategory
- Web.Exhentai.Parsing.Gallery: Reclass :: TagCategory
- Web.Exhentai.Parsing.Gallery: _TagCategory :: Prism' Text TagCategory
- Web.Exhentai.Parsing.Gallery: data TagCategory
- Web.Exhentai.Parsing.Gallery: instance GHC.Classes.Eq Web.Exhentai.Parsing.Gallery.TagCategory
- Web.Exhentai.Parsing.Gallery: instance GHC.Enum.Enum Web.Exhentai.Parsing.Gallery.TagCategory
- Web.Exhentai.Parsing.Gallery: instance GHC.Generics.Generic Web.Exhentai.Parsing.Gallery.TagCategory
- Web.Exhentai.Parsing.Gallery: instance GHC.Show.Show Web.Exhentai.Parsing.Gallery.TagCategory
- Web.Exhentai.Parsing.Gallery: readTagCat :: Text -> Maybe TagCategory
- Web.Exhentai.Parsing.Image: nextPage :: Traversal' Element Text
- Web.Exhentai.Parsing.MPV: MpvImage :: {-# UNPACK #-}Text -> {-# UNPACK #-}Text -> {-# UNPACK #-}Text -> MpvImage
- Web.Exhentai.Parsing.MPV: Vars :: {-# UNPACK #-}Int -> {-# UNPACK #-}Text -> {-# UNPACK #-}Text -> {-# UNPACK #-}Int -> [MpvImage] -> Vars
- Web.Exhentai.Parsing.MPV: [$sel:apiUrl:Vars] :: Vars -> {-# UNPACK #-}Text
- Web.Exhentai.Parsing.MPV: [$sel:gid:Vars] :: Vars -> {-# UNPACK #-}Int
- Web.Exhentai.Parsing.MPV: [$sel:imageList:Vars] :: Vars -> [MpvImage]
- Web.Exhentai.Parsing.MPV: [$sel:key:MpvImage] :: MpvImage -> {-# UNPACK #-}Text
- Web.Exhentai.Parsing.MPV: [$sel:mpvkey:Vars] :: Vars -> {-# UNPACK #-}Text
- Web.Exhentai.Parsing.MPV: [$sel:name:MpvImage] :: MpvImage -> {-# UNPACK #-}Text
- Web.Exhentai.Parsing.MPV: [$sel:pageCount:Vars] :: Vars -> {-# UNPACK #-}Int
- Web.Exhentai.Parsing.MPV: [$sel:thumbnail:MpvImage] :: MpvImage -> {-# UNPACK #-}Text
- Web.Exhentai.Parsing.MPV: allScripts :: Traversal' Document Text
- Web.Exhentai.Parsing.MPV: data MpvImage
- Web.Exhentai.Parsing.MPV: data Vars
- Web.Exhentai.Parsing.MPV: extractEnv :: Text -> IO (Result Vars)
- Web.Exhentai.Parsing.MPV: instance Data.Aeson.Types.FromJSON.FromJSON Web.Exhentai.Parsing.MPV.MpvImage
- Web.Exhentai.Parsing.MPV: instance GHC.Classes.Eq Web.Exhentai.Parsing.MPV.MpvImage
- Web.Exhentai.Parsing.MPV: instance GHC.Classes.Eq Web.Exhentai.Parsing.MPV.Vars
- Web.Exhentai.Parsing.MPV: instance GHC.Generics.Generic Web.Exhentai.Parsing.MPV.MpvImage
- Web.Exhentai.Parsing.MPV: instance GHC.Generics.Generic Web.Exhentai.Parsing.MPV.Vars
- Web.Exhentai.Parsing.MPV: instance GHC.Show.Show Web.Exhentai.Parsing.MPV.MpvImage
- Web.Exhentai.Parsing.MPV: instance GHC.Show.Show Web.Exhentai.Parsing.MPV.Vars
- Web.Exhentai.Parsing.Search: galleryLength :: Traversal' Element GalleryLength
- Web.Exhentai.Types: ArtistCG :: GalleryCat
- Web.Exhentai.Types: AsianPorn :: GalleryCat
- Web.Exhentai.Types: AverageRating :: Float -> AverageRating
- Web.Exhentai.Types: Cosplay :: GalleryCat
- Web.Exhentai.Types: Doujinshi :: GalleryCat
- Web.Exhentai.Types: FavoriteCount :: Int -> FavoriteCount
- Web.Exhentai.Types: Gallery :: {-# UNPACK #-} !Int -> {-# UNPACK #-} !Text -> Gallery
- Web.Exhentai.Types: GalleryLength :: Int -> GalleryLength
- Web.Exhentai.Types: GameCG :: GalleryCat
- Web.Exhentai.Types: ImageSet :: GalleryCat
- Web.Exhentai.Types: Manga :: GalleryCat
- Web.Exhentai.Types: Misc :: GalleryCat
- Web.Exhentai.Types: NonH :: GalleryCat
- Web.Exhentai.Types: PopUpLink :: Text -> PopUpLink
- Web.Exhentai.Types: Private :: GalleryCat
- Web.Exhentai.Types: Western :: GalleryCat
- Web.Exhentai.Types: [$sel:galleryId:Gallery] :: Gallery -> {-# UNPACK #-} !Int
- Web.Exhentai.Types: [$sel:token:Gallery] :: Gallery -> {-# UNPACK #-} !Text
- Web.Exhentai.Types: [$sel:unFavoriteCount:FavoriteCount] :: FavoriteCount -> Int
- Web.Exhentai.Types: [$sel:unGalleryLength:GalleryLength] :: GalleryLength -> Int
- Web.Exhentai.Types: [$sel:unLink:PopUpLink] :: PopUpLink -> Text
- Web.Exhentai.Types: [$sel:unRating:AverageRating] :: AverageRating -> Float
- Web.Exhentai.Types: _AverageRating :: Prism' Text AverageRating
- Web.Exhentai.Types: _GalleryCat :: Prism' Text GalleryCat
- Web.Exhentai.Types: _GalleryLength :: Prism' Text GalleryLength
- Web.Exhentai.Types: _GalleryLink :: Prism' Text Gallery
- Web.Exhentai.Types: _PopUpLink :: Prism' Text PopUpLink
- Web.Exhentai.Types: allGalleryCats :: Set GalleryCat
- Web.Exhentai.Types: data Gallery
- Web.Exhentai.Types: data GalleryCat
- Web.Exhentai.Types: instance GHC.Classes.Eq Web.Exhentai.Types.AverageRating
- Web.Exhentai.Types: instance GHC.Classes.Eq Web.Exhentai.Types.FavoriteCount
- Web.Exhentai.Types: instance GHC.Classes.Eq Web.Exhentai.Types.Gallery
- Web.Exhentai.Types: instance GHC.Classes.Eq Web.Exhentai.Types.GalleryCat
- Web.Exhentai.Types: instance GHC.Classes.Eq Web.Exhentai.Types.GalleryLength
- Web.Exhentai.Types: instance GHC.Classes.Eq Web.Exhentai.Types.PopUpLink
- Web.Exhentai.Types: instance GHC.Classes.Ord Web.Exhentai.Types.GalleryCat
- Web.Exhentai.Types: instance GHC.Enum.Bounded Web.Exhentai.Types.GalleryCat
- Web.Exhentai.Types: instance GHC.Enum.Enum Web.Exhentai.Types.GalleryCat
- Web.Exhentai.Types: instance GHC.Generics.Generic Web.Exhentai.Types.Gallery
- Web.Exhentai.Types: instance GHC.Show.Show Web.Exhentai.Types.AverageRating
- Web.Exhentai.Types: instance GHC.Show.Show Web.Exhentai.Types.FavoriteCount
- Web.Exhentai.Types: instance GHC.Show.Show Web.Exhentai.Types.Gallery
- Web.Exhentai.Types: instance GHC.Show.Show Web.Exhentai.Types.GalleryCat
- Web.Exhentai.Types: instance GHC.Show.Show Web.Exhentai.Types.GalleryLength
- Web.Exhentai.Types: instance GHC.Show.Show Web.Exhentai.Types.PopUpLink
- Web.Exhentai.Types: newtype AverageRating
- Web.Exhentai.Types: newtype FavoriteCount
- Web.Exhentai.Types: newtype GalleryLength
- Web.Exhentai.Types: newtype PopUpLink
- Web.Exhentai.Types: parseAverageRating :: Text -> Maybe AverageRating
- Web.Exhentai.Types: parseFavoriteCount :: Text -> Maybe FavoriteCount
- Web.Exhentai.Types: parseGalleryLength :: Text -> Maybe GalleryLength
- Web.Exhentai.Types: parseGalleryLink :: Text -> Maybe Gallery
- Web.Exhentai.Types: parsePopUpLink :: Text -> Maybe PopUpLink
- Web.Exhentai.Types: parsePreviewLink :: Text -> Maybe Text
- Web.Exhentai.Types: readCat :: Text -> Maybe GalleryCat
- Web.Exhentai.Types: showCat :: GalleryCat -> Text
- Web.Exhentai.Types: toBitField :: Set GalleryCat -> Int
- Web.Exhentai.Types: toGalleryLink :: Gallery -> Text
- Web.Exhentai.Types: toMpvLink :: Gallery -> Text
- Web.Exhentai.Types: type Parser = Parsec Void Text
- Web.Exhentai.Types.CookieT: CookieEnv :: Policy -> MVar CookieJar -> {-# UNPACK #-} !Manager -> CookieEnv
- Web.Exhentai.Types.CookieT: CookieT :: ReaderT CookieEnv m a -> CookieT m a
- Web.Exhentai.Types.CookieT: Policy :: RetryPolicy -> Policy
- Web.Exhentai.Types.CookieT: [$sel:jarRef:CookieEnv] :: CookieEnv -> MVar CookieJar
- Web.Exhentai.Types.CookieT: [$sel:manager:CookieEnv] :: CookieEnv -> {-# UNPACK #-} !Manager
- Web.Exhentai.Types.CookieT: [$sel:policy:CookieEnv] :: CookieEnv -> Policy
- Web.Exhentai.Types.CookieT: [$sel:unCookieT:CookieT] :: CookieT m a -> ReaderT CookieEnv m a
- Web.Exhentai.Types.CookieT: attachFormData :: MonadHttp m => [Part] -> Request -> m Request
- Web.Exhentai.Types.CookieT: class (Monad m, MonadCatch m, MonadThrow m) => MonadHttp m
- Web.Exhentai.Types.CookieT: class (MonadMask m, MonadTime m, MonadHttp m, MonadIO m) => MonadHttpState m
- Web.Exhentai.Types.CookieT: data CookieEnv
- Web.Exhentai.Types.CookieT: formRequest :: MonadHttp m => String -> m Request
- Web.Exhentai.Types.CookieT: getRetryPolicy :: MonadHttp m => m Policy
- Web.Exhentai.Types.CookieT: instance (Control.Monad.Catch.MonadMask m, Control.Monad.IO.Unlift.MonadUnliftIO m, Control.Monad.Time.MonadTime m) => Web.Exhentai.Types.CookieT.MonadHttpState (Web.Exhentai.Types.CookieT.CookieT m)
- Web.Exhentai.Types.CookieT: instance (Control.Monad.IO.Class.MonadIO m, Control.Monad.IO.Unlift.MonadUnliftIO m, Control.Monad.Catch.MonadCatch m, Control.Monad.Catch.MonadThrow m) => Web.Exhentai.Types.CookieT.MonadHttp (Web.Exhentai.Types.CookieT.CookieT m)
- Web.Exhentai.Types.CookieT: instance (Web.Exhentai.Types.CookieT.MonadHttp m, Control.Monad.Trans.Class.MonadTrans f, GHC.Base.Monad (f m), Control.Monad.Catch.MonadCatch (f m), Control.Monad.Catch.MonadThrow (f m)) => Web.Exhentai.Types.CookieT.MonadHttp (f m)
- Web.Exhentai.Types.CookieT: instance (Web.Exhentai.Types.CookieT.MonadHttpState m, Control.Monad.Trans.Class.MonadTrans f, Control.Monad.Catch.MonadMask (f m), Control.Monad.Time.MonadTime (f m), Web.Exhentai.Types.CookieT.MonadHttp (f m), Control.Monad.IO.Class.MonadIO (f m)) => Web.Exhentai.Types.CookieT.MonadHttpState (f m)
- Web.Exhentai.Types.CookieT: instance Control.Monad.Base.MonadBase b m => Control.Monad.Base.MonadBase b (Web.Exhentai.Types.CookieT.CookieT m)
- Web.Exhentai.Types.CookieT: instance Control.Monad.Catch.MonadCatch m => Control.Monad.Catch.MonadCatch (Web.Exhentai.Types.CookieT.CookieT m)
- Web.Exhentai.Types.CookieT: instance Control.Monad.Catch.MonadMask m => Control.Monad.Catch.MonadMask (Web.Exhentai.Types.CookieT.CookieT m)
- Web.Exhentai.Types.CookieT: instance Control.Monad.Catch.MonadThrow m => Control.Monad.Catch.MonadThrow (Web.Exhentai.Types.CookieT.CookieT m)
- Web.Exhentai.Types.CookieT: instance Control.Monad.Error.Class.MonadError e m => Control.Monad.Error.Class.MonadError e (Web.Exhentai.Types.CookieT.CookieT m)
- Web.Exhentai.Types.CookieT: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Web.Exhentai.Types.CookieT.CookieT m)
- Web.Exhentai.Types.CookieT: instance Control.Monad.IO.Unlift.MonadUnliftIO m => Control.Monad.IO.Unlift.MonadUnliftIO (Web.Exhentai.Types.CookieT.CookieT m)
- Web.Exhentai.Types.CookieT: instance Control.Monad.Time.MonadTime m => Control.Monad.Time.MonadTime (Web.Exhentai.Types.CookieT.CookieT m)
- Web.Exhentai.Types.CookieT: instance Control.Monad.Trans.Class.MonadTrans Web.Exhentai.Types.CookieT.CookieT
- Web.Exhentai.Types.CookieT: instance Control.Monad.Trans.Control.MonadBaseControl b m => Control.Monad.Trans.Control.MonadBaseControl b (Web.Exhentai.Types.CookieT.CookieT m)
- Web.Exhentai.Types.CookieT: instance Control.Monad.Trans.Resource.Internal.MonadResource m => Control.Monad.Trans.Resource.Internal.MonadResource (Web.Exhentai.Types.CookieT.CookieT m)
- Web.Exhentai.Types.CookieT: instance GHC.Base.Applicative m => GHC.Base.Applicative (Web.Exhentai.Types.CookieT.CookieT m)
- Web.Exhentai.Types.CookieT: instance GHC.Base.Functor m => GHC.Base.Functor (Web.Exhentai.Types.CookieT.CookieT m)
- Web.Exhentai.Types.CookieT: instance GHC.Base.Monad m => Control.Monad.Reader.Class.MonadReader Web.Exhentai.Types.CookieT.CookieEnv (Web.Exhentai.Types.CookieT.CookieT m)
- Web.Exhentai.Types.CookieT: instance GHC.Base.Monad m => GHC.Base.Monad (Web.Exhentai.Types.CookieT.CookieT m)
- Web.Exhentai.Types.CookieT: instance GHC.Generics.Generic Web.Exhentai.Types.CookieT.CookieEnv
- Web.Exhentai.Types.CookieT: instance Network.HTTP.Client.Types.HasHttpManager Web.Exhentai.Types.CookieT.CookieEnv
- Web.Exhentai.Types.CookieT: modifyingJar :: MonadHttpState m => Request -> m ()
- Web.Exhentai.Types.CookieT: newtype CookieT m a
- Web.Exhentai.Types.CookieT: newtype Policy
- Web.Exhentai.Types.CookieT: openWithJar :: (MonadHttpState m, MonadIO n) => Request -> m (Response (ConduitT i ByteString n ()))
- Web.Exhentai.Types.CookieT: putCookieJar :: MonadHttpState m => CookieJar -> m ()
- Web.Exhentai.Types.CookieT: readCookieJar :: MonadHttpState m => m CookieJar
- Web.Exhentai.Types.CookieT: reqNoBody :: MonadHttp m => Request -> m (Response ())
- Web.Exhentai.Types.CookieT: respClose :: MonadHttp m => Response body -> m ()
- Web.Exhentai.Types.CookieT: respOpen :: (MonadHttp m, MonadIO n) => Request -> m (Response (ConduitT i ByteString n ()))
- Web.Exhentai.Types.CookieT: retryWhenTimeout :: MonadHttpState m => m a -> m a
- Web.Exhentai.Types.CookieT: runCookieT :: MonadIO m => RetryPolicy -> CookieT m a -> m a
- Web.Exhentai.Types.CookieT: takeCookieJar :: MonadHttpState m => m CookieJar
- Web.Exhentai.Types.CookieT: withJar :: (MonadHttpState m, MonadIO n) => Request -> (ConduitT i ByteString n () -> m a) -> m a
- Web.Exhentai.Utils: attributeSatisfies' :: Name -> (Maybe Text -> Bool) -> Traversal' Element Element
- Web.Exhentai.Utils: lower :: Traversal' Element Node
- Web.Exhentai.Utils: withoutAttribute :: Name -> Traversal' Element Element
+ Web.Exhentai: ExtractionFailure :: ExhentaiError
+ Web.Exhentai: JSONParseFailure :: String -> ExhentaiError
+ Web.Exhentai: XMLParseFailure :: Text -> Text -> ExhentaiError
+ Web.Exhentai: [$sel:reason:JSONParseFailure] :: ExhentaiError -> Text
+ Web.Exhentai: [$sel:url:JSONParseFailure] :: ExhentaiError -> Text
+ Web.Exhentai: [PutCookie] :: CookieJar -> Cookie m ()
+ Web.Exhentai: [ReadCookie] :: Cookie m CookieJar
+ Web.Exhentai: [RunConduitIO] :: ConduitT () Void IO a -> ConduitIO m a
+ Web.Exhentai: [TakeCookie] :: Cookie m CookieJar
+ Web.Exhentai: conduitIOToIO :: Eff (Embed IO) m => ConduitIOToIOC m a -> m a
+ Web.Exhentai: cookieToIO :: (Eff (Embed IO) m, Threaders '[ReaderThreads] m p) => CookieToIOC m a -> m a
+ Web.Exhentai: data ConduitIO :: Effect
+ Web.Exhentai: data ConduitIOH
+ Web.Exhentai: data Cookie :: Effect
+ Web.Exhentai: data CookieH
+ Web.Exhentai: data ExhentaiError
+ Web.Exhentai: data Http :: Effect
+ Web.Exhentai: data HttpH
+ Web.Exhentai: exhToIO :: (Effs '[Embed IO, Reader Manager] m, Threaders '[ReaderThreads] m p) => ExhC m a -> m a
+ Web.Exhentai: formRequest :: Effs '[Http, Error HttpException] m => String -> m Request
+ Web.Exhentai: genBoundary :: Eff Http m => m ByteString
+ Web.Exhentai: httpToIO :: (Effs '[Embed IO, Reader Manager] m, Threaders '[ReaderThreads] m p) => HttpToIOC m a -> m a
+ Web.Exhentai: putCookie :: Eff Cookie m => CookieJar -> m ()
+ Web.Exhentai: readCookie :: Eff Cookie m => m CookieJar
+ Web.Exhentai: respClose :: Eff Http m => Response a -> m ()
+ Web.Exhentai: respOpen :: Effs '[Http, Error HttpException] m => Request -> m (Response BodyReader)
+ Web.Exhentai: runConduitIO :: Eff ConduitIO m => ConduitT () Void IO a -> m a
+ Web.Exhentai: takeCookie :: Eff Cookie m => m CookieJar
+ Web.Exhentai: type ConduitIOToIOC = InterpretC ConduitIOH ConduitIO
+ Web.Exhentai: type CookieToIOC = CompositionC '[CookieToReaderC, ReaderC (MVar CookieJar)]
+ Web.Exhentai: type ExhC = CompositionC '[HttpToIOC, ConduitIOToIOC, CookieToIOC]
+ Web.Exhentai: type HttpToIOC = InterpretC HttpH Http
+ Web.Exhentai.API.Auth: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.ByteString.Internal.ByteString, b GHC.Types.~ Data.ByteString.Internal.ByteString) => Optics.Internal.Optic.LabelOptic "password" k Web.Exhentai.API.Auth.Credential Web.Exhentai.API.Auth.Credential a b
+ Web.Exhentai.API.Auth: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.ByteString.Internal.ByteString, b GHC.Types.~ Data.ByteString.Internal.ByteString) => Optics.Internal.Optic.LabelOptic "username" k Web.Exhentai.API.Auth.Credential Web.Exhentai.API.Auth.Credential a b
+ Web.Exhentai.API.Gallery: Artist :: TagCategory
+ Web.Exhentai.API.Gallery: ArtistCG :: GalleryCategory
+ Web.Exhentai.API.Gallery: AsianPorn :: GalleryCategory
+ Web.Exhentai.API.Gallery: Character :: TagCategory
+ Web.Exhentai.API.Gallery: Cosplay :: GalleryCategory
+ Web.Exhentai.API.Gallery: Doujinshi :: GalleryCategory
+ Web.Exhentai.API.Gallery: Female :: TagCategory
+ Web.Exhentai.API.Gallery: Gallery :: {-# UNPACK #-} !Int -> {-# UNPACK #-} !Text -> Gallery
+ Web.Exhentai.API.Gallery: GameCG :: GalleryCategory
+ Web.Exhentai.API.Gallery: Group :: TagCategory
+ Web.Exhentai.API.Gallery: ImageSet :: GalleryCategory
+ Web.Exhentai.API.Gallery: Language :: TagCategory
+ Web.Exhentai.API.Gallery: Male :: TagCategory
+ Web.Exhentai.API.Gallery: Manga :: GalleryCategory
+ Web.Exhentai.API.Gallery: Misc :: GalleryCategory
+ Web.Exhentai.API.Gallery: Misc' :: TagCategory
+ Web.Exhentai.API.Gallery: NonH :: GalleryCategory
+ Web.Exhentai.API.Gallery: Parody :: TagCategory
+ Web.Exhentai.API.Gallery: Private :: GalleryCategory
+ Web.Exhentai.API.Gallery: Reclass :: TagCategory
+ Web.Exhentai.API.Gallery: Western :: GalleryCategory
+ Web.Exhentai.API.Gallery: [$sel:galleryId:Gallery] :: Gallery -> {-# UNPACK #-} !Int
+ Web.Exhentai.API.Gallery: [$sel:token:Gallery] :: Gallery -> {-# UNPACK #-} !Text
+ Web.Exhentai.API.Gallery: allGalleryCats :: Set GalleryCategory
+ Web.Exhentai.API.Gallery: data Gallery
+ Web.Exhentai.API.Gallery: data GalleryCategory
+ Web.Exhentai.API.Gallery: data TagCategory
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Text.Internal.Text, b GHC.Types.~ Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "archiverLink" k Web.Exhentai.API.Gallery.GalleryInfo Web.Exhentai.API.Gallery.GalleryInfo a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Text.Internal.Text, b GHC.Types.~ Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "language" k Web.Exhentai.API.Gallery.GalleryInfo Web.Exhentai.API.Gallery.GalleryInfo a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Text.Internal.Text, b GHC.Types.~ Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "previewLink" k Web.Exhentai.API.Gallery.GalleryInfo Web.Exhentai.API.Gallery.GalleryInfo a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Text.Internal.Text, b GHC.Types.~ Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "title" k Web.Exhentai.API.Gallery.GalleryInfo Web.Exhentai.API.Gallery.GalleryInfo a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Text.Internal.Text, b GHC.Types.~ Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "torrentLink" k Web.Exhentai.API.Gallery.GalleryInfo Web.Exhentai.API.Gallery.GalleryInfo a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Text.Internal.Text, b GHC.Types.~ Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "uploader" k Web.Exhentai.API.Gallery.GalleryInfo Web.Exhentai.API.Gallery.GalleryInfo a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Time.Clock.Internal.UTCTime.UTCTime, b GHC.Types.~ Data.Time.Clock.Internal.UTCTime.UTCTime) => Optics.Internal.Optic.LabelOptic "uploadTime" k Web.Exhentai.API.Gallery.GalleryInfo Web.Exhentai.API.Gallery.GalleryInfo a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Maybe.Maybe Data.Text.Internal.Text, b GHC.Types.~ GHC.Maybe.Maybe Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "jaTitle" k Web.Exhentai.API.Gallery.GalleryInfo Web.Exhentai.API.Gallery.GalleryInfo a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Maybe.Maybe Web.Exhentai.API.Gallery.Gallery, b GHC.Types.~ GHC.Maybe.Maybe Web.Exhentai.API.Gallery.Gallery) => Optics.Internal.Optic.LabelOptic "newer" k Web.Exhentai.API.Gallery.GalleryInfo Web.Exhentai.API.Gallery.GalleryInfo a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Maybe.Maybe Web.Exhentai.API.Gallery.Gallery, b GHC.Types.~ GHC.Maybe.Maybe Web.Exhentai.API.Gallery.Gallery) => Optics.Internal.Optic.LabelOptic "parent" k Web.Exhentai.API.Gallery.GalleryInfo Web.Exhentai.API.Gallery.GalleryInfo a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Types.Double, b GHC.Types.~ GHC.Types.Double) => Optics.Internal.Optic.LabelOptic "rating" k Web.Exhentai.API.Gallery.GalleryInfo Web.Exhentai.API.Gallery.GalleryInfo a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Types.Int, b GHC.Types.~ GHC.Types.Int) => Optics.Internal.Optic.LabelOptic "favoriteCount" k Web.Exhentai.API.Gallery.GalleryInfo Web.Exhentai.API.Gallery.GalleryInfo a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Types.Int, b GHC.Types.~ GHC.Types.Int) => Optics.Internal.Optic.LabelOptic "length" k Web.Exhentai.API.Gallery.GalleryInfo Web.Exhentai.API.Gallery.GalleryInfo a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Types.Int, b GHC.Types.~ GHC.Types.Int) => Optics.Internal.Optic.LabelOptic "ratingCount" k Web.Exhentai.API.Gallery.GalleryInfo Web.Exhentai.API.Gallery.GalleryInfo a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Web.Exhentai.API.Gallery.GalleryCategory, b GHC.Types.~ Web.Exhentai.API.Gallery.GalleryCategory) => Optics.Internal.Optic.LabelOptic "category" k Web.Exhentai.API.Gallery.GalleryInfo Web.Exhentai.API.Gallery.GalleryInfo a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Web.Exhentai.API.Gallery.Visibility, b GHC.Types.~ Web.Exhentai.API.Gallery.Visibility) => Optics.Internal.Optic.LabelOptic "visibility" k Web.Exhentai.API.Gallery.GalleryInfo Web.Exhentai.API.Gallery.GalleryInfo a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ [(Web.Exhentai.API.Gallery.TagCategory, [Data.Text.Internal.Text])], b GHC.Types.~ [(Web.Exhentai.API.Gallery.TagCategory, [Data.Text.Internal.Text])]) => Optics.Internal.Optic.LabelOptic "tags" k Web.Exhentai.API.Gallery.GalleryInfo Web.Exhentai.API.Gallery.GalleryInfo a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Artist" k Web.Exhentai.API.Gallery.TagCategory Web.Exhentai.API.Gallery.TagCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_ArtistCG" k Web.Exhentai.API.Gallery.GalleryCategory Web.Exhentai.API.Gallery.GalleryCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_AsianPorn" k Web.Exhentai.API.Gallery.GalleryCategory Web.Exhentai.API.Gallery.GalleryCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Character" k Web.Exhentai.API.Gallery.TagCategory Web.Exhentai.API.Gallery.TagCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Cosplay" k Web.Exhentai.API.Gallery.GalleryCategory Web.Exhentai.API.Gallery.GalleryCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Doujinshi" k Web.Exhentai.API.Gallery.GalleryCategory Web.Exhentai.API.Gallery.GalleryCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Expunged" k Web.Exhentai.API.Gallery.Visibility Web.Exhentai.API.Gallery.Visibility a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Female" k Web.Exhentai.API.Gallery.TagCategory Web.Exhentai.API.Gallery.TagCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_GameCG" k Web.Exhentai.API.Gallery.GalleryCategory Web.Exhentai.API.Gallery.GalleryCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Group" k Web.Exhentai.API.Gallery.TagCategory Web.Exhentai.API.Gallery.TagCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_ImageSet" k Web.Exhentai.API.Gallery.GalleryCategory Web.Exhentai.API.Gallery.GalleryCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Language" k Web.Exhentai.API.Gallery.TagCategory Web.Exhentai.API.Gallery.TagCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Male" k Web.Exhentai.API.Gallery.TagCategory Web.Exhentai.API.Gallery.TagCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Manga" k Web.Exhentai.API.Gallery.GalleryCategory Web.Exhentai.API.Gallery.GalleryCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Misc" k Web.Exhentai.API.Gallery.GalleryCategory Web.Exhentai.API.Gallery.GalleryCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Misc'" k Web.Exhentai.API.Gallery.TagCategory Web.Exhentai.API.Gallery.TagCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_NonH" k Web.Exhentai.API.Gallery.GalleryCategory Web.Exhentai.API.Gallery.GalleryCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Parody" k Web.Exhentai.API.Gallery.TagCategory Web.Exhentai.API.Gallery.TagCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Private" k Web.Exhentai.API.Gallery.GalleryCategory Web.Exhentai.API.Gallery.GalleryCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Reclass" k Web.Exhentai.API.Gallery.TagCategory Web.Exhentai.API.Gallery.TagCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Replaced" k Web.Exhentai.API.Gallery.Visibility Web.Exhentai.API.Gallery.Visibility a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Visible" k Web.Exhentai.API.Gallery.Visibility Web.Exhentai.API.Gallery.Visibility a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ (), b GHC.Types.~ ()) => Optics.Internal.Optic.LabelOptic "_Western" k Web.Exhentai.API.Gallery.GalleryCategory Web.Exhentai.API.Gallery.GalleryCategory a b
+ Web.Exhentai.API.Gallery: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ Data.Text.Internal.Text, b GHC.Types.~ Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "_Unknown" k Web.Exhentai.API.Gallery.Visibility Web.Exhentai.API.Gallery.Visibility a b
+ Web.Exhentai.API.Gallery: instance GHC.Classes.Eq Web.Exhentai.API.Gallery.Gallery
+ Web.Exhentai.API.Gallery: instance GHC.Classes.Eq Web.Exhentai.API.Gallery.GalleryCategory
+ Web.Exhentai.API.Gallery: instance GHC.Classes.Eq Web.Exhentai.API.Gallery.TagCategory
+ Web.Exhentai.API.Gallery: instance GHC.Classes.Ord Web.Exhentai.API.Gallery.GalleryCategory
+ Web.Exhentai.API.Gallery: instance GHC.Enum.Bounded Web.Exhentai.API.Gallery.GalleryCategory
+ Web.Exhentai.API.Gallery: instance GHC.Enum.Enum Web.Exhentai.API.Gallery.GalleryCategory
+ Web.Exhentai.API.Gallery: instance GHC.Enum.Enum Web.Exhentai.API.Gallery.TagCategory
+ Web.Exhentai.API.Gallery: instance GHC.Show.Show Web.Exhentai.API.Gallery.Gallery
+ Web.Exhentai.API.Gallery: instance GHC.Show.Show Web.Exhentai.API.Gallery.GalleryCategory
+ Web.Exhentai.API.Gallery: instance GHC.Show.Show Web.Exhentai.API.Gallery.TagCategory
+ Web.Exhentai.API.Gallery: parseGalleryLink :: Text -> Maybe Gallery
+ Web.Exhentai.API.MPV: buildRequest :: Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket, Throw ExhentaiError] m => Gallery -> m [DispatchRequest]
+ Web.Exhentai.API.MPV: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Text.Internal.Text, b GHC.Types.~ Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "dimension" k Web.Exhentai.API.MPV.DispatchResult Web.Exhentai.API.MPV.DispatchResult a b
+ Web.Exhentai.API.MPV: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Text.Internal.Text, b GHC.Types.~ Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "galleryPath" k Web.Exhentai.API.MPV.DispatchResult Web.Exhentai.API.MPV.DispatchResult a b
+ Web.Exhentai.API.MPV: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Text.Internal.Text, b GHC.Types.~ Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "imgKey" k Web.Exhentai.API.MPV.DispatchRequest Web.Exhentai.API.MPV.DispatchRequest a b
+ Web.Exhentai.API.MPV: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Text.Internal.Text, b GHC.Types.~ Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "imgLink" k Web.Exhentai.API.MPV.DispatchResult Web.Exhentai.API.MPV.DispatchResult a b
+ Web.Exhentai.API.MPV: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Text.Internal.Text, b GHC.Types.~ Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "mpvKey" k Web.Exhentai.API.MPV.DispatchRequest Web.Exhentai.API.MPV.DispatchRequest a b
+ Web.Exhentai.API.MPV: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Text.Internal.Text, b GHC.Types.~ Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "origImgPath" k Web.Exhentai.API.MPV.DispatchResult Web.Exhentai.API.MPV.DispatchResult a b
+ Web.Exhentai.API.MPV: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Text.Internal.Text, b GHC.Types.~ Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "searchPath" k Web.Exhentai.API.MPV.DispatchResult Web.Exhentai.API.MPV.DispatchResult a b
+ Web.Exhentai.API.MPV: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Maybe.Maybe Web.Exhentai.API.MPV.Server, b GHC.Types.~ GHC.Maybe.Maybe Web.Exhentai.API.MPV.Server) => Optics.Internal.Optic.LabelOptic "exclude" k Web.Exhentai.API.MPV.DispatchRequest Web.Exhentai.API.MPV.DispatchRequest a b
+ Web.Exhentai.API.MPV: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Types.Int, b GHC.Types.~ GHC.Types.Int) => Optics.Internal.Optic.LabelOptic "galleryId" k Web.Exhentai.API.MPV.DispatchRequest Web.Exhentai.API.MPV.DispatchRequest a b
+ Web.Exhentai.API.MPV: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Types.Int, b GHC.Types.~ GHC.Types.Int) => Optics.Internal.Optic.LabelOptic "page" k Web.Exhentai.API.MPV.DispatchRequest Web.Exhentai.API.MPV.DispatchRequest a b
+ Web.Exhentai.API.MPV: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Web.Exhentai.API.MPV.Dim, b GHC.Types.~ Web.Exhentai.API.MPV.Dim) => Optics.Internal.Optic.LabelOptic "height" k Web.Exhentai.API.MPV.DispatchResult Web.Exhentai.API.MPV.DispatchResult a b
+ Web.Exhentai.API.MPV: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Web.Exhentai.API.MPV.Dim, b GHC.Types.~ Web.Exhentai.API.MPV.Dim) => Optics.Internal.Optic.LabelOptic "width" k Web.Exhentai.API.MPV.DispatchResult Web.Exhentai.API.MPV.DispatchResult a b
+ Web.Exhentai.API.MPV: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Web.Exhentai.API.MPV.Server, b GHC.Types.~ Web.Exhentai.API.MPV.Server) => Optics.Internal.Optic.LabelOptic "server" k Web.Exhentai.API.MPV.DispatchResult Web.Exhentai.API.MPV.DispatchResult a b
+ Web.Exhentai.API.MPV: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ Data.Text.Internal.Text, b GHC.Types.~ Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "_Other" k Web.Exhentai.API.MPV.Server Web.Exhentai.API.MPV.Server a b
+ Web.Exhentai.API.MPV: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Prism, a GHC.Types.~ GHC.Types.Int, b GHC.Types.~ GHC.Types.Int) => Optics.Internal.Optic.LabelOptic "_HAtH" k Web.Exhentai.API.MPV.Server Web.Exhentai.API.MPV.Server a b
+ Web.Exhentai.API.MPV: instance (k GHC.Types.~ Optics.Internal.Optic.Types.An_Iso, a GHC.Types.~ GHC.Types.Int, b GHC.Types.~ GHC.Types.Int) => Optics.Internal.Optic.LabelOptic "_Dim" k Web.Exhentai.API.MPV.Dim Web.Exhentai.API.MPV.Dim a b
+ Web.Exhentai.API.Search: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ Data.Text.Internal.Text, b GHC.Types.~ Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "searchString" k Web.Exhentai.API.Search.SearchQuery Web.Exhentai.API.Search.SearchQuery a b
+ Web.Exhentai.API.Search: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Maybe.Maybe (Data.Set.Internal.Set Web.Exhentai.API.Gallery.GalleryCategory), b GHC.Types.~ GHC.Maybe.Maybe (Data.Set.Internal.Set Web.Exhentai.API.Gallery.GalleryCategory)) => Optics.Internal.Optic.LabelOptic "categories" k Web.Exhentai.API.Search.SearchQuery Web.Exhentai.API.Search.SearchQuery a b
+ Web.Exhentai.API.Search: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Maybe.Maybe Data.Text.Internal.Text, b GHC.Types.~ GHC.Maybe.Maybe Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "nextPage" k Web.Exhentai.API.Search.SearchResult Web.Exhentai.API.Search.SearchResult a b
+ Web.Exhentai.API.Search: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Maybe.Maybe Data.Text.Internal.Text, b GHC.Types.~ GHC.Maybe.Maybe Data.Text.Internal.Text) => Optics.Internal.Optic.LabelOptic "prevPage" k Web.Exhentai.API.Search.SearchResult Web.Exhentai.API.Search.SearchResult a b
+ Web.Exhentai.API.Search: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ [Web.Exhentai.API.Gallery.Gallery], b GHC.Types.~ [Web.Exhentai.API.Gallery.Gallery]) => Optics.Internal.Optic.LabelOptic "galleries" k Web.Exhentai.API.Search.SearchResult Web.Exhentai.API.Search.SearchResult a b
+ Web.Exhentai.Parsing.Image: nextImage :: Traversal' Element Text
+ Web.Exhentai.Utils: (!!) :: [a] -> Int -> Maybe a
- Web.Exhentai.API.Archiver: streamOriginal :: (MonadHttpState m, MonadIO n) => Text -> ContT r m (Response (ConduitT i ByteString n ()))
+ Web.Exhentai.API.Archiver: streamOriginal :: Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket, Throw ExhentaiError] m => Text -> ContT r m (Response (ConduitT i ByteString IO ()))
- Web.Exhentai.API.Archiver: streamResampled :: (MonadHttpState m, MonadIO n) => Text -> ContT r m (Response (ConduitT i ByteString n ()))
+ Web.Exhentai.API.Archiver: streamResampled :: Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket, Throw ExhentaiError] m => Text -> ContT r m (Response (ConduitT i ByteString IO ()))
- Web.Exhentai.API.Auth: auth :: MonadHttpState m => Credential -> m ()
+ Web.Exhentai.API.Auth: auth :: Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket] m => Credential -> m ()
- Web.Exhentai.API.Gallery: GalleryInfo :: {-# UNPACK #-}Text -> {-# UNPACK #-}Text -> GalleryCat -> Maybe Text -> {-# UNPACK #-}Text -> {-# UNPACK #-}Float -> {-# UNPACK #-}Int -> {-# UNPACK #-}Int -> [(TagCategory, [Text])] -> {-# UNPACK #-}UTCTime -> Maybe Gallery -> Maybe Gallery -> Visibility -> {-# UNPACK #-}Text -> {-# UNPACK #-}Int -> {-# UNPACK #-}Text -> {-# UNPACK #-}Text -> GalleryInfo
+ Web.Exhentai.API.Gallery: GalleryInfo :: {-# UNPACK #-}Text -> {-# UNPACK #-}Text -> GalleryCategory -> Maybe Text -> {-# UNPACK #-}Text -> {-# UNPACK #-}Double -> {-# UNPACK #-}Int -> {-# UNPACK #-}Int -> [(TagCategory, [Text])] -> {-# UNPACK #-}UTCTime -> Maybe Gallery -> Maybe Gallery -> Visibility -> {-# UNPACK #-}Text -> {-# UNPACK #-}Int -> {-# UNPACK #-}Text -> {-# UNPACK #-}Text -> GalleryInfo
- Web.Exhentai.API.Gallery: [$sel:category:GalleryInfo] :: GalleryInfo -> GalleryCat
+ Web.Exhentai.API.Gallery: [$sel:category:GalleryInfo] :: GalleryInfo -> GalleryCategory
- Web.Exhentai.API.Gallery: [$sel:rating:GalleryInfo] :: GalleryInfo -> {-# UNPACK #-}Float
+ Web.Exhentai.API.Gallery: [$sel:rating:GalleryInfo] :: GalleryInfo -> {-# UNPACK #-}Double
- Web.Exhentai.API.Gallery: fetchGalleryInfo :: MonadHttpState m => Gallery -> m GalleryInfo
+ Web.Exhentai.API.Gallery: fetchGalleryInfo :: Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket, Throw ExhentaiError] m => Gallery -> m GalleryInfo
- Web.Exhentai.API.MPV: DispatchRequest :: Int -> Int -> Text -> Text -> Maybe Server -> DispatchRequest
+ Web.Exhentai.API.MPV: DispatchRequest :: {-# UNPACK #-}Int -> {-# UNPACK #-}Int -> {-# UNPACK #-}Text -> {-# UNPACK #-}Text -> Maybe Server -> DispatchRequest
- Web.Exhentai.API.MPV: HAtH :: Int -> Server
+ Web.Exhentai.API.MPV: HAtH :: {-# UNPACK #-}Int -> Server
- Web.Exhentai.API.MPV: Other :: Text -> Server
+ Web.Exhentai.API.MPV: Other :: {-# UNPACK #-}Text -> Server
- Web.Exhentai.API.MPV: [$sel:galleryId:DispatchRequest] :: DispatchRequest -> Int
+ Web.Exhentai.API.MPV: [$sel:galleryId:DispatchRequest] :: DispatchRequest -> {-# UNPACK #-}Int
- Web.Exhentai.API.MPV: [$sel:imgKey:DispatchRequest] :: DispatchRequest -> Text
+ Web.Exhentai.API.MPV: [$sel:imgKey:DispatchRequest] :: DispatchRequest -> {-# UNPACK #-}Text
- Web.Exhentai.API.MPV: [$sel:mpvKey:DispatchRequest] :: DispatchRequest -> Text
+ Web.Exhentai.API.MPV: [$sel:mpvKey:DispatchRequest] :: DispatchRequest -> {-# UNPACK #-}Text
- Web.Exhentai.API.MPV: [$sel:page:DispatchRequest] :: DispatchRequest -> Int
+ Web.Exhentai.API.MPV: [$sel:page:DispatchRequest] :: DispatchRequest -> {-# UNPACK #-}Int
- Web.Exhentai.API.MPV: fetchImage :: (MonadHttpState m, MonadIO n) => DispatchRequest -> ContT r m (Response (ConduitT i ByteString n ()))
+ Web.Exhentai.API.MPV: fetchImage :: Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket, Throw ExhentaiError] m => DispatchRequest -> ContT r m (Response (ConduitT i ByteString IO ()))
- Web.Exhentai.API.Search: SearchQuery :: Maybe (Set GalleryCat) -> {-# UNPACK #-}Text -> SearchQuery
+ Web.Exhentai.API.Search: SearchQuery :: Maybe (Set GalleryCategory) -> {-# UNPACK #-}Text -> SearchQuery
- Web.Exhentai.API.Search: [$sel:categories:SearchQuery] :: SearchQuery -> Maybe (Set GalleryCat)
+ Web.Exhentai.API.Search: [$sel:categories:SearchQuery] :: SearchQuery -> Maybe (Set GalleryCategory)
- Web.Exhentai.API.Search: fetchSearchPage :: MonadHttpState m => Text -> m SearchResult
+ Web.Exhentai.API.Search: fetchSearchPage :: Effs '[Http, Error HttpException, ConduitIO, Cookie, Bracket] m => Text -> m SearchResult
- Web.Exhentai.API.Search: search :: MonadHttpState m => SearchQuery -> m SearchResult
+ Web.Exhentai.API.Search: search :: Effs '[Http, Error HttpException, ConduitIO, Cookie, Bracket] m => SearchQuery -> m SearchResult
- Web.Exhentai.API.Search: searchRecur :: MonadHttpState m => SearchQuery -> ConduitT i Gallery m ()
+ Web.Exhentai.API.Search: searchRecur :: Effs '[Http, Error HttpException, ConduitIO, Cookie, Bracket] m => SearchQuery -> ConduitT i Gallery m ()
- Web.Exhentai.API.Search: searchRecurResumable :: MonadHttpState m => SearchQuery -> ConduitT i (Either Text Gallery) m ()
+ Web.Exhentai.API.Search: searchRecurResumable :: Effs '[Http, Error HttpException, ConduitIO, Cookie, Bracket] m => SearchQuery -> ConduitT i (Either Text Gallery) m ()
- Web.Exhentai.API.Watched: fetchPopular :: MonadHttpState m => m [Gallery]
+ Web.Exhentai.API.Watched: fetchPopular :: Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket] m => m [Gallery]
- Web.Exhentai.API.Watched: fetchWatched :: MonadHttpState m => m [Gallery]
+ Web.Exhentai.API.Watched: fetchWatched :: Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket] m => m [Gallery]
- Web.Exhentai.Parsing.Gallery: averageRating :: Traversal' Element AverageRating
+ Web.Exhentai.Parsing.Gallery: averageRating :: Traversal' Element Text
- Web.Exhentai.Parsing.Gallery: category :: Traversal' Element GalleryCat
+ Web.Exhentai.Parsing.Gallery: category :: Traversal' Element Text
- Web.Exhentai.Parsing.Gallery: meta :: Traversal' Element Element
+ Web.Exhentai.Parsing.Gallery: meta :: AffineTraversal' Element Element
- Web.Exhentai.Parsing.Gallery: newer :: Traversal' Element Gallery
+ Web.Exhentai.Parsing.Gallery: newer :: Traversal' Element Text
- Web.Exhentai.Parsing.Gallery: parent :: Traversal' Element Gallery
+ Web.Exhentai.Parsing.Gallery: parent :: Traversal' Element Text
- Web.Exhentai.Parsing.Gallery: popupLink :: Traversal' Element PopUpLink
+ Web.Exhentai.Parsing.Gallery: popupLink :: Traversal' Element Text
- Web.Exhentai.Parsing.Gallery: tagCategory :: Traversal' Element TagCategory
+ Web.Exhentai.Parsing.Gallery: tagCategory :: Traversal' Element Text
- Web.Exhentai.Parsing.Search: galleryLink :: Traversal' Element Gallery
+ Web.Exhentai.Parsing.Search: galleryLink :: Traversal' Element Text
- Web.Exhentai.Utils: (^..:) :: Document -> Fold Element a -> [a]
+ Web.Exhentai.Utils: (^..:) :: (Is (Join A_Traversal l) A_Fold, Is l (Join A_Traversal l), Is A_Traversal (Join A_Traversal l)) => Document -> Optic l is Element Element a a -> [a]
- Web.Exhentai.Utils: (^?:) :: Document -> Fold Element a -> Maybe a
+ Web.Exhentai.Utils: (^?:) :: (Is (Join A_Traversal l) A_Fold, Is l (Join A_Traversal l), Is A_Traversal (Join A_Traversal l)) => Document -> Optic l is Element Element a a -> Maybe a
- Web.Exhentai.Utils: a :: Traversal' Element Element
+ Web.Exhentai.Utils: a :: AffineTraversal' Element Element
- Web.Exhentai.Utils: cl :: Text -> Traversal' Element Element
+ Web.Exhentai.Utils: cl :: Text -> AffineTraversal' Element Element
- Web.Exhentai.Utils: div :: Traversal' Element Element
+ Web.Exhentai.Utils: div :: AffineTraversal' Element Element
- Web.Exhentai.Utils: h1 :: Traversal' Element Element
+ Web.Exhentai.Utils: h1 :: AffineTraversal' Element Element
- Web.Exhentai.Utils: htmlRequest :: MonadHttpState m => Request -> m Document
+ Web.Exhentai.Utils: htmlRequest :: Effs '[Http, Error HttpException, ConduitIO, Cookie, Bracket] m => Request -> m Document
- Web.Exhentai.Utils: htmlRequest' :: MonadHttpState m => Text -> m Document
+ Web.Exhentai.Utils: htmlRequest' :: Effs '[Http, Error HttpException, ConduitIO, Cookie, Bracket] m => Text -> m Document
- Web.Exhentai.Utils: id :: Text -> Traversal' Element Element
+ Web.Exhentai.Utils: id :: Text -> AffineTraversal' Element Element
- Web.Exhentai.Utils: img :: Traversal' Element Element
+ Web.Exhentai.Utils: img :: AffineTraversal' Element Element
- Web.Exhentai.Utils: jsonRequest :: (FromJSON a, MonadHttpState m) => Request -> m (Either String a)
+ Web.Exhentai.Utils: jsonRequest :: (FromJSON a, Effs '[Http, Error HttpException, ConduitIO, Cookie, Bracket] m) => Request -> m (Either String a)
- Web.Exhentai.Utils: scripts :: Traversal' Element Element
+ Web.Exhentai.Utils: scripts :: AffineTraversal' Element Element
- Web.Exhentai.Utils: table :: Traversal' Element Element
+ Web.Exhentai.Utils: table :: AffineTraversal' Element Element
- Web.Exhentai.Utils: td :: Traversal' Element Element
+ Web.Exhentai.Utils: td :: AffineTraversal' Element Element
- Web.Exhentai.Utils: tr :: Traversal' Element Element
+ Web.Exhentai.Utils: tr :: AffineTraversal' Element Element

Files

CHANGELOG.md view
@@ -8,3 +8,7 @@  - Bug fixes - Add a resumable variant of the search function++## 1.0.0++- Rewrite using in-other-words
exh.cabal view
@@ -1,6 +1,6 @@ cabal-version:   3.0 name:            exh-version:         0.2.0+version:         1.0.0 synopsis:        A library for crawling exhentai description:   A library for crawling exhentai, with the support of streaming@@ -11,7 +11,7 @@ author:          Poscat maintainer:      Poscat <poscat@mail.poscat.moe> copyright:       Copyright (c) Poscat 2021-stability:       alpha+stability:       experimental homepage:        https://github.com/poscat0x04/exh bug-reports:     https://github.com/poscat0x04/exh/issues extra-doc-files:@@ -20,29 +20,23 @@  common common-attrs   build-depends:-    , aeson              >=1.4.7   && <1.6-    , base               >=4.10    && <5-    , bytestring         >=0.10.12 && <0.12-    , conduit            ^>=1.3.4-    , containers         ^>=0.6.2-    , exceptions         ^>=0.10.4-    , html-conduit       ^>=1.3.2-    , http-client        >=0.6.4   && <0.8-    , http-client-tls    ^>=0.3.5-    , http-conduit       ^>=2.3.7-    , lens               ^>=4.19-    , megaparsec         ^>=9.0-    , monad-control      ^>=1.0.2-    , monad-time         ^>=0.3-    , mtl                ^>=2.2-    , quickjs-hs         ^>=0.1.2.4-    , retry              ^>=0.8.1-    , text               ^>=1.2.3-    , time               >=1.9.3   && <1.12-    , transformers       ^>=0.5.6-    , transformers-base  ^>=0.4.5-    , xml-conduit        >=1.8.0   && <1.10-    , xml-lens           >=0.2     && <0.3+    , aeson                >=1.4.7   && <1.6+    , base                 >=4.10    && <5+    , bytestring           >=0.10.12 && <0.12+    , conduit              ^>=1.3.4+    , containers           ^>=0.6.2+    , html-conduit         ^>=1.3.2+    , http-client          >=0.6.4   && <0.8+    , in-other-words+    , language-javascript+    , megaparsec           ^>=9.0+    , optics-core+    , optics-th+    , text                 ^>=1.2.3+    , time                 >=1.9.3   && <1.12+    , transformers+    , xml-conduit          >=1.8.0   && <1.10+    , xml-optics    default-language:   Haskell2010   default-extensions:@@ -86,16 +80,16 @@     Web.Exhentai.API.MPV     Web.Exhentai.API.Search     Web.Exhentai.API.Watched-    Web.Exhentai.Errors     Web.Exhentai.Parsing.Gallery     Web.Exhentai.Parsing.Image-    Web.Exhentai.Parsing.MPV     Web.Exhentai.Parsing.Search-    Web.Exhentai.Types-    Web.Exhentai.Types.CookieT     Web.Exhentai.Utils    other-modules:+    Control.Effect.Exh+    Language.JavaScript.Extraction+    Web.Exhentai.Errors+   hs-source-dirs:  src  test-suite exh-test@@ -104,6 +98,7 @@   build-depends:     , exh     , hspec+    , http-client-tls    hs-source-dirs: test   main-is:        Spec.hs
+ src/Control/Effect/Exh.hs view
@@ -0,0 +1,195 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE UndecidableInstances #-}++-- | Internal module+module Control.Effect.Exh where++import Conduit+import Control.Concurrent+  ( MVar,+    newMVar,+    putMVar,+    readMVar,+    takeMVar,+  )+import Control.Effect+import Control.Effect.Bracket+import Control.Effect.Error+import Control.Effect.Reader+import Control.Monad+import Control.Monad.Trans.Cont+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import Network.HTTP.Client hiding (Cookie)+import Network.HTTP.Client.MultipartFormData++data Http :: Effect where+  FormRequest :: String -> Http m Request+  GenBoundary :: Http m ByteString+  RespOpen :: Request -> Http m (Response BodyReader)+  RespClose :: Response a -> Http m ()++formRequest :: Effs '[Http, Error HttpException] m => String -> m Request+formRequest = send . FormRequest+{-# INLINE formRequest #-}++genBoundary :: Eff Http m => m ByteString+genBoundary = send GenBoundary+{-# INLINE genBoundary #-}++respOpen :: Effs '[Http, Error HttpException] m => Request -> m (Response BodyReader)+respOpen = send . RespOpen+{-# INLINE respOpen #-}++respClose :: Eff Http m => Response a -> m ()+respClose = send . RespClose+{-# INLINE respClose #-}++data HttpH++instance (Effs '[Embed IO, Reader Manager] m) => Handler HttpH Http m where+  effHandler (FormRequest s) = embed @IO $ parseRequest s+  effHandler GenBoundary = embed webkitBoundary+  effHandler (RespOpen req) = ask >>= embed . responseOpen req+  effHandler (RespClose resp) = embed $ responseClose resp+  {-# INLINEABLE effHandler #-}++type HttpToIOC = InterpretC HttpH Http++httpToIO :: (Effs '[Embed IO, Reader Manager] m, Threaders '[ReaderThreads] m p) => HttpToIOC m a -> m a+httpToIO = interpretViaHandler+{-# INLINE httpToIO #-}++--------------------------------------------------+--++data Cookie :: Effect where+  TakeCookie :: Cookie m CookieJar+  ReadCookie :: Cookie m CookieJar+  PutCookie :: CookieJar -> Cookie m ()++takeCookie :: Eff Cookie m => m CookieJar+takeCookie = send TakeCookie+{-# INLINE takeCookie #-}++readCookie :: Eff Cookie m => m CookieJar+readCookie = send ReadCookie+{-# INLINE readCookie #-}++putCookie :: Eff Cookie m => CookieJar -> m ()+putCookie = send . PutCookie+{-# INLINE putCookie #-}++data CookieH++instance Effs '[Reader (MVar CookieJar), Embed IO] m => Handler CookieH Cookie m where+  effHandler TakeCookie = ask >>= embed . takeMVar+  effHandler ReadCookie = ask >>= embed . readMVar+  effHandler (PutCookie c) = do+    ref <- ask+    embed $ putMVar ref c+  {-# INLINEABLE effHandler #-}++type CookieToReaderC = InterpretC CookieH Cookie++type CookieToIOC = CompositionC '[CookieToReaderC, ReaderC (MVar CookieJar)]++cookieToIO :: (Eff (Embed IO) m, Threaders '[ReaderThreads] m p) => CookieToIOC m a -> m a+cookieToIO m = do+  ref <- embed $ newMVar mempty+  runReader ref $+    interpretViaHandler $+      runComposition m+{-# INLINE cookieToIO #-}++--------------------------------------------------+--++data ConduitIO :: Effect where+  RunConduitIO :: ConduitT () Void IO a -> ConduitIO m a++runConduitIO :: Eff ConduitIO m => ConduitT () Void IO a -> m a+runConduitIO = send . RunConduitIO+{-# INLINE runConduitIO #-}++data ConduitIOH++instance Eff (Embed IO) m => Handler ConduitIOH ConduitIO m where+  effHandler (RunConduitIO c) = embed $ runConduit c+  {-# INLINEABLE effHandler #-}++type ConduitIOToIOC = InterpretC ConduitIOH ConduitIO++conduitIOToIO :: Eff (Embed IO) m => ConduitIOToIOC m a -> m a+conduitIOToIO = interpretViaHandler+{-# INLINE conduitIOToIO #-}++--------------------------------------------------+--++type ExhC = CompositionC '[HttpToIOC, ConduitIOToIOC, CookieToIOC]++exhToIO :: (Effs '[Embed IO, Reader Manager] m, Threaders '[ReaderThreads] m p) => ExhC m a -> m a+exhToIO m =+  cookieToIO $+    conduitIOToIO $+      httpToIO $+        runComposition m+{-# INLINE exhToIO #-}++--------------------------------------------------+--++attachFormData :: Eff Http m => [PartM m] -> Request -> m Request+attachFormData parts req = do+  boundary <- genBoundary+  formDataBodyWithBoundary boundary parts req++bodyReaderSource :: BodyReader -> ConduitT i ByteString IO ()+bodyReaderSource br = loop+  where+    loop = do+      bs <- lift br+      unless (B.null bs) $ do+        yield bs+        loop++resetCookie :: Eff Cookie m => m ()+resetCookie = takeCookie >> putCookie mempty+{-# INLINEABLE resetCookie #-}++modifyJar ::+  Effs '[Http, Cookie, Error HttpException, Bracket] m =>+  Request ->+  m ()+modifyJar req =+  bracketOnError+    takeCookie+    putCookie+    $ \jar -> do+      let req' = req {cookieJar = Just jar}+      bracket+        (respOpen req')+        respClose+        (putCookie . responseCookieJar)+{-# INLINEABLE modifyJar #-}++openWithJar ::+  Effs '[Http, Cookie, Error HttpException] m => Request -> m (Response (ConduitT i ByteString IO ()))+openWithJar req = do+  jar <- readCookie+  resp <- respOpen (req {cookieJar = Just jar})+  pure $ fmap bodyReaderSource resp++withSource ::+  Effs '[Http, Cookie, Error HttpException, Bracket] m =>+  Request ->+  ContT r m (Response (ConduitT i ByteString IO ()))+withSource req = ContT $ \k -> do+  jar <- readCookie+  bracket+    (respOpen (req {cookieJar = Just jar}))+    respClose+    (k . fmap bodyReaderSource)+{-# INLINEABLE withSource #-}
+ src/Language/JavaScript/Extraction.hs view
@@ -0,0 +1,155 @@+{-# LANGUAGE StrictData #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE UndecidableInstances #-}++-- | Internal module. Extract values from mpv script+module Language.JavaScript.Extraction where++import Data.Aeson+import Data.ByteString.Lazy.Char8 (pack)+import Data.Maybe+import Data.Text (Text)+import Language.JavaScript.Parser.AST+import Optics.TH+import Text.Read+import Web.Exhentai.Utils hiding (div)+import Prelude hiding ((!!))++data MpvImage = MpvImage+  { name :: {-# UNPACK #-} Text,+    key :: {-# UNPACK #-} Text,+    thumbnail :: {-# UNPACK #-} Text+  }+  deriving (Show, Eq)++instance FromJSON MpvImage where+  parseJSON = withObject "mpv image" $ \o ->+    MpvImage+      <$> o .: "n"+      <*> o .: "k"+      <*> o .: "t"++-- | All the variables defined in the scripts that came with the MPV+data Vars = Vars+  { gid :: {-# UNPACK #-} Int,+    mpvkey :: {-# UNPACK #-} Text,+    pageCount :: {-# UNPACK #-} Int,+    imageList :: [MpvImage]+  }+  deriving (Show, Eq)++class As a b where+  as :: a -> Maybe b++instance As JSExpression Int where+  as (JSDecimal _ s) = decode (pack s)+  as (JSExpressionBinary expr1 op expr2) = do+    i1 <- as expr1+    i2 <- as expr2+    case op of+      JSBinOpPlus {} -> pure $ i1 + i2+      JSBinOpMinus {} -> pure $ i1 - i2+      JSBinOpTimes {} -> pure $ i1 * i2+      JSBinOpDivide {} -> pure $ i1 `div` i2+      _ -> Nothing+  as (JSUnaryExpression op expr) = do+    i <- as expr+    case op of+      JSUnaryOpIncr {} -> pure $ i + 1+      JSUnaryOpDecr {} -> pure $ i - 1+      JSUnaryOpMinus {} -> pure $ - i+      JSUnaryOpPlus {} -> pure i+      _ -> Nothing+  as (JSVarInitExpression _ initializer) = as initializer+  as _ = Nothing++instance As JSExpression a => As JSVarInitializer a where+  as (JSVarInit _ expr) = as expr+  as _ = Nothing++instance As (JSCommaList JSExpression) a => As JSStatement a where+  as (JSVariable _ l _) = as l+  as _ = Nothing++instance {-# OVERLAPPABLE #-} As a b => As (JSCommaList a) b where+  as (JSLOne x) = as x+  as (JSLCons _ _ x) = as x+  as _ = Nothing++instance As JSExpression Text where+  as (JSStringLiteral _ s) = decode (pack s)+  as (JSVarInitExpression _ initializer) = as initializer+  as _ = Nothing++instance As JSPropertyName Text where+  as (JSPropertyString _ s) = readMaybe s+  as _ = Nothing++instance As JSArrayElement a => As JSExpression [a] where+  as (JSArrayLiteral _ l _) = Just $ mapMaybe as l+  as (JSVarInitExpression _ initializer) = as initializer+  as _ = Nothing++instance As JSExpression a => As JSArrayElement a where+  as (JSArrayElement expr) = as expr+  as (JSArrayComma _) = Nothing++class AsPair a where+  asPair :: a -> Maybe (Text, Text)++instance AsPair JSObjectProperty where+  asPair (JSPropertyNameandValue n _ [expr]) = (,) <$> as n <*> as expr+  asPair _ = Nothing++instance As (JSCommaList JSObjectProperty) [(Text, Text)] where+  as (JSLCons xs _ p) = (:) <$> asPair p <*> as xs+  as (JSLOne p) = pure <$> asPair p+  as JSLNil = pure []++instance {-# OVERLAPPABLE #-} As (JSCommaList a) b => As (JSCommaTrailingList a) b where+  as (JSCTLComma l _) = as l+  as (JSCTLNone l) = as l++instance As JSObjectPropertyList MpvImage where+  as l = do+    mapping <- (as l :: Maybe [(Text, Text)])+    thumbnail <- lookup "t" mapping+    key <- lookup "k" mapping+    name <- lookup "n" mapping+    pure MpvImage {..}++instance As JSExpression MpvImage where+  as (JSObjectLiteral _ l _) = as l+  as _ = Nothing++instance As JSAST Vars where+  as (JSAstProgram stmts _) = do+    gidStmt <- stmts !! 1+    gid <- as gidStmt+    imgStmt <- stmts !! 3+    imageList <- as imgStmt+    keyStmt <- stmts !! 2+    mpvkey <- as keyStmt+    pgCountStmt <- stmts !! 9+    pageCount <- as pgCountStmt+    pure Vars {..}+  as _ = Nothing++makeFieldLabelsWith noPrefixFieldLabels ''Vars+makeFieldLabelsWith noPrefixFieldLabels ''MpvImage++{-+extractEnv :: Text -> IO (Result Vars)+extractEnv script = quickjs $ do+  eval_ $ encodeUtf8 script+  gid' <- eval "gid"+  mpvkey' <- eval "mpvkey"+  imageList' <- eval "imagelist"+  pageCount' <- eval "pagecount"+  pure $ do+    gid <- fromJSON gid'+    mpvkey <- fromJSON mpvkey'+    imageList <- fromJSON imageList'+    pageCount <- fromJSON pageCount'+    pure Vars {..}+-}
src/Web/Exhentai.hs view
@@ -1,5 +1,35 @@ module Web.Exhentai-  ( -- ** Authentication+  ( -- * Effects and effect interpreters++    -- ** Important+    Http,+    formRequest,+    genBoundary,+    respOpen,+    respClose,+    ConduitIO (..),+    runConduitIO,+    Cookie (..),+    takeCookie,+    readCookie,+    putCookie,+    ExhC,+    exhToIO,++    -- ** Unimportant+    HttpH,+    HttpToIOC,+    httpToIO,+    CookieH,+    CookieToIOC,+    cookieToIO,+    ConduitIOH,+    ConduitIOToIOC,+    conduitIOToIO,++    -- * API++    -- ** Authentication     module Au,      -- ** Getting information about galleries@@ -16,12 +46,17 @@      -- ** Fetching archives     module Ar,++    -- * Errors+    ExhentaiError (..),   ) where +import Control.Effect.Exh import Web.Exhentai.API.Archiver as Ar import Web.Exhentai.API.Auth as Au import Web.Exhentai.API.Gallery as G import Web.Exhentai.API.MPV as M import Web.Exhentai.API.Search as S import Web.Exhentai.API.Watched as W+import Web.Exhentai.Errors
src/Web/Exhentai/API/Archiver.hs view
@@ -5,64 +5,73 @@ where  import Conduit-import Control.Lens (Traversal')-import Control.Monad.Catch-import Control.Monad.Cont+import Control.Effect+import Control.Effect.Bracket+import Control.Effect.Error+import Control.Effect.Exh+import Control.Monad.Trans.Cont import Data.ByteString (ByteString) import Data.Text (Text, unpack)-import Network.HTTP.Client.Conduit+import Network.HTTP.Client hiding (Cookie) import Network.HTTP.Client.MultipartFormData-import Text.XML.Lens+import Optics.Core (Traversal')+import Text.XML.Optics import Web.Exhentai.Errors-import Web.Exhentai.Types.CookieT import Web.Exhentai.Utils import Prelude hiding (id)  downloadLink :: Traversal' Element Text-downloadLink = id "db" ... id "continue" ... attr "href"+downloadLink = id "db" .// id "continue" .// attr "href"+{-# INLINE downloadLink #-} -originalParts :: [Part]+originalParts :: Applicative m => [PartM m] originalParts =   [ partBS "dltype" "org",     partBS "dlcheck" "Download Original Archive"   ] {-# INLINE originalParts #-} -resampledParts :: [Part]+resampledParts :: Applicative m => [PartM m] resampledParts =   [ partBS "dltype" "res",     partBS "dlcheck" "Download Resample Archive"   ] {-# INLINE resampledParts #-} -streamWith :: (MonadHttpState m, MonadIO n) => [Part] -> Text -> ContT r m (Response (ConduitT i ByteString n ()))+streamWith ::+  Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket, Throw ExhentaiError] m =>+  [PartM m] ->+  Text ->+  ContT r m (Response (ConduitT i ByteString IO ())) streamWith parts url = ContT $ \k -> do   initReq <- formRequest $ unpack url-  req <- formDataBody parts initReq+  req <- attachFormData parts initReq   d <- htmlRequest req   case d ^?: downloadLink of-    Nothing -> throwM $ XMLParseFailure "download link" url+    Nothing -> throw $ XMLParseFailure "download link" url     Just l -> do       newReq <- formRequest $ unpack l       let req' = setQueryString [("start", Just "1")] newReq-      retryWhenTimeout $-        bracket-          (respOpen req')-          respClose-          k+      bracket+        (respOpen req')+        respClose+        (k . fmap bodyReaderSource)+{-# INLINEABLE streamWith #-}  -- | Download an origian archive from an archiver url as a stream streamOriginal ::-  (MonadHttpState m, MonadIO n) =>+  Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket, Throw ExhentaiError] m =>   -- | Archiver url, usually the 'archiverLink` field   Text ->-  ContT r m (Response (ConduitT i ByteString n ()))+  ContT r m (Response (ConduitT i ByteString IO ())) streamOriginal = streamWith originalParts+{-# INLINEABLE streamOriginal #-}  -- | Download an resampled archive from an archiver url as a stream streamResampled ::-  (MonadHttpState m, MonadIO n) =>+  Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket, Throw ExhentaiError] m =>   -- | Archiver url, usually the 'archiverLink` field   Text ->-  ContT r m (Response (ConduitT i ByteString n ()))+  ContT r m (Response (ConduitT i ByteString IO ())) streamResampled = streamWith resampledParts+{-# INLINEABLE streamResampled #-}
src/Web/Exhentai/API/Auth.hs view
@@ -1,24 +1,31 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE UndecidableInstances #-}++-- | Authentication API. module Web.Exhentai.API.Auth   ( Credential (..),     auth,   ) where +import Control.Effect+import Control.Effect.Bracket+import Control.Effect.Error+import Control.Effect.Exh import Data.ByteString (ByteString)-import GHC.Generics-import Network.HTTP.Client.Conduit+import Network.HTTP.Client hiding (Cookie) import Network.HTTP.Client.MultipartFormData-import Web.Exhentai.Types.CookieT+import Optics.TH  data Credential = Credential   { username :: ByteString,     password :: ByteString   }-  deriving (Show, Eq, Generic)+  deriving (Show, Eq)  -- | Authenticates and loads user preferences. -- This should be called before any other functions are called-auth :: MonadHttpState m => Credential -> m ()+auth :: Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket] m => Credential -> m () auth Credential {..} = do   initReq <- formRequest "POST https://forums.e-hentai.org/index.php"   let parts =@@ -36,10 +43,13 @@           ]           initReq   finalReq <- attachFormData parts req-  modifyingJar finalReq+  modifyJar finalReq   req2 <- formRequest "https://exhentai.org"-  modifyingJar req2+  modifyJar req2   req3 <- formRequest "https://exhentai.org/uconfig.php"-  modifyingJar req3+  modifyJar req3   req4 <- formRequest "https://exhentai.org/mytags"-  modifyingJar req4+  modifyJar req4+{-# INLINEABLE auth #-}++makeFieldLabelsWith noPrefixFieldLabels ''Credential
src/Web/Exhentai/API/Gallery.hs view
@@ -1,41 +1,133 @@ {-# LANGUAGE StrictData #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE UndecidableInstances #-} +-- | Gallery API. module Web.Exhentai.API.Gallery-  ( GalleryInfo (..),+  ( Gallery (..),+    GalleryInfo (..),     Visibility (..),+    GalleryCategory (..),+    TagCategory (..),+    allGalleryCats,     fetchGalleryInfo,++    -- ** Internal API+    parseGalleryLink,     parseGallery,   ) where -import Control.Lens ((^..), (^?))+import Control.Effect+import Control.Effect.Bracket+import Control.Effect.Error+import Control.Effect.Exh import Control.Monad-import Control.Monad.Catch-import Data.Coerce-import Data.Text (Text, strip)+import Data.Maybe+import Data.Set (Set, fromList)+import Data.Text (Text, pack, strip) import Data.Time-import GHC.Generics+import Data.Void+import Network.HTTP.Client hiding (Cookie)+import Optics.Core+import Optics.TH+import Text.Megaparsec hiding (token)+import Text.Megaparsec.Char.Lexer import Text.XML-import Text.XML.Lens+import Text.XML.Optics import Web.Exhentai.Errors import qualified Web.Exhentai.Parsing.Gallery as G-import Web.Exhentai.Types-import Web.Exhentai.Types.CookieT import Web.Exhentai.Utils import Prelude hiding (length) import qualified Prelude as P +data Gallery = Gallery+  { galleryId :: {-# UNPACK #-} !Int,+    token :: {-# UNPACK #-} !Text+  }+  deriving (Show, Eq)++toGalleryLink :: Gallery -> Text+toGalleryLink Gallery {..} = "https://exhentai.org/g/" <> pack (show galleryId) <> "/" <> token <> "/"++parseGalleryLink :: Text -> Maybe Gallery+parseGalleryLink = parseMaybe galleryLink+  where+    galleryLink :: Parser Gallery+    galleryLink = do+      _ <- chunk "https://exhentai.org/g/"+      galleryId <- decimal+      _ <- single '/'+      token <- takeWhile1P Nothing (/= '/')+      _ <- optional $ single '/'+      pure Gallery {..}++data TagCategory+  = Language+  | Parody+  | Character+  | Group+  | Artist+  | Male+  | Female+  | Misc'+  | Reclass+  deriving (Show, Eq, Enum)++readTagCat :: Text -> Maybe TagCategory+readTagCat "language:" = Just Language+readTagCat "parody:" = Just Parody+readTagCat "character:" = Just Character+readTagCat "group:" = Just Group+readTagCat "artist:" = Just Artist+readTagCat "male:" = Just Male+readTagCat "female:" = Just Female+readTagCat "misc:" = Just Misc'+readTagCat "reclass:" = Just Reclass+readTagCat _ = Nothing++data GalleryCategory+  = Misc+  | Doujinshi+  | Manga+  | ArtistCG+  | GameCG+  | ImageSet+  | Cosplay+  | AsianPorn+  | NonH+  | Western+  | Private+  deriving (Show, Eq, Ord, Enum, Bounded)++allGalleryCats :: Set GalleryCategory+allGalleryCats = fromList [Misc .. Private]++readCat :: Text -> Maybe GalleryCategory+readCat "Doujinshi" = Just Doujinshi+readCat "Manga" = Just Manga+readCat "Artist CG" = Just ArtistCG+readCat "Game CG" = Just GameCG+readCat "Non-H" = Just NonH+readCat "Image Set" = Just ImageSet+readCat "Western" = Just Western+readCat "Cosplay" = Just Cosplay+readCat "Misc" = Just Misc+readCat "Private" = Just Private+readCat "Asian Porn" = Just AsianPorn+readCat _ = Nothing+ -- | Information about a gallery data GalleryInfo = GalleryInfo   { title :: {-# UNPACK #-} Text,     previewLink :: {-# UNPACK #-} Text,-    category :: GalleryCat,+    category :: GalleryCategory,     jaTitle :: Maybe Text,     uploader :: {-# UNPACK #-} Text,-    rating :: {-# UNPACK #-} Float,+    rating :: {-# UNPACK #-} Double,     ratingCount :: {-# UNPACK #-} Int,     favoriteCount :: {-# UNPACK #-} Int,-    tags :: [(G.TagCategory, [Text])],+    tags :: [(TagCategory, [Text])],     uploadTime :: {-# UNPACK #-} UTCTime,     newer :: Maybe Gallery,     parent :: Maybe Gallery,@@ -45,14 +137,14 @@     archiverLink :: {-# UNPACK #-} Text,     torrentLink :: {-# UNPACK #-} Text   }-  deriving (Show, Eq, Generic)+  deriving (Show, Eq)  data Visibility   = Visible   | Replaced   | Expunged   | Unknown {-# UNPACK #-} Text-  deriving (Show, Eq, Generic)+  deriving (Show, Eq)  readVisibility :: Text -> Visibility readVisibility "Yes" = Visible@@ -66,33 +158,111 @@   title <- annotate "title" $ d ^?: G.enTitle   previewLink <- annotate "preview link" $ d ^?: G.previewStr >>= parsePreviewLink   let jaTitle = d ^?: G.jaTitle-  category <- annotate "category" $ d ^?: G.category+  category <- annotate "category" $ d ^?: G.category >>= readCat   uploader <- annotate "uploader" $ d ^?: G.uploader-  (coerce -> rating) <- annotate "average rating" $ d ^?: G.averageRating+  rating <- annotate "average rating" $ d ^?: G.averageRating >>= parseAverageRating   ratingCount <- annotate "rating count" $ d ^?: G.ratingCount-  (coerce -> archiverLink) <- annotate "archiver link" $ d ^?: G.popupLink-  (coerce -> torrentLink) <- annotate "torrent link" $ case d ^..: G.popupLink of (_ : tl : _) -> Just tl; _ -> Nothing-  let newer = d ^?: G.newer+  archiverLink <- annotate "archiver link" $ d ^?: G.popupLink >>= parsePopUpLink+  torrentLink <- annotate "torrent link" $ case d ^..: G.popupLink of (_ : tl : _) -> Just tl; _ -> Nothing+  let newer = d ^?: G.newer >>= parseGalleryLink   case d ^..: G.metaValues of     (time : parn : vis : lang : _ : len : fav : _) -> do-      uploadTime <- annotate "upload time" $ time ^? lower . _Content >>= parseUploadTime-      let parent = parn ^? lower . _Element . attr "href" . _GalleryLink-      (readVisibility -> visibility) <- annotate "visibility" $ vis ^? lower . _Content-      (strip -> language) <- annotate "language" $ lang ^? lower . _Content-      (coerce -> length) <- annotate "length" $ len ^? lower . _Content >>= parseGalleryLength-      let cats = d ^..: G.tagCategory+      uploadTime <- annotate "upload time" $ time ^? pre lower % _Content >>= parseUploadTime+      let parent = parn ^? pre lower % _Element % attr "href" >>= parseGalleryLink+      (readVisibility -> visibility) <- annotate "visibility" $ vis ^? pre lower % _Content+      (strip -> language) <- annotate "language" $ lang ^? pre lower % _Content+      length <- annotate "length" $ len ^? pre lower % _Content >>= parseGalleryLength+      let cats = mapMaybe readTagCat (d ^..: G.tagCategory)       let tags' = map (^.. G.tags) $ d ^..: G.tagsByCategory-      unless (P.length cats == P.length tags') $ Left ""+      unless (P.length cats == P.length tags') $ Left "length of categories and tags does not match"       let tags = zip cats tags'-      (coerce -> favoriteCount) <- annotate "favorite count" $ fav ^? lower . _Content >>= parseFavoriteCount+      favoriteCount <- annotate "favorite count" $ fav ^? pre lower % _Content >>= parseFavoriteCount       pure GalleryInfo {..}     _ -> Left "extracting metadata"  -- | Fetch a gallery's 'GalleryInfo'-fetchGalleryInfo :: MonadHttpState m => Gallery -> m GalleryInfo+fetchGalleryInfo ::+  Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket, Throw ExhentaiError] m =>+  Gallery ->+  m GalleryInfo fetchGalleryInfo g = do   let url = toGalleryLink g   d <- htmlRequest' url   case parseGallery d of-    Left err -> throwM $ XMLParseFailure err url+    Left err -> throw $ XMLParseFailure err url     Right info -> pure info+{-# INLINEABLE fetchGalleryInfo #-}++--------------------------------------------------+--++type Parser = Parsec Void Text++parsePopUpLink :: Text -> Maybe Text+parsePopUpLink = parseMaybe archiverLink+  where+    archiverLink :: Parser Text+    archiverLink = do+      _ <- chunk "return popUp('"+      url <- takeWhile1P Nothing (/= '\'')+      _ <- takeRest+      pure url++parseAverageRating :: Text -> Maybe Double+parseAverageRating = parseMaybe averageRating+  where+    averageRating :: Parser Double+    averageRating =+      ( do+          _ <- chunk "Average: "+          float+      )+        <|> (chunk "Not Yet Rated" >> pure 0)++parseGalleryLength :: Text -> Maybe Int+parseGalleryLength = parseMaybe galleryLength+  where+    galleryLength :: Parser Int+    galleryLength = do+      d <- decimal+      _ <- chunk " pages"+      pure d++parseFavoriteCount :: Text -> Maybe Int+parseFavoriteCount = parseMaybe favoriteCount+  where+    once = do+      _ <- chunk "Once"+      pure 1+    never = do+      _ <- chunk "Never"+      pure 0+    favoriteCount :: Parser Int+    favoriteCount =+      ( do+          d <- decimal+          _ <- chunk " times"+          pure d+      )+        <|> once+        <|> never++parsePreviewLink :: Text -> Maybe Text+parsePreviewLink = parseMaybe previewLink+  where+    previewLink :: Parser Text+    previewLink = do+      _ <- many $ do+        notFollowedBy urlOpening+        anySingle+      _ <- urlOpening+      url <- takeWhile1P Nothing (/= ')')+      _ <- takeRest+      pure url+    urlOpening :: Parser Text+    urlOpening = chunk "url("++makeFieldLabelsWith noPrefixFieldLabels ''GalleryInfo+makePrismLabels ''TagCategory+makePrismLabels ''GalleryCategory+makePrismLabels ''Visibility
src/Web/Exhentai/API/MPV.hs view
@@ -1,42 +1,47 @@ {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE StrictData #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE UndecidableInstances #-} +-- | MPV (multi-page viewer) API. module Web.Exhentai.API.MPV   ( DispatchRequest (..),-    DispatchResult (..),-    Vars (..),     Server (..),     Dim (..),-    fetchMpv,-    toRequests,-    imageDispatch,+    buildRequest,     fetchImage,-    fetchImage',   ) where  import Conduit import Control.Applicative-import Control.Lens ((^.))-import Control.Monad.Catch+import Control.Effect+import Control.Effect.Bracket+import Control.Effect.Error+import Control.Effect.Exh+import Control.Monad import Control.Monad.Trans.Cont import Data.Aeson import Data.ByteString (ByteString)-import Data.Text (Text, unpack)-import GHC.Generics-import Network.HTTP.Client.Conduit+import Data.Text (Text, pack, unpack)+import Language.JavaScript.Extraction+import Language.JavaScript.Parser+import Network.HTTP.Client hiding (Cookie)+import Optics.Core+import Optics.TH import Text.XML+import Text.XML.Optics+import Web.Exhentai.API.Gallery import Web.Exhentai.Errors-import Web.Exhentai.Parsing.MPV-import Web.Exhentai.Types-import Web.Exhentai.Types.CookieT import Web.Exhentai.Utils+import Prelude hiding ((!!))  data Server-  = HAtH Int-  | Other Text-  deriving (Show, Eq, Generic)+  = HAtH {-# UNPACK #-} Int+  | Other {-# UNPACK #-} Text+  deriving (Show, Eq)  instance FromJSON Server where   parseJSON v =@@ -57,21 +62,21 @@  data DispatchResult = DispatchResult   { -- | A piece of text describing the dimensions and the size of this image-    dimension :: Text,+    dimension :: {-# UNPACK #-} Text,     -- | The path part of the url pointing to the original image-    origImgPath :: Text,+    origImgPath :: {-# UNPACK #-} Text,     -- | The path part of the url that searches for the gallery containing this image-    searchPath :: Text,+    searchPath :: {-# UNPACK #-} Text,     -- | The path part of the non-mpv page that displays this image-    galleryPath :: Text,-    width :: Dim,-    height :: Dim,+    galleryPath :: {-# UNPACK #-} Text,+    width :: {-# UNPACK #-} Dim,+    height :: {-# UNPACK #-} Dim,     -- | The full url to this image-    imgLink :: Text,+    imgLink :: {-# UNPACK #-} Text,     -- | The server that serves this image     server :: Server   }-  deriving (Show, Eq, Generic)+  deriving (Show, Eq)  instance FromJSON DispatchResult where   parseJSON = withObject "imagedispatch result" $ \o ->@@ -86,13 +91,13 @@       <*> o .: "s"  data DispatchRequest = DispatchRequest-  { galleryId :: Int,-    page :: Int,-    imgKey :: Text,-    mpvKey :: Text,+  { galleryId :: {-# UNPACK #-} Int,+    page :: {-# UNPACK #-} Int,+    imgKey :: {-# UNPACK #-} Text,+    mpvKey :: {-# UNPACK #-} Text,     exclude :: Maybe Server   }-  deriving (Show, Eq, Generic)+  deriving (Show, Eq)  instance ToJSON DispatchRequest where   toJSON DispatchRequest {..} = object l@@ -120,34 +125,64 @@           mpvKey = mpvkey         } +toMpvLink :: Gallery -> Text+toMpvLink Gallery {..} = "https://exhentai.org/mpv/" <> pack (show galleryId) <> "/" <> token <> "/"+ -- | Fetch the 'Vars' from a Gallery's mpv page-fetchMpv :: (MonadHttpState m, MonadIO m) => Gallery -> m Vars+fetchMpv ::+  Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket, Throw ExhentaiError] m =>+  Gallery ->+  m Vars fetchMpv g = htmlRequest' (toMpvLink g) >>= parseMpv+{-# INLINEABLE fetchMpv #-} -parseMpv :: (MonadIO m, MonadThrow m) => Document -> m Vars+parseMpv :: Effs '[Throw ExhentaiError] m => Document -> m Vars parseMpv doc = do-  let script = doc ^. allScripts-  res <- liftIO $ extractEnv script-  case res of-    Error e -> throwM $ ExtractionFailure e-    Success vars -> pure vars+  let script = foldOf allScripts doc+      mast = parse (unpack script) ""+  case mast of+    Left _ -> error "impossible, javascript parse failed"+    Right ast ->+      case as ast of+        Nothing -> throw ExtractionFailure+        Just vars -> pure vars+{-# INLINEABLE parseMpv #-} +-- | Build dispatch requests for a gallery+buildRequest ::+  Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket, Throw ExhentaiError] m =>+  Gallery ->+  m [DispatchRequest]+buildRequest g = toRequests <$> fetchMpv g+{-# INLINEABLE buildRequest #-}+ -- | Calls the API to dispatch a image request to a H@H server-imageDispatch :: MonadHttpState m => DispatchRequest -> m DispatchResult+imageDispatch ::+  Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket, Throw ExhentaiError] m =>+  DispatchRequest ->+  m DispatchResult imageDispatch dreq = do   initReq <- formRequest "https://exhentai.org/api.php"   let req = initReq {method = "POST", requestBody = RequestBodyLBS $ encode dreq}   r <- jsonRequest req   case r of-    Left e -> throwM $ JSONParseFailure e+    Left e -> throw $ JSONParseFailure e     Right res -> pure res+{-# INLINEABLE imageDispatch #-}  -- | Fetch an image with a 'DispatchRequest'-fetchImage :: (MonadHttpState m, MonadIO n) => DispatchRequest -> ContT r m (Response (ConduitT i ByteString n ()))+fetchImage ::+  Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket, Throw ExhentaiError] m =>+  DispatchRequest ->+  ContT r m (Response (ConduitT i ByteString IO ())) fetchImage dreq = ContT $ \k -> bracket (fetchImage' dreq) respClose k+{-# INLINEABLE fetchImage #-}  -- | Like 'fetchImage', but the user is responsible of closing the response-fetchImage' :: (MonadHttpState m, MonadIO n) => DispatchRequest -> m (Response (ConduitT i ByteString n ()))+fetchImage' ::+  Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket, Throw ExhentaiError] m =>+  DispatchRequest ->+  m (Response (ConduitT i ByteString IO ())) fetchImage' dreq = do   res <- imageDispatch dreq   req <- formRequest $ unpack $ imgLink res@@ -157,3 +192,12 @@     openWithJar req' `catch` \(_ :: HttpException) -> do       req'' <- formRequest $ unpack $ "https://exhentai.org/" <> origImgPath res       openWithJar req''+{-# INLINEABLE fetchImage' #-}++allScripts :: Traversal' Document Text+allScripts = body .// (scripts % lower %> _Content)++makeFieldLabelsWith noPrefixFieldLabels ''DispatchResult+makeFieldLabelsWith noPrefixFieldLabels ''DispatchRequest+makePrismLabels ''Dim+makePrismLabels ''Server
src/Web/Exhentai/API/Search.hs view
@@ -1,4 +1,6 @@ {-# LANGUAGE StrictData #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE UndecidableInstances #-}  module Web.Exhentai.API.Search   ( SearchQuery (..),@@ -6,36 +8,40 @@     search,     searchRecur,     searchRecurResumable,-    searchRecurResumable',     fetchSearchPage,-    fetchSearchPage',   ) where  import Conduit-import Control.Lens ((...))-import Control.Lens.Fold+import Control.Effect+import Control.Effect.Bracket+import Control.Effect.Error+import Control.Effect.Exh import Control.Monad-import Data.Set (Set, (\\))+import Data.Maybe+import Data.Set (Set, toList, (\\)) import Data.String import Data.Text (Text, unpack) import Data.Text.Encoding (encodeUtf8)-import GHC.Generics-import Network.HTTP.Client.Conduit-import Text.XML+import Network.HTTP.Client hiding (Cookie)+import Optics.Core+import Optics.TH+import Text.XML.Optics+import Web.Exhentai.API.Gallery import Web.Exhentai.Parsing.Search-import Web.Exhentai.Types-import Web.Exhentai.Types.CookieT import Web.Exhentai.Utils import Prelude hiding (last) +toBitField :: Set GalleryCategory -> Int+toBitField = sum . map ((2 ^) . fromEnum) . toList+ data SearchQuery = SearchQuery-  { categories :: Maybe (Set GalleryCat),+  { categories :: Maybe (Set GalleryCategory),     searchString :: {-# UNPACK #-} Text   }-  deriving (Show, Eq, Generic)+  deriving (Show, Eq) -queryArgCat :: Set GalleryCat -> Int+queryArgCat :: Set GalleryCategory -> Int queryArgCat s = toBitField $ allGalleryCats \\ s  data SearchResult = SearchResult@@ -43,33 +49,44 @@     prevPage :: Maybe Text,     nextPage :: Maybe Text   }-  deriving (Show, Eq, Generic)+  deriving (Show, Eq)  parseSearchPage :: Document -> SearchResult parseSearchPage d =-  let galleries = d ^..: galleryPreviewElement . galleryLink-      fld :: Fold Document Element-      fld = body ... pagesElem+  let galleries = mapMaybe parseGalleryLink $ d ^..: galleryPreviewElement % galleryLink+      fld :: Traversal' Document Element+      fld = body .// pagesElem       prevPage = do-        first <- firstOf fld d-        first ^? linkOf+        first <- headOf fld d+        first ^? pre linkOf       nextPage = do         last <- lastOf fld d-        last ^? linkOf+        last ^? pre linkOf    in SearchResult {..}  -- | Fetch a search page using a 'Request'-fetchSearchPage' :: MonadHttpState m => Request -> m SearchResult+fetchSearchPage' ::+  Effs '[Http, Error HttpException, ConduitIO, Cookie, Bracket] m =>+  Request ->+  m SearchResult fetchSearchPage' req = do   d <- htmlRequest req   pure $ parseSearchPage d+{-# INLINEABLE fetchSearchPage' #-}  -- | Fetch a search page using its url-fetchSearchPage :: MonadHttpState m => Text -> m SearchResult+fetchSearchPage ::+  Effs '[Http, Error HttpException, ConduitIO, Cookie, Bracket] m =>+  Text ->+  m SearchResult fetchSearchPage = fetchSearchPage' <=< formRequest . unpack+{-# INLINEABLE fetchSearchPage #-}  -- | Search a search query-search :: MonadHttpState m => SearchQuery -> m SearchResult+search ::+  Effs '[Http, Error HttpException, ConduitIO, Cookie, Bracket] m =>+  SearchQuery ->+  m SearchResult search SearchQuery {..} = do   let catQ = maybe [] (\c -> [("f_cats", Just $ fromString $ show $ queryArgCat c)]) categories   initReq <- formRequest "https://exhentai.org"@@ -81,18 +98,23 @@           )           initReq   fetchSearchPage' req+{-# INLINEABLE search #-}  -- | Iterate through all the Galleries asosciated with a search query, putting them into a stream-searchRecur :: MonadHttpState m => SearchQuery -> ConduitT i Gallery m ()+searchRecur ::+  Effs '[Http, Error HttpException, ConduitIO, Cookie, Bracket] m =>+  SearchQuery ->+  ConduitT i Gallery m () searchRecur q = do   SearchResult {..} <- lift $ search q   yieldMany galleries   case nextPage of     Nothing -> pure ()     Just url -> searchRecur' url+{-# INLINEABLE searchRecur #-}  searchRecur' ::-  MonadHttpState m =>+  Effs '[Http, Error HttpException, ConduitIO, Cookie, Bracket] m =>   -- | url   Text ->   ConduitT i Gallery m ()@@ -102,18 +124,23 @@   case nextPage of     Nothing -> pure ()     Just url' -> searchRecur' url'+{-# INLINEABLE searchRecur' #-}  -- | A resumable version of 'searchRecur' that reports it's progress.-searchRecurResumable :: MonadHttpState m => SearchQuery -> ConduitT i (Either Text Gallery) m ()+searchRecurResumable ::+  Effs '[Http, Error HttpException, ConduitIO, Cookie, Bracket] m =>+  SearchQuery ->+  ConduitT i (Either Text Gallery) m () searchRecurResumable q = do   SearchResult {..} <- lift $ search q   yieldMany $ map Right galleries   case nextPage of     Nothing -> pure ()     Just url -> searchRecurResumable' url+{-# INLINEABLE searchRecurResumable #-}  searchRecurResumable' ::-  MonadHttpState m =>+  Effs '[Http, Error HttpException, ConduitIO, Cookie, Bracket] m =>   -- | url   Text ->   ConduitT i (Either Text Gallery) m ()@@ -124,3 +151,7 @@   case nextPage of     Nothing -> pure ()     Just url' -> searchRecurResumable' url'+{-# INLINEABLE searchRecurResumable' #-}++makeFieldLabelsWith noPrefixFieldLabels ''SearchQuery+makeFieldLabelsWith noPrefixFieldLabels ''SearchResult
src/Web/Exhentai/API/Watched.hs view
@@ -4,19 +4,27 @@   ) where +import Control.Effect+import Control.Effect.Bracket+import Control.Effect.Error+import Control.Effect.Exh+import Data.Maybe+import Network.HTTP.Client hiding (Cookie)+import Optics.Core+import Web.Exhentai.API.Gallery import Web.Exhentai.Parsing.Search-import Web.Exhentai.Types-import Web.Exhentai.Types.CookieT import Web.Exhentai.Utils  -- | Fetch the list of watched galleries-fetchWatched :: MonadHttpState m => m [Gallery]+fetchWatched :: Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket] m => m [Gallery] fetchWatched = do   d <- htmlRequest' "https://exhentai.org/watched"-  pure $ d ^..: galleryPreviewElement . galleryLink+  pure $ mapMaybe parseGalleryLink $ d ^..: galleryPreviewElement % galleryLink+{-# INLINEABLE fetchWatched #-}  -- | Fetch the list of popular galleries-fetchPopular :: MonadHttpState m => m [Gallery]+fetchPopular :: Effs '[Http, Error HttpException, Cookie, ConduitIO, Bracket] m => m [Gallery] fetchPopular = do   d <- htmlRequest' "https://exhentai.org/popular"-  pure $ d ^..: galleryPreviewElement . galleryLink+  pure $ mapMaybe parseGalleryLink $ d ^..: galleryPreviewElement % galleryLink+{-# INLINEABLE fetchPopular #-}
src/Web/Exhentai/Errors.hs view
@@ -6,7 +6,6 @@  import Control.Exception import Data.Text (Text)-import GHC.Generics  data ExhentaiError   = JSONParseFailure String@@ -14,6 +13,6 @@       { reason :: Text,         url :: Text       }-  | ExtractionFailure String-  deriving (Show, Eq, Generic)+  | ExtractionFailure+  deriving (Show, Eq)   deriving (Exception)
src/Web/Exhentai/Parsing/Gallery.hs view
@@ -1,122 +1,109 @@ {-# LANGUAGE RankNTypes #-} +-- | Internal module module Web.Exhentai.Parsing.Gallery where -import Control.Lens-import Data.Text (Text, pack)-import GHC.Generics+import Data.Text (Text)+import Optics.Core import Text.XML hiding (readFile)-import Text.XML.Lens-import Web.Exhentai.Types-  ( AverageRating,-    Gallery,-    GalleryCat,-    PopUpLink,-    _AverageRating,-    _GalleryCat,-    _GalleryLink,-    _PopUpLink,-  )+import Text.XML.Optics import Web.Exhentai.Utils import Prelude hiding (div, id, readFile) -data TagCategory-  = Language-  | Parody-  | Character-  | Group-  | Artist-  | Male-  | Female-  | Misc-  | Reclass-  deriving (Show, Eq, Enum, Generic)--readTagCat :: Text -> Maybe TagCategory-readTagCat "language:" = Just Language-readTagCat "parody:" = Just Parody-readTagCat "character:" = Just Character-readTagCat "group:" = Just Group-readTagCat "artist:" = Just Artist-readTagCat "male:" = Just Male-readTagCat "female:" = Just Female-readTagCat "misc:" = Just Misc-readTagCat "reclass:" = Just Reclass-readTagCat _ = Nothing--_TagCategory :: Prism' Text TagCategory-_TagCategory = prism' (pack . show) readTagCat--meta :: Traversal' Element Element-meta = div . cl "gm"+meta :: AffineTraversal' Element Element+meta = div % cl "gm"+{-# INLINE meta #-}  title :: Traversal' Element Element-title = meta ... div . id "gd2"+title = meta .// (div % id "gd2")+{-# INLINE title #-}  enTitle :: Traversal' Element Text-enTitle = title ... h1 . id "gn" . lower . _Content+enTitle = title .// (h1 % id "gn" <% lower % _Content)+{-# INLINE enTitle #-}  jaTitle :: Traversal' Element Text-jaTitle = title ... h1 . id "gj" . lower . _Content+jaTitle = title .// (h1 % id "gj" <% lower % _Content)+{-# INLINE jaTitle #-}  mainMeta :: Traversal' Element Element-mainMeta = meta ... div . id "gmid"+mainMeta = meta .// (div % id "gmid")+{-# INLINE mainMeta #-}  mainMetaL :: Traversal' Element Element-mainMetaL = mainMeta ... div . id "gd3"+mainMetaL = mainMeta .// (div % id "gd3")+{-# INLINE mainMetaL #-}  mainMetaM :: Traversal' Element Element-mainMetaM = mainMeta ... div . id "gd4"+mainMetaM = mainMeta .// (div % id "gd4")+{-# INLINE mainMetaM #-}  mainMetaR :: Traversal' Element Element-mainMetaR = mainMeta ... div . id "gd5"+mainMetaR = mainMeta .// (div % id "gd5")+{-# INLINE mainMetaR #-}  previewStr :: Traversal' Element Text-previewStr = meta ... id "gleft" ... id "gd1" ... attr "style"+previewStr = meta .// id "gleft" .// id "gd1" .// attr "style"+{-# INLINE previewStr #-} -category :: Traversal' Element GalleryCat-category = mainMetaL ... div . id "gdc" ... div . lower . _Content . _GalleryCat+category :: Traversal' Element Text+category = mainMetaL .// (div % id "gdc") .// (div <% lower % _Content)+{-# INLINE category #-}  uploader :: Traversal' Element Text-uploader = mainMetaL ... div . id "gdn" ... a . lower . _Content+uploader = mainMetaL .// (div % id "gdn") .// (a % lower %> _Content)+{-# INLINE uploader #-}  metaPath :: Traversal' Element Element-metaPath = mainMetaL ... div . id "gdd" ... table ... tr+metaPath = mainMetaL .// (div % id "gdd") .// table .// tr+{-# INLINE metaPath #-}  metaKeys :: Traversal' Element Text-metaKeys = metaPath ... cl "gdt1" . lower . _Content+metaKeys = metaPath .// (cl "gdt1" <% lower % _Content)+{-# INLINE metaKeys #-}  metaValues :: Traversal' Element Element-metaValues = metaPath ... cl "gdt2"+metaValues = metaPath .// cl "gdt2"+{-# INLINE metaValues #-} -parent :: Traversal' Element Gallery-parent = lower . _Element . attr "href" . _GalleryLink+parent :: Traversal' Element Text+parent = lower %> _Element % attr "href"+{-# INLINE parent #-}  ratingCount :: Traversal' Element Int ratingCount =-  mainMetaL ... id "gdr" ... table ... tr ... id "grt3" ... lower . _Content . viaShowRead+  mainMetaL .// id "gdr" .// table .// tr .// id "grt3" .// (lower %> _Content % viaShowRead)+{-# INLINE ratingCount #-} -averageRating :: Traversal' Element AverageRating+averageRating :: Traversal' Element Text averageRating =-  mainMetaL ... id "gdr" ... table ... tr ... id "rating_label" . lower . _Content . _AverageRating+  mainMetaL .// id "gdr" .// table .// tr .// (id "rating_label" % lower %> _Content)+{-# INLINE averageRating #-}  tagList :: Traversal' Element Element-tagList = mainMetaM ... div . id "taglist" ... table ... tr+tagList = mainMetaM .// (div % id "taglist") .// table .// tr+{-# INLINE tagList #-} -tagCategory :: Traversal' Element TagCategory-tagCategory = tagList ... td . cl "tc" . lower . _Content . _TagCategory+tagCategory :: Traversal' Element Text+tagCategory = tagList .// (td % cl "tc" % lower %> _Content)+{-# INLINE tagCategory #-}  tagsByCategory :: Traversal' Element Element-tagsByCategory = tagList ... td . withoutAttribute "class"+tagsByCategory = tagList .// (td % withoutAttribute "class")+{-# INLINE tagsByCategory #-}  tags :: Traversal' Element Text-tags = td ... div ... a . lower . _Content+tags = td .// div .// (a % lower %> _Content)+{-# INLINE tags #-} -popupLink :: Traversal' Element PopUpLink-popupLink = mainMetaR ... named "p" ... attr "onclick" . _PopUpLink+popupLink :: Traversal' Element Text+popupLink = mainMetaR .// named "p" .// attr "onclick"+{-# INLINE popupLink #-}  imagePages :: Traversal' Element Text-imagePages = div . id "gdt" ... cl "gdtl" ... a . attr "href"+imagePages = (div % id "gdt") .// cl "gdtl" .// (a % attr "href")+{-# INLINE imagePages #-} -newer :: Traversal' Element Gallery-newer = div . id "gnd" ... a . attr "href" . _GalleryLink+newer :: Traversal' Element Text+newer = (div % id "gnd") .// (a % attr "href")+{-# INLINE newer #-}
src/Web/Exhentai/Parsing/Image.hs view
@@ -1,13 +1,16 @@+-- | Internal module module Web.Exhentai.Parsing.Image where -import Control.Lens import Data.Text (Text)-import Text.XML.Lens+import Optics.Core+import Text.XML.Optics import Web.Exhentai.Utils import Prelude hiding (id)  imageSrc :: Traversal' Element Text-imageSrc = id "i1" ... id "i3" ... a ... img . attr "src"+imageSrc = id "i1" .// id "i3" .// a .// (img % attr "src")+{-# INLINE imageSrc #-} -nextPage :: Traversal' Element Text-nextPage = id "i1" ... id "i3" ... a . attr "href"+nextImage :: Traversal' Element Text+nextImage = id "i1" .// id "i3" .// (a % attr "href")+{-# INLINE nextImage #-}
− src/Web/Exhentai/Parsing/MPV.hs
@@ -1,55 +0,0 @@-{-# LANGUAGE StrictData #-}--module Web.Exhentai.Parsing.MPV where--import Control.Lens-import Data.Aeson-import Data.Text (Text)-import Data.Text.Encoding (encodeUtf8)-import GHC.Generics-import Quickjs-import Text.XML.Lens-import Web.Exhentai.Utils--allScripts :: Traversal' Document Text-allScripts = body ... scripts . lower . _Content--data MpvImage = MpvImage-  { name :: {-# UNPACK #-} Text,-    key :: {-# UNPACK #-} Text,-    thumbnail :: {-# UNPACK #-} Text-  }-  deriving (Show, Eq, Generic)--instance FromJSON MpvImage where-  parseJSON = withObject "mpv image" $ \o ->-    MpvImage-      <$> o .: "n"-      <*> o .: "k"-      <*> o .: "t"---- | All the variables defined in the scripts that came with the MPV-data Vars = Vars-  { gid :: {-# UNPACK #-} Int,-    mpvkey :: {-# UNPACK #-} Text,-    apiUrl :: {-# UNPACK #-} Text,-    pageCount :: {-# UNPACK #-} Int,-    imageList :: [MpvImage]-  }-  deriving (Show, Eq, Generic)--extractEnv :: Text -> IO (Result Vars)-extractEnv script = quickjs $ do-  eval_ $ encodeUtf8 script-  gid' <- eval "gid"-  mpvkey' <- eval "mpvkey"-  imageList' <- eval "imagelist"-  apiUrl' <- eval "api_url"-  pageCount' <- eval "pagecount"-  pure $ do-    gid <- fromJSON gid'-    mpvkey <- fromJSON mpvkey'-    imageList <- fromJSON imageList'-    apiUrl <- fromJSON apiUrl'-    pageCount <- fromJSON pageCount'-    pure Vars {..}
src/Web/Exhentai/Parsing/Search.hs view
@@ -1,32 +1,36 @@+-- | Internal modules module Web.Exhentai.Parsing.Search where -import Control.Lens import Data.Text (Text)-import Text.XML.Lens-import Web.Exhentai.Types+import Optics.Core+import Text.XML.Optics import Web.Exhentai.Utils import Prelude hiding (div)  pages :: Traversal' Element Int-pages = pagesElem ... a . lower . _Content . viaShowRead+pages = pagesElem .// (a % lower %> _Content % viaShowRead)+{-# INLINE pages #-}  pagesElem :: Traversal' Element Element-pagesElem = cl "ido" ... div ... cl "ptt" ... tr ... td+pagesElem = cl "ido" .// div .// cl "ptt" .// tr .// td+{-# INLINE pagesElem #-}  linkOf :: Traversal' Element Text-linkOf = lower . _Element . attr "href"+linkOf = lower %> _Element % attr "href"+{-# INLINE linkOf #-}  galleryPreviewElement :: Traversal' Element Element-galleryPreviewElement = cl "ido" ... div ... cl "itg glte" ... tr+galleryPreviewElement = cl "ido" .// div .// cl "itg glte" .// tr+{-# INLINE galleryPreviewElement #-}  previewImage :: Traversal' Element Text-previewImage = tr ... cl "gl1e" ... div ... a ... img . attr "src"+previewImage = tr .// cl "gl1e" .// div .// a .// (img % attr "src")+{-# INLINE previewImage #-}  title :: Traversal' Element Text-title = tr ... cl "gl1e" ... div ... a ... img . attr "title"--galleryLink :: Traversal' Element Gallery-galleryLink = tr ... cl "gl1e" ... div ... a . attr "href" . _GalleryLink+title = tr .// cl "gl1e" .// div .// a .// (img % attr "title")+{-# INLINE title #-} -galleryLength :: Traversal' Element GalleryLength-galleryLength = tr ... cl "gl2e" ... div ... cl "gl3e" ... lower . _Content . _GalleryLength+galleryLink :: Traversal' Element Text+galleryLink = tr .// cl "gl1e" .// div .// (a % attr "href")+{-# INLINE galleryLink #-}
− src/Web/Exhentai/Types.hs
@@ -1,187 +0,0 @@-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}--module Web.Exhentai.Types where--import Control.Applicative ((<|>))-import Control.Lens-import Data.Set (Set, fromList, toList)-import Data.Text (Text, pack)-import Data.Void-import GHC.Generics-import Text.Megaparsec-  ( MonadParsec (notFollowedBy, takeWhile1P),-    Parsec,-    anySingle,-    chunk,-    many,-    optional,-    parseMaybe,-    single,-    takeRest,-  )-import Text.Megaparsec.Char.Lexer--type Parser = Parsec Void Text--data GalleryCat-  = Misc-  | Doujinshi-  | Manga-  | ArtistCG-  | GameCG-  | ImageSet-  | Cosplay-  | AsianPorn-  | NonH-  | Western-  | Private-  deriving (Show, Eq, Ord, Enum, Bounded)--allGalleryCats :: Set GalleryCat-allGalleryCats = fromList [Misc .. Private]--toBitField :: Set GalleryCat -> Int-toBitField = sum . map ((2 ^) . fromEnum) . toList--showCat :: GalleryCat -> Text-showCat Doujinshi = "Doujinshi"-showCat Manga = "Manga"-showCat ArtistCG = "Artist CG"-showCat GameCG = "Game CG"-showCat NonH = "Non-H"-showCat ImageSet = "Image Set"-showCat Western = "Western"-showCat Cosplay = "Cosplay"-showCat Misc = "Misc"-showCat Private = "Private"-showCat AsianPorn = "Asian Porn"--readCat :: Text -> Maybe GalleryCat-readCat "Doujinshi" = Just Doujinshi-readCat "Manga" = Just Manga-readCat "Artist CG" = Just ArtistCG-readCat "Game CG" = Just GameCG-readCat "Non-H" = Just NonH-readCat "Image Set" = Just ImageSet-readCat "Western" = Just Western-readCat "Cosplay" = Just Cosplay-readCat "Misc" = Just Misc-readCat "Private" = Just Private-readCat "Asian Porn" = Just AsianPorn-readCat _ = Nothing--_GalleryCat :: Prism' Text GalleryCat-_GalleryCat = prism' showCat readCat--newtype PopUpLink = PopUpLink {unLink :: Text}-  deriving newtype (Show, Eq)--_PopUpLink :: Prism' Text PopUpLink-_PopUpLink = prism' unLink parsePopUpLink--parsePopUpLink :: Text -> Maybe PopUpLink-parsePopUpLink = parseMaybe archiverLink-  where-    archiverLink :: Parser PopUpLink-    archiverLink = do-      _ <- chunk "return popUp('"-      url <- takeWhile1P Nothing (/= '\'')-      _ <- takeRest-      pure $ PopUpLink url--newtype AverageRating = AverageRating {unRating :: Float}-  deriving newtype (Show, Eq)--_AverageRating :: Prism' Text AverageRating-_AverageRating = prism' (pack . show . unRating) parseAverageRating--parseAverageRating :: Text -> Maybe AverageRating-parseAverageRating = parseMaybe averageRating-  where-    averageRating :: Parser AverageRating-    averageRating =-      ( do-          _ <- chunk "Average: "-          AverageRating <$> float-      )-        <|> (chunk "Not Yet Rated" >> pure (AverageRating 0))--newtype GalleryLength = GalleryLength {unGalleryLength :: Int}-  deriving newtype (Show, Eq)--_GalleryLength :: Prism' Text GalleryLength-_GalleryLength = prism' (pack . show . unGalleryLength) parseGalleryLength--parseGalleryLength :: Text -> Maybe GalleryLength-parseGalleryLength = parseMaybe galleryLength-  where-    galleryLength :: Parser GalleryLength-    galleryLength = do-      d <- decimal-      _ <- chunk " pages"-      pure $ GalleryLength d--newtype FavoriteCount = FavoriteCount {unFavoriteCount :: Int}-  deriving newtype (Show, Eq)--parseFavoriteCount :: Text -> Maybe FavoriteCount-parseFavoriteCount = parseMaybe favoriteCount-  where-    once = do-      _ <- chunk "Once"-      pure $ FavoriteCount 1-    never = do-      _ <- chunk "Never"-      pure $ FavoriteCount 0-    favoriteCount :: Parser FavoriteCount-    favoriteCount =-      ( do-          d <- decimal-          _ <- chunk " times"-          pure $ FavoriteCount d-      )-        <|> once-        <|> never--data Gallery = Gallery-  { galleryId :: {-# UNPACK #-} !Int,-    token :: {-# UNPACK #-} !Text-  }-  deriving (Show, Eq, Generic)--_GalleryLink :: Prism' Text Gallery-_GalleryLink = prism' toGalleryLink parseGalleryLink--toGalleryLink :: Gallery -> Text-toGalleryLink Gallery {..} = "https://exhentai.org/g/" <> pack (show galleryId) <> "/" <> token <> "/"--toMpvLink :: Gallery -> Text-toMpvLink Gallery {..} = "https://exhentai.org/mpv/" <> pack (show galleryId) <> "/" <> token <> "/"--parseGalleryLink :: Text -> Maybe Gallery-parseGalleryLink = parseMaybe galleryLink-  where-    galleryLink :: Parser Gallery-    galleryLink = do-      _ <- chunk "https://exhentai.org/g/"-      galleryId <- decimal-      _ <- single '/'-      token <- takeWhile1P Nothing (/= '/')-      _ <- optional $ single '/'-      pure Gallery {..}--parsePreviewLink :: Text -> Maybe Text-parsePreviewLink = parseMaybe previewLink-  where-    previewLink :: Parser Text-    previewLink = do-      _ <- many $ do-        notFollowedBy urlOpening-        anySingle-      _ <- urlOpening-      url <- takeWhile1P Nothing (/= ')')-      _ <- takeRest-      pure url-    urlOpening :: Parser Text-    urlOpening = chunk "url("
− src/Web/Exhentai/Types/CookieT.hs
@@ -1,180 +0,0 @@-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE UndecidableInstances #-}--module Web.Exhentai.Types.CookieT where--import Conduit-import Control.Concurrent-import Control.Monad.Base-import Control.Monad.Catch-import Control.Monad.Except-import Control.Monad.Reader-import Control.Monad.Time-import Control.Monad.Trans.Control-import Control.Retry-import Data.ByteString (ByteString)-import Data.Function ((&))-import GHC.Generics-import Network.HTTP.Client.Conduit-import Network.HTTP.Client.MultipartFormData-import Network.HTTP.Client.TLS--newtype Policy = Policy RetryPolicy--class (Monad m, MonadCatch m, MonadThrow m) => MonadHttp m where-  getRetryPolicy :: m Policy-  formRequest :: String -> m Request-  attachFormData :: [Part] -> Request -> m Request-  respOpen :: MonadIO n => Request -> m (Response (ConduitT i ByteString n ()))-  respClose :: Response body -> m ()-  reqNoBody :: Request -> m (Response ())--class (MonadMask m, MonadTime m, MonadHttp m, MonadIO m) => MonadHttpState m where-  takeCookieJar :: m CookieJar-  readCookieJar :: m CookieJar-  putCookieJar :: CookieJar -> m ()--modifyingJar :: MonadHttpState m => Request -> m ()-modifyingJar req =-  bracketOnError-    takeCookieJar-    putCookieJar-    $ \jar -> do-      let req' = req {cookieJar = Just jar}-      resp <- retryWhenTimeout $ reqNoBody req'-      putCookieJar $ responseCookieJar resp-      pure ()--openWithJar :: (MonadHttpState m, MonadIO n) => Request -> m (Response (ConduitT i ByteString n ()))-openWithJar req = do-  jar <- readCookieJar-  let req' = req {cookieJar = Just jar}-  respOpen req'--withJar :: (MonadHttpState m, MonadIO n) => Request -> (ConduitT i ByteString n () -> m a) -> m a-withJar req k = do-  jar <- readCookieJar-  let req' = req {cookieJar = Just jar}-  bracket-    (respOpen req')-    respClose-    (k . responseBody)--data CookieEnv = CookieEnv-  { policy :: Policy,-    jarRef :: MVar CookieJar,-    manager :: {-# UNPACK #-} !Manager-  }-  deriving (Generic)--instance HasHttpManager CookieEnv where-  getHttpManager = manager--newtype CookieT m a = CookieT {unCookieT :: ReaderT CookieEnv m a}-  deriving newtype-    ( Functor,-      Applicative,-      Monad,-      MonadReader CookieEnv,-      MonadThrow,-      MonadCatch,-      MonadMask,-      MonadIO,-      MonadResource,-      MonadUnliftIO,-      MonadError e,-      MonadBase b,-      MonadBaseControl b,-      MonadTime-    )--runCookieT :: MonadIO m => RetryPolicy -> CookieT m a -> m a-runCookieT (Policy -> policy) m = do-  manager <- liftIO newTlsManager-  jarRef <- liftIO $ newMVar mempty-  m-    & unCookieT-    & flip runReaderT CookieEnv {..}--instance MonadTrans CookieT where-  lift = CookieT . lift--instance-  ( MonadIO m,-    MonadUnliftIO m,-    MonadCatch m,-    MonadThrow m-  ) =>-  MonadHttp (CookieT m)-  where-  getRetryPolicy = asks policy-  formRequest = parseRequest-  attachFormData = formDataBody-  respOpen = responseOpen-  respClose = responseClose-  reqNoBody = httpNoBody--instance-  {-# OVERLAPPABLE #-}-  ( MonadHttp m,-    MonadTrans f,-    Monad (f m),-    MonadCatch (f m),-    MonadThrow (f m)-  ) =>-  MonadHttp (f m)-  where-  getRetryPolicy = lift getRetryPolicy-  formRequest = lift . formRequest-  attachFormData p = lift . attachFormData p-  respOpen = lift . respOpen-  respClose = lift . respClose-  reqNoBody = lift . reqNoBody--retryWhenTimeout :: MonadHttpState m => m a -> m a-retryWhenTimeout action = do-  Policy policy <- getRetryPolicy-  recovering policy handlers (const action)-  where-    handlers =-      skipAsyncExceptions-        ++ [ const (Handler (pure . judge))-           ]-    judge (HttpExceptionRequest _ c)-      | ResponseTimeout <- c = True-      | ConnectionTimeout <- c = True-    judge _ = False--instance-  ( MonadMask m,-    MonadUnliftIO m,-    MonadTime m-  ) =>-  MonadHttpState (CookieT m)-  where-  takeCookieJar = do-    ref <- asks jarRef-    liftIO $ takeMVar ref-  putCookieJar jar = do-    ref <- asks jarRef-    liftIO $ putMVar ref jar-  readCookieJar = do-    ref <- asks jarRef-    liftIO $ readMVar ref--instance-  {-# OVERLAPPABLE #-}-  ( MonadHttpState m,-    MonadTrans f,-    MonadMask (f m),-    MonadTime (f m),-    MonadHttp (f m),-    MonadIO (f m)-  ) =>-  MonadHttpState (f m)-  where-  takeCookieJar = lift takeCookieJar-  putCookieJar = lift . putCookieJar-  readCookieJar = lift readCookieJar
src/Web/Exhentai/Utils.hs view
@@ -1,86 +1,100 @@ {-# LANGUAGE RankNTypes #-} +-- | Internal module module Web.Exhentai.Utils where  import Conduit-import Control.Lens+import Control.Effect+import Control.Effect.Bracket+import Control.Effect.Error+import Control.Effect.Exh+import Control.Monad.Trans.Cont import Data.Aeson import Data.ByteString (ByteString)-import Data.Maybe (isNothing) import Data.Text (Text, pack, unpack) import Data.Time-import Network.HTTP.Client.Conduit+import Network.HTTP.Client hiding (Cookie)+import Optics.Core import Text.HTML.DOM-import Text.Read+import Text.Read (readMaybe) import Text.XML hiding (sinkDoc)-import Text.XML.Lens-import Web.Exhentai.Types.CookieT--attributeSatisfies' :: Name -> (Maybe Text -> Bool) -> Traversal' Element Element-attributeSatisfies' n p = filtered (p . preview (attrs . ix n))--withoutAttribute :: Name -> Traversal' Element Element-withoutAttribute = flip attributeSatisfies' isNothing--lower :: Traversal' Element Node-lower = nodes . traverse+import Text.XML.Optics+import Prelude hiding ((!!))  body :: Traversal' Document Element-body = root . named "html" ... named "body"+body = (root % named "html") .// named "body"+{-# INLINE body #-} -div :: Traversal' Element Element+div :: AffineTraversal' Element Element div = named "div"+{-# INLINE div #-} -h1 :: Traversal' Element Element+h1 :: AffineTraversal' Element Element h1 = named "h1"+{-# INLINE h1 #-} -a :: Traversal' Element Element+a :: AffineTraversal' Element Element a = named "a"+{-# INLINE a #-} -table :: Traversal' Element Element+table :: AffineTraversal' Element Element table = named "table"+{-# INLINE table #-} -tr :: Traversal' Element Element+tr :: AffineTraversal' Element Element tr = named "tr"+{-# INLINE tr #-} -td :: Traversal' Element Element+td :: AffineTraversal' Element Element td = named "td"+{-# INLINE td #-} -img :: Traversal' Element Element+img :: AffineTraversal' Element Element img = named "img"+{-# INLINE img #-} -cl :: Text -> Traversal' Element Element+cl :: Text -> AffineTraversal' Element Element cl = attributeIs "class"+{-# INLINE cl #-} -id :: Text -> Traversal' Element Element+id :: Text -> AffineTraversal' Element Element id = attributeIs "id"+{-# INLINE id #-}  viaShowRead :: (Show a, Read a) => Prism' Text a viaShowRead = prism' (pack . show) (readMaybe . unpack)+{-# INLINE viaShowRead #-} -scripts :: Traversal' Element Element-scripts = named "script" . attributeIs "type" "text/javascript"+scripts :: AffineTraversal' Element Element+scripts = named "script" % attributeIs "type" "text/javascript"+{-# INLINE scripts #-}  infixl 8 ^?: -(^?:) :: Document -> Fold Element a -> Maybe a-doc ^?: fld = doc ^? body ... fld+(^?:) :: (Is (Join A_Traversal l) A_Fold, Is l (Join A_Traversal l), Is A_Traversal (Join A_Traversal l)) => Document -> Optic l is Element Element a a -> Maybe a+doc ^?: fld = doc ^? pre (body .// fld)+{-# INLINE (^?:) #-}  infixl 8 ^..: -(^..:) :: Document -> Fold Element a -> [a]-doc ^..: fld = doc ^.. body ... fld+(^..:) :: (Is (Join A_Traversal l) A_Fold, Is l (Join A_Traversal l), Is A_Traversal (Join A_Traversal l)) => Document -> Optic l is Element Element a a -> [a]+doc ^..: fld = doc ^.. body .// fld+{-# INLINE (^..:) #-}  sinkAeson :: (FromJSON a, Monad m) => ConduitT ByteString o m (Either String a) sinkAeson = eitherDecode <$> sinkLazy -jsonRequest :: (FromJSON a, MonadHttpState m) => Request -> m (Either String a)-jsonRequest req = withJar req $ \source -> runConduit $ source .| sinkAeson+jsonRequest :: (FromJSON a, Effs '[Http, Error HttpException, ConduitIO, Cookie, Bracket] m) => Request -> m (Either String a)+jsonRequest req = evalContT $ do+  resp <- withSource req+  lift $ runConduitIO $ responseBody resp .| sinkAeson -htmlRequest :: MonadHttpState m => Request -> m Document-htmlRequest req = withJar req $ \source -> runConduit $ source .| sinkDoc+htmlRequest :: Effs '[Http, Error HttpException, ConduitIO, Cookie, Bracket] m => Request -> m Document+htmlRequest req = evalContT $ do+  resp <- withSource req+  lift $ runConduitIO $ responseBody resp .| sinkDoc -htmlRequest' :: MonadHttpState m => Text -> m Document+htmlRequest' :: Effs '[Http, Error HttpException, ConduitIO, Cookie, Bracket] m => Text -> m Document htmlRequest' url = do   req <- formRequest $ unpack url   htmlRequest req@@ -91,3 +105,14 @@ annotate :: ann -> Maybe a -> Either ann a annotate _ (Just a') = Right a' annotate ann Nothing = Left ann++(!!) :: [a] -> Int -> Maybe a+l !! i+  | i == 0,+    (x : _) <- l =+    Just x+  | i > 0,+    (_ : xs) <- l =+    xs !! (i - 1)+  | otherwise = Nothing+{-# INLINE (!!) #-}
test/Spec.hs view
@@ -1,42 +1,21 @@ module Main where -import Conduit-import Control.Monad.Trans.Cont-import Control.Retry import Data.Either import Data.Maybe-import Network.HTTP.Client+import Optics.Core import Test.Hspec import Text.HTML.DOM-import Web.Exhentai.API.Auth import Web.Exhentai.API.Gallery-import Web.Exhentai.API.MPV import qualified Web.Exhentai.Parsing.Gallery as G import Web.Exhentai.Parsing.Image import qualified Web.Exhentai.Parsing.Search as S-import Web.Exhentai.Types-import Web.Exhentai.Types.CookieT import Web.Exhentai.Utils import Prelude hiding (readFile)  main :: IO () main = do   sanityCheck-  runCookieT retryPolicyDefault $ do-    auth $ Credential "VictorYu" "Victor142857"-    vars <- fetchMpv $ Gallery 1815207 "5f4ceed6d7"-    withSinkFile "1.png" $ \sinkF -> evalContT $ do-      resp <- fetchImage $ head $ toRequests vars-      let source = responseBody resp-      runConduit $ source .| sinkF -{--    withSinkFile "2.zip" $ \sinkF -> evalContT $ do-      resp <- streamOriginal "https://exhentai.org/archiver.php?gid=1800698&token=3d413c962e&or=447251--2c3a0356a675f5eb4c5a2afe08b9f15e385647bb"-      let source = responseBody resp-      runConduit $ source .| sinkF--}- sanityCheck :: IO () sanityCheck = do   image <- readFile "test/Image.html"@@ -52,7 +31,7 @@      describe "Image.nextPage" $ do       it "should return the link to the next page" $ do-        (image ^?: nextPage) `shouldSatisfy` isJust+        (image ^?: nextImage) `shouldSatisfy` isJust      describe "Gallery.enTitle" $ do       it "should return the title of the gallery" $ do@@ -113,4 +92,4 @@      describe "Search.galleryLink" $ do       it "should return galleries" $ do-        (search ^?: S.galleryPreviewElement . S.galleryLink) `shouldSatisfy` isJust+        (search ^?: S.galleryPreviewElement % S.galleryLink) `shouldSatisfy` isJust