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 +3/−0
- LICENSE +21/−0
- README.md +10/−0
- Setup.hs +2/−0
- captcha-core.cabal +62/−0
- src/Captcha.hs +47/−0
- src/Captcha/Internal.hs +43/−0
- src/Captcha/Internal/Monad.hs +41/−0
- src/Captcha/Internal/Monad/Class.hs +85/−0
- src/Captcha/Internal/Request.hs +27/−0
- src/Captcha/Internal/Types.hs +197/−0
+ 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++[](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