packages feed

captcha-core (empty) → 0.1.0.0

raw patch · 11 files changed

+538/−0 lines, 11 filesdep +aesondep +basedep +bytestringsetup-changed

Dependencies added: aeson, base, bytestring, cookie, data-default-extra, lens, mtl, o-clock, string-conversions, text, unliftio, wreq

Files

+ CHANGELOG.md view
@@ -0,0 +1,3 @@+## 0.1.0.0++- Initial release.
+ LICENSE view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2021 Edward Yang++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,10 @@+# captcha-haskell++[![Hackage](http://img.shields.io/hackage/v/captcha-core.svg)](https://hackage.haskell.org/package/captcha-core)++This repository is split into multiple packages:+- [captcha-core](https://github.com/qwbarch/captcha-haskell/tree/main/core)+- [captcha-2captcha](https://github.com/qwbarch/captcha-haskell/tree/main/2captcha)+- [captcha-capmonster](https://github.com/qwbarch/captcha-haskell/tree/main/capmonster)++Visit the [microsite](https://qwbarch.github.io/captcha-haskell/) for more information.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ captcha-core.cabal view
@@ -0,0 +1,62 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.34.4.+--+-- see: https://github.com/sol/hpack++name:           captcha-core+version:        0.1.0.0+synopsis:       A package for integrating a variety of captcha solving services.+description:    A package for integrating a variety of captcha solving services.+                .+                Feature list:+                .+                * Makes use of the [lens](https://hackage.haskell.org/package/lens) package+                * Mtl-style typeclass, allowing you to use your own monad transformer stack+                * No exceptions are thrown. Errors are shown explicitly through its type signature+                * Minimal test suite provided for each captcha type+                * Captcha services are separated into different packages. Use what you need!+category:       Network+homepage:       https://github.com/qwbarch/captcha-haskell#readme+bug-reports:    https://github.com/qwbarch/captcha-haskell/issues+author:         Edward Yang+maintainer:     qwbarch@gmail.com+copyright:      2021 Edward Yang+license:        MIT+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md+    CHANGELOG.md++source-repository head+  type: git+  location: https://github.com/qwbarch/captcha-haskell++library+  exposed-modules:+      Captcha+      Captcha.Internal+      Captcha.Internal.Monad+      Captcha.Internal.Monad.Class+      Captcha.Internal.Request+      Captcha.Internal.Types+  other-modules:+      Paths_captcha_core+  hs-source-dirs:+      src+  ghc-options: -Wall -Wno-name-shadowing+  build-depends:+      aeson >=1.5.6 && <1.6+    , base >=4.7 && <5+    , bytestring >=0.10.12.0 && <0.11+    , cookie >=0.4.5 && <0.5+    , data-default-extra >=0.0.1 && <0.1+    , lens >=4.19.2 && <5.1+    , mtl >=2.2.2 && <2.3+    , o-clock >=1.2.1 && <1.3+    , string-conversions >=0.4.0.1 && <0.5+    , text >=1.2.4.1 && <1.3+    , unliftio >=0.2.20 && <0.3+    , wreq >=0.5.3.3 && <0.6+  default-language: Haskell2010
+ src/Captcha.hs view
@@ -0,0 +1,47 @@+-- |+-- Module: Captcha+-- Copyright: (c) 2022 Edward Yang+-- License: MIT+--+-- This module exports functions that follow the pvp versioning policies.+module Captcha+  ( module Captcha.Internal.Types,+    module Captcha.Internal.Monad,+    module Captcha.Internal.Monad.Class,+  )+where++import Captcha.Internal.Monad+import Captcha.Internal.Monad.Class+import Captcha.Internal.Types+  ( FunCaptcha,+    HCaptcha,+    HasAction (..),+    HasAddress (..),+    HasApiKey (..),+    HasAuth (..),+    HasBody (..),+    HasCaptchaKey (..),+    HasCaptchaUrl (..),+    HasCookies (..),+    HasDataS (..),+    HasInvisible (..),+    HasMinScore (..),+    HasPassword (..),+    HasPollingInterval (..),+    HasPort (..),+    HasProtocol (..),+    HasProxy (..),+    HasRqData (..),+    HasServiceUrl (..),+    HasTimeoutDuration (..),+    HasUserAgent (..),+    HasUsername (..),+    ImageCaptcha,+    Proxy (..),+    ProxyAuth (..),+    ProxyProtocol (..),+    ReCaptchaV2,+    ReCaptchaV3,+    TextCaptcha,+  )
+ src/Captcha/Internal.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE FlexibleContexts #-}++-- |+-- Module: Captcha.Internal+-- Copyright: (c) 2022 Edward Yang+-- License: MIT+--+-- This module is for internal-use and does not follow pvp versioning policies.+module Captcha.Internal where++import Captcha.Internal.Types (HasAddress (address), HasAuth (auth), HasCookies (cookies), HasPassword (password), HasPort (port), HasProtocol (protocol), HasProxy (proxy), HasUsername (username), Proxy)+import Control.Lens (preview, view, (^?), _Just)+import Data.ByteString.Builder (toLazyByteString)+import Data.String.Conversions (cs)+import Data.Text (Text)+import Data.Text.Lazy (toStrict)+import Data.Text.Lazy.Encoding (decodeUtf8)+import Web.Cookie (Cookies)+import qualified Web.Cookie as Cookie++-- | Render the cookies as a lazy text.+renderCookies :: HasCookies a Cookies => a -> Text+renderCookies = toStrict . decodeUtf8 . toLazyByteString . Cookie.renderCookies . view cookies++-- | Retrieve the proxy's type as, converted into 'Text'.+getProxyType :: HasProxy a (Maybe Proxy) => a -> Maybe Text+getProxyType captcha = cs . show <$> captcha ^? proxy . _Just . protocol++-- | Retrieve the proxy's host address.+getProxyAddress :: HasProxy a (Maybe Proxy) => a -> Maybe Text+getProxyAddress = preview $ proxy . _Just . address++-- | Retrieve the proxy's port.+getProxyPort :: HasProxy a (Maybe Proxy) => a -> Maybe Int+getProxyPort = preview $ proxy . _Just . port++-- | Retrieve the proxy's authentication username.+getProxyUsername :: HasProxy a (Maybe Proxy) => a -> Maybe Text+getProxyUsername = preview $ proxy . _Just . auth . _Just . username++-- | Retrieve the proxy's authentication password.+getProxyPassword :: HasProxy a (Maybe Proxy) => a -> Maybe Text+getProxyPassword = preview $ proxy . _Just . auth . _Just . password
+ src/Captcha/Internal/Monad.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE TemplateHaskell #-}++-- |+-- Module: Captcha.Internal.Monad+-- Copyright: (c) 2022 Edward Yang+-- License: MIT+--+-- This module is for internal-use and does not follow pvp versioning policies.+module Captcha.Internal.Monad where++import Control.Lens.TH (makeClassy)+import Control.Monad.Cont (MonadIO (liftIO))+import Control.Monad.Reader (MonadReader, ReaderT (ReaderT))+import Network.Wreq.Session (Session, newSession)+import UnliftIO (MonadUnliftIO)++-- | Effect providing an environment required to solve captchas.+newtype Captcha a = Captcha+  { runCaptcha :: CaptchaEnv -> IO a+  }+  deriving+    ( Functor,+      Applicative,+      Monad,+      MonadIO,+      MonadReader CaptchaEnv,+      MonadUnliftIO+    )+    via ReaderT CaptchaEnv IO++-- | Provides an HTTP 'Session' to be reused for each request.+newtype CaptchaEnv = CaptchaEnv+  { _session :: Session+  }++makeClassy ''CaptchaEnv++-- | Create the environment required to solve captchas.+mkCaptchaEnv :: MonadIO m => m CaptchaEnv+mkCaptchaEnv = CaptchaEnv <$> liftIO newSession
+ src/Captcha/Internal/Monad/Class.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}++-- |+-- Module: Captcha.Internal.Monad.Class+-- Copyright: (c) 2022 Edward Yang+-- License: MIT+--+-- This module is for internal-use and does not follow pvp versioning policies.+module Captcha.Internal.Monad.Class where++import Captcha.Internal.Types (HasApiKey, HasPollingInterval, HasTimeoutDuration)+import Data.Aeson (Value)+import Data.ByteString.Lazy (ByteString)+import Data.Text (Text)+import Network.Wreq (Response)+import Time (Millisecond, Time)++-- | Abstracts over a captcha solving service.+class Monad m => MonadCaptcha api r m where+  -- | An error specific to the captcha solving service.+  type CaptchaError api r m++  -- | Submit a task to be solved by the api service.+  createTask ::+    CaptchaRequest api ctx r m =>+    -- | The captcha to be solved.+    ctx ->+    -- | Captcha id to be used with 'getTask'.+    m (Either (CaptchaError api r m) (CaptchaId ctx))++  -- | Attempt to retrieve the answer of the captcha.+  getTask ::+    CaptchaResponse api ctx =>+    -- | The captcha service's API key.+    Text ->+    -- | The captcha to check the answer of.+    CaptchaId ctx ->+    -- | The captcha's solution.+    m (Either (CaptchaError api r m) Text)++  -- |+  -- Solves a captcha by submitting it with 'createTask' and then polling with 'getTask'+  -- until the answer is ready.+  --+  -- This will poll until the configured timeout duration is past.+  -- Its default value depends on the captcha service.+  solve ::+    ( CaptchaRequest api ctx r m,+      CaptchaResponse api ctx,+      HasApiKey ctx Text,+      HasPollingInterval ctx (Maybe (Time Millisecond)),+      HasTimeoutDuration ctx (Maybe (Time Millisecond))+    ) =>+    -- | Captcha to be solved.+    ctx ->+    -- | The captcha's solution.+    m (Either (CaptchaError api r m) Text)++-- |+-- Different captcha services have different request formats.+-- This abstracts over it and sends the correct HTTP request.+class CaptchaRequest api ctx r m where+  -- | Send a request using the given captcha context.+  request ::+    -- | The captcha to be solved.+    ctx ->+    -- | The url to send the request to.+    Text ->+    m (Response ByteString)++-- |+-- Different captcha services have different response formats.+-- This abstracts over it and provides the captcha result.+class CaptchaResponse api ctx where+  -- | Parse the captcha result from the given json.+  parseResult :: Value -> Maybe Value++-- | Identifier for retrieving a captcha's answer.+newtype CaptchaId ctx = CaptchaId+  { unCaptchaId :: Integer+  }+  deriving (Show, Eq, Ord)
+ src/Captcha/Internal/Request.hs view
@@ -0,0 +1,27 @@+-- |+-- Module: Captcha.Internal.Request+-- Copyright: (c) 2022 Edward Yang+-- License: MIT+--+-- This module is for internal-use and does not follow pvp versioning policies.+module Captcha.Internal.Request where++import Captcha.Internal.Monad (HasCaptchaEnv (session))+import Control.Lens ((^.))+import Control.Monad.Cont (MonadIO (liftIO))+import Control.Monad.Reader (MonadReader (ask))+import Data.ByteString.Lazy (ByteString)+import Data.Text (Text, unpack)+import Network.Wreq (Options, Response)+import qualified Network.Wreq.Session as Session+import Network.Wreq.Types (Postable)++-- | Send a POST request with the given session from 'CaptchaEnv'.+post :: (HasCaptchaEnv r, MonadReader r m, MonadIO m, Postable a) => Options -> Text -> a -> m (Response ByteString)+post options url payload =+  ask >>= \env -> liftIO $ Session.postWith options (env ^. session) (unpack url) payload++-- | Send a GET request with the given session from 'CaptchaEnv'.+get :: (HasCaptchaEnv r, MonadReader r m, MonadIO m) => Options -> Text -> m (Response ByteString)+get options url =+  ask >>= \env -> liftIO $ Session.getWith options (env ^. session) (unpack url)
+ src/Captcha/Internal/Types.hs view
@@ -0,0 +1,197 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE StrictData #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -Wno-orphans #-}++-- |+-- Module: Captcha.Internal.Types+-- Copyright: (c) 2022 Edward Yang+-- License: MIT+--+-- This module is for internal-use and does not follow pvp versioning policies.+module Captcha.Internal.Types where++import Control.Lens.TH (makeFieldsNoPrefix)+import Data.Default (Default (def))+import Data.Text (Text)+import GHC.Generics (Generic)+import Time (Millisecond, Time)+import Web.Cookie (Cookies)++-- | 'Default' instance for 'Bool' is not defined by default.+instance Default Bool where+  def = False++-- | Proxy protocol.+data ProxyProtocol = Http | Https | Socks4 | Socks5 deriving (Show)++instance Default ProxyProtocol where+  def = Http++-- | Proxy authentication.+data ProxyAuth = ProxyAuth+  { _username :: Text,+    _password :: Text+  }+  deriving (Generic, Default, Show)++makeFieldsNoPrefix ''ProxyAuth++-- | Proxy to be used when solving a captcha.+data Proxy = Proxy+  { -- | Proxy address.+    _address :: Text,+    -- | Protocol of the proxy.+    _protocol :: ProxyProtocol,+    -- | Proxy port.+    _port :: Int,+    -- | Proxy authentication, if required.+    _auth :: Maybe ProxyAuth+  }+  deriving (Generic, Default, Show)++makeFieldsNoPrefix ''Proxy++-- | Parameters for solving a captcha with text within an image.+data ImageCaptcha = ImageCaptcha+  { -- | The captcha solver's API key.+    _apiKey :: Text,+    -- | The interval to poll for the captcha's answer.+    _pollingInterval :: Maybe (Time Millisecond),+    -- | The duration to keep polling for the answer.+    _timeoutDuration :: Maybe (Time Millisecond),+    -- | The image, encoded in base-64.+    _body :: Text+  }+  deriving (Generic, Default, Show)++makeFieldsNoPrefix ''ImageCaptcha++-- | Parameters for solving a text captcha.+data TextCaptcha = TextCaptcha+  { -- | The captcha solver's API key.+    _apiKey :: Text,+    -- | The interval to poll for the captcha's answer.+    _pollingInterval :: Maybe (Time Millisecond),+    -- | The duration to keep polling for the answer.+    _timeoutDuration :: Maybe (Time Millisecond),+    -- | The text captcha to solve.+    _body :: Text+  }+  deriving (Generic, Default, Show)++makeFieldsNoPrefix ''TextCaptcha++-- | Parameters for solving Arkose Lab's FunCaptcha.+data FunCaptcha = FunCaptcha+  { -- | The captcha solver's API key.+    _apiKey :: Text,+    -- | The interval to poll for the captcha's answer.+    _pollingInterval :: Maybe (Time Millisecond),+    -- | The duration to keep polling for the answer.+    _timeoutDuration :: Maybe (Time Millisecond),+    -- | Url where the captcha is found.+    _captchaUrl :: Text,+    -- | FunCaptcha's __data-pkey__ value.+    _captchaKey :: Text,+    -- | FunCaptcha's __surl__ service url value.+    _serviceUrl :: Maybe Text,+    -- | User agent to be used when solving the captcha.+    _userAgent :: Maybe Text,+    -- | Proxy to be used when solving the captcha.+    _proxy :: Maybe Proxy,+    -- | Cookies to be used when solving the captcha.+    _cookies :: Cookies+  }+  deriving (Generic, Default, Show)++makeFieldsNoPrefix ''FunCaptcha++-- | Parameters for solving Google's reCAPTCHA v2.+data ReCaptchaV2 = ReCaptchaV2+  { -- | The captcha solver's API key.+    _apiKey :: Text,+    -- | The interval to poll for the captcha's answer.+    _pollingInterval :: Maybe (Time Millisecond),+    -- | The duration to keep polling for the answer.+    _timeoutDuration :: Maybe (Time Millisecond),+    -- | Url where the captcha is found.+    _captchaUrl :: Text,+    -- | reCAPTCHA v2's __data-sitekey__ value.+    _captchaKey :: Text,+    -- | reCAPTCHA's v2's __data-s__ value.+    _dataS :: Maybe Text,+    -- | Is the reCAPTCHA an __invisible__ or __normal__ captcha?+    _invisible :: Bool,+    -- | User agent to be used when solving the captcha.+    _userAgent :: Maybe Text,+    -- | Proxy to be used when solving the captcha.+    _proxy :: Maybe Proxy,+    -- | Cookies to be used when solving the captcha.+    _cookies :: Cookies+  }+  deriving (Generic, Default, Show)++makeFieldsNoPrefix ''ReCaptchaV2++-- | Parameters for solving Google's reCAPTCHA v3.+data ReCaptchaV3 = ReCaptchaV3+  { -- | The captcha solver's API key.+    _apiKey :: Text,+    -- | The interval to poll for the captcha's answer.+    _pollingInterval :: Maybe (Time Millisecond),+    -- | The duration to keep polling for the answer.+    _timeoutDuration :: Maybe (Time Millisecond),+    -- | Url where the captcha is found.+    _captchaUrl :: Text,+    -- | reCAPTCHA v3's __sitekey__ value.+    _captchaKey :: Text,+    -- | reCAPTCHA v3's minimum score.+    _minScore :: Double,+    -- | reCAPTCHA v3's __action__ value.+    _action :: Maybe Text,+    -- | User agent to be used when solving the captcha.+    _userAgent :: Maybe Text,+    -- | Proxy to be used when solving the captcha.+    _proxy :: Maybe Proxy,+    -- | Cookies to be used when solving the captcha.+    _cookies :: Cookies+  }+  deriving (Generic, Default, Show)++makeFieldsNoPrefix ''ReCaptchaV3++-- | Parameters for solving hCaptcha.+data HCaptcha = HCaptcha+  { -- | The captcha solver's API key.+    _apiKey :: Text,+    -- | The interval to poll for the captcha's answer.+    _pollingInterval :: Maybe (Time Millisecond),+    -- | The duration to keep polling for the answer.+    _timeoutDuration :: Maybe (Time Millisecond),+    -- | Url where the captcha is found.+    _captchaUrl :: Text,+    -- | hCaptcha's __data-sitekey__ value.+    _captchaKey :: Text,+    -- | Is the hCaptcha an __invisible__ or __normal__ captcha?+    _invisible :: Bool,+    -- |+    -- Custom data used in some implementations of hCaptcha.+    -- Note: You must provide a matching user agent if this is used.+    _rqData :: Maybe Text,+    -- | User agent to be used when solving the captcha. Required when using 'rqData'.+    _userAgent :: Maybe Text,+    -- | Proxy to be used when solving the captcha.+    _proxy :: Maybe Proxy,+    -- | Cookies to be used when solving the captcha.+    _cookies :: Cookies+  }+  deriving (Show, Generic, Default)++makeFieldsNoPrefix ''HCaptcha