captcha-2captcha (empty) → 0.1.0.0
raw patch · 22 files changed
+885/−0 lines, 22 filesdep +aesondep +basedep +bytestringsetup-changed
Dependencies added: aeson, base, bytestring, captcha-2captcha, captcha-core, data-default, errors, extra, http-client, lens, lens-aeson, mtl, o-clock, string-conversions, string-interpolate, tasty, tasty-hunit, text, unliftio, wreq
Files
- CHANGELOG.md +3/−0
- LICENSE +21/−0
- README.md +10/−0
- Setup.hs +2/−0
- captcha-2captcha.cabal +107/−0
- src/Captcha/TwoCaptcha.hs +20/−0
- src/Captcha/TwoCaptcha/Internal.hs +144/−0
- src/Captcha/TwoCaptcha/Internal/Error.hs +158/−0
- src/Captcha/TwoCaptcha/Internal/Types/FunCaptcha.hs +38/−0
- src/Captcha/TwoCaptcha/Internal/Types/HCaptcha.hs +40/−0
- src/Captcha/TwoCaptcha/Internal/Types/Image.hs +32/−0
- src/Captcha/TwoCaptcha/Internal/Types/ReCaptchaV2.hs +42/−0
- src/Captcha/TwoCaptcha/Internal/Types/ReCaptchaV3.hs +36/−0
- src/Captcha/TwoCaptcha/Internal/Types/Text.hs +31/−0
- test/Captcha/TwoCaptcha.hs +45/−0
- test/Captcha/TwoCaptcha/Test/FunCaptcha.hs +22/−0
- test/Captcha/TwoCaptcha/Test/HCaptcha.hs +22/−0
- test/Captcha/TwoCaptcha/Test/Image.hs +22/−0
- test/Captcha/TwoCaptcha/Test/ReCaptchaV2.hs +22/−0
- test/Captcha/TwoCaptcha/Test/ReCaptchaV3.hs +22/−0
- test/Captcha/TwoCaptcha/Test/Text.hs +21/−0
- test/Main.hs +25/−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-2captcha.cabal view
@@ -0,0 +1,107 @@+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-2captcha+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.TwoCaptcha+ Captcha.TwoCaptcha.Internal+ Captcha.TwoCaptcha.Internal.Error+ Captcha.TwoCaptcha.Internal.Types.FunCaptcha+ Captcha.TwoCaptcha.Internal.Types.HCaptcha+ Captcha.TwoCaptcha.Internal.Types.Image+ Captcha.TwoCaptcha.Internal.Types.ReCaptchaV2+ Captcha.TwoCaptcha.Internal.Types.ReCaptchaV3+ Captcha.TwoCaptcha.Internal.Types.Text+ other-modules:+ Paths_captcha_2captcha+ 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+ , captcha-core ==0.1.*+ , errors >=2.3.0 && <2.4+ , extra >=1.7.9 && <1.8+ , http-client >=0.6.4.1 && <0.8+ , lens >=4.19.2 && <5.1+ , lens-aeson >=1.1.3 && <1.2+ , mtl >=2.2.2 && <2.3+ , o-clock >=1.2.1 && <1.3+ , string-conversions >=0.4.0.1 && <0.5+ , string-interpolate >=0.3.1.1 && <0.4+ , text >=1.2.4.1 && <1.3+ , unliftio >=0.2.20 && <0.3+ , wreq >=0.5.3.3 && <0.6+ default-language: Haskell2010++test-suite 2captcha-test+ type: exitcode-stdio-1.0+ main-is: Main.hs+ other-modules:+ Captcha.TwoCaptcha+ Captcha.TwoCaptcha.Test.FunCaptcha+ Captcha.TwoCaptcha.Test.HCaptcha+ Captcha.TwoCaptcha.Test.Image+ Captcha.TwoCaptcha.Test.ReCaptchaV2+ Captcha.TwoCaptcha.Test.ReCaptchaV3+ Captcha.TwoCaptcha.Test.Text+ Paths_captcha_2captcha+ hs-source-dirs:+ test+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -Wno-name-shadowing+ build-depends:+ aeson >=1.5.6 && <1.6+ , base >=4.7 && <5+ , bytestring >=0.10.12.0 && <0.11+ , captcha-2captcha+ , captcha-core ==0.1.*+ , data-default+ , errors >=2.3.0 && <2.4+ , extra >=1.7.9 && <1.8+ , http-client >=0.6.4.1 && <0.8+ , lens >=4.19.2 && <5.1+ , lens-aeson >=1.1.3 && <1.2+ , mtl >=2.2.2 && <2.3+ , o-clock >=1.2.1 && <1.3+ , string-conversions >=0.4.0.1 && <0.5+ , string-interpolate >=0.3.1.1 && <0.4+ , tasty+ , tasty-hunit+ , 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/TwoCaptcha.hs view
@@ -0,0 +1,20 @@+-- |+-- Module: Captcha.TwoCaptcha+-- Copyright: (c) 2022 Edward Yang+-- License: MIT+--+-- This module exports functions that follow the pvp versioning policies.+module Captcha.TwoCaptcha+ ( module Captcha.TwoCaptcha.Internal.Error,+ module Captcha.TwoCaptcha.Internal,+ )+where++import Captcha.TwoCaptcha.Internal (TwoCaptcha)+import Captcha.TwoCaptcha.Internal.Error (TwoCaptchaError (..), TwoCaptchaErrorCode (..))+import Captcha.TwoCaptcha.Internal.Types.FunCaptcha ()+import Captcha.TwoCaptcha.Internal.Types.HCaptcha ()+import Captcha.TwoCaptcha.Internal.Types.Image ()+import Captcha.TwoCaptcha.Internal.Types.ReCaptchaV2 ()+import Captcha.TwoCaptcha.Internal.Types.ReCaptchaV3 ()+import Captcha.TwoCaptcha.Internal.Types.Text ()
+ src/Captcha/TwoCaptcha/Internal.hs view
@@ -0,0 +1,144 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NumericUnderscores #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}++-- |+-- Module: Captcha.TwoCaptcha.Internal+-- Copyright: (c) 2022 Edward Yang+-- License: MIT+--+-- This module is for internal-use and does not follow pvp versioning policies.+module Captcha.TwoCaptcha.Internal where++import Captcha.Internal (getProxyAddress, getProxyPassword, getProxyPort, getProxyUsername)+import Captcha.Internal.Monad (HasCaptchaEnv)+import Captcha.Internal.Monad.Class (CaptchaId (CaptchaId, unCaptchaId), CaptchaRequest (request), CaptchaResponse (parseResult), MonadCaptcha (CaptchaError, createTask, getTask, solve))+import Captcha.Internal.Request (get)+import Captcha.Internal.Types (HasApiKey (apiKey), HasPollingInterval (pollingInterval), HasProtocol (protocol), HasProxy (proxy), HasTimeoutDuration (timeoutDuration), Proxy)+import Captcha.TwoCaptcha.Internal.Error (TwoCaptchaError (NetworkError, TimeoutError, TwoCaptchaResponseError, UnknownError, UnknownResponseError), TwoCaptchaErrorCode (BadDuplicates, CaptchaNotReady, CaptchaUnsolvable), parseError)+import Control.Error (ExceptT (ExceptT), note, runExceptT)+import Control.Lens (preview, view, (&), (.~), (^.), (^?), _Just)+import Control.Monad (liftM2, (<=<))+import Control.Monad.Reader (MonadReader)+import Data.Aeson (Value)+import Data.Aeson.Lens (key, _Integer, _String, _Value)+import Data.Bifunctor (Bifunctor (bimap))+import Data.ByteString.Lazy (ByteString)+import Data.Maybe (fromMaybe, maybeToList)+import Data.String.Conversions (cs)+import Data.String.Interpolate (i)+import Data.Text (Text, toUpper)+import Data.Text.Read (decimal)+import Network.HTTP.Client (HttpException)+import Network.Wreq (Options, Response, defaults, param, responseBody)+import Time (Microsecond, Millisecond, Time (Time), threadDelay, toNum)+import UnliftIO (MonadUnliftIO, timeout, try)++-- | Used for picking 'MonadCaptcha' instances for 2Captcha.+data TwoCaptcha++-- | Parse the http response into the captcha answer, handling any errors found.+parseResponse :: (Value -> Maybe Value) -> Either HttpException (Response ByteString) -> Either TwoCaptchaError Value+parseResponse f response =+ liftM2 (=<<) adaptError parseStatus =<< parseBody+ where+ errorFooter = "This is likely due to a change in 2Captcha's API and will need to be fixed." :: Text+ missingResponse = [i|The response body is missing. #{errorFooter}|]+ invalidResponse = [i|Unable to parse 2Captcha response. #{errorFooter}|]+ parseBody = note (UnknownError missingResponse) =<< bimap NetworkError (preview $ responseBody . _Value) response+ parseStatus body = note (UnknownError "2Captcha response body is missing a status/request field.") $ do+ status <- body ^? key "status" . _Integer+ code <- body ^? key "request" . _String+ return (status, code, fromMaybe mempty (body ^? key "error_text" . _String))+ adaptError body (status, code, errorText)+ | status == 0 =+ case parseError code of+ Just exception -> Left $ TwoCaptchaResponseError exception+ Nothing -> Left $ UnknownResponseError code errorText+ | otherwise = note (UnknownError invalidResponse) (f body)++instance (HasCaptchaEnv r, MonadReader r m, MonadUnliftIO m) => MonadCaptcha TwoCaptcha r m where+ type CaptchaError TwoCaptcha r m = TwoCaptchaError++ createTask :: forall ctx. CaptchaRequest TwoCaptcha ctx r m => ctx -> m (Either TwoCaptchaError (CaptchaId ctx))+ createTask captcha =+ (parseCaptchaId <=< parseResponse (preview $ key "request")) <$> try (request @TwoCaptcha @ctx @r @m captcha url)+ where+ url = "https://2captcha.com/in.php"+ captchaIdMissing = [i|2Captcha did not send a captcha id after creating a task.|]+ captchaIdInvalid captchaId = [i|CaptchaId is not an Integer: #{captchaId}|]+ parseCaptchaId captchaId =+ bimap (const . UnknownError $ captchaIdInvalid captchaId) (CaptchaId . fst) . decimal+ =<< note (UnknownError captchaIdMissing) (captchaId ^? _String)++ getTask :: forall ctx. CaptchaResponse TwoCaptcha ctx => Text -> CaptchaId ctx -> m (Either TwoCaptchaError Text)+ getTask apiKey captchaId =+ fmap (view _String) . parseResponse (parseResult @TwoCaptcha @ctx) <$> try (get options url)+ where+ url = "https://2captcha.com/res.php"+ options =+ defaultOptions+ & param "key" .~ [apiKey]+ & param "id" .~ [cs . show $ unCaptchaId captchaId]+ & param "action" .~ ["get"]++ solve ::+ forall ctx.+ ( CaptchaRequest TwoCaptcha ctx r m,+ CaptchaResponse TwoCaptcha ctx,+ HasApiKey ctx Text,+ HasPollingInterval ctx (Maybe (Time Millisecond)),+ HasTimeoutDuration ctx (Maybe (Time Millisecond))+ ) =>+ ctx ->+ m (Either TwoCaptchaError Text)+ solve captcha =+ handleTimeout (captcha ^. timeoutDuration) . runExceptT $+ ExceptT . pollResult =<< ExceptT (createTask @TwoCaptcha @r @m captcha)+ where+ handleTimeout (Just duration) f = fromMaybe (Left TimeoutError) <$> timeout (toNum @Microsecond duration) f+ handleTimeout Nothing f = f+ pollResult captchaId =+ threadDelay (fromMaybe (Time @Millisecond 10_000) (captcha ^. pollingInterval))+ *> getTask @TwoCaptcha @r @m @ctx (captcha ^. apiKey) captchaId+ >>= \case+ Left (TwoCaptchaResponseError CaptchaNotReady) -> pollResult captchaId+ -- 2Captcha sends this error when users fail enough times.+ Left (TwoCaptchaResponseError BadDuplicates) -> solve @TwoCaptcha captcha+ -- 2Captcha sends this error when the captcha isn't solved quick enough.+ Left (TwoCaptchaResponseError CaptchaUnsolvable) -> solve @TwoCaptcha captcha+ x -> pure x++instance CaptchaResponse TwoCaptcha ctx where+ parseResult = preview $ key "request"++-- | Parse the proxy type to its textual representation.+parseProxyType :: HasProxy a (Maybe Proxy) => a -> [Text]+parseProxyType captcha = maybeToList $ toUpper . cs . show <$> captcha ^? proxy . _Just . protocol++-- | Parse the proxy into the format: username:password@address:port+parseProxy :: HasProxy a (Maybe Proxy) => a -> [Text]+parseProxy captcha = maybeToList $ do+ let auth = do+ username <- getProxyUsername captcha+ password <- getProxyPassword captcha+ pure [i|#{username}:#{password}@|]+ address <- getProxyAddress captcha+ port <- getProxyPort captcha+ pure $ fromMaybe mempty auth <> [i|#{address}:#{port}|]++-- | Default option parameters when making 2Captcha requests.+defaultOptions :: Options+defaultOptions =+ defaults+ & param "json" .~ ["1"]+ & param "soft_id" .~ ["3283"]
+ src/Captcha/TwoCaptcha/Internal/Error.hs view
@@ -0,0 +1,158 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE StrictData #-}++-- |+-- Module: Captcha.TwoCaptcha.Internal.Error+-- Copyright: (c) 2022 Edward Yang+-- License: MIT+--+-- This module is for internal-use and does not follow pvp versioning policies.+module Captcha.TwoCaptcha.Internal.Error where++import Control.Exception (Exception)+import Data.Foldable (find)+import Data.Text (Text)+import Network.HTTP.Client (HttpException)++-- | All possible errors when solving a captcha using 2Captcha.+data TwoCaptchaError+ = TwoCaptchaResponseError TwoCaptchaErrorCode+ | UnknownResponseError Text Text+ | UnknownError Text+ | NetworkError HttpException+ | TimeoutError+ deriving (Show, Exception)++-- | An error code returned by the TwoCaptcha API.+data TwoCaptchaErrorCode+ = -- | The api key you provided is invalid. Please ensure it is 32 characters long.+ WrongUserKey+ | -- | The key you've provided does not exist.+ KeyDoesNotExist+ | -- | You don't have funds in your account.+ ZeroBalance+ | -- | The __pageurl__ parameter is missing in your request.+ PageUrlMissing+ | -- |+ -- You can receive this error in two cases:+ --+ -- 1. __If you solve token-based captchas (reCAPTCHA, hCaptcha, ArkoseLabs FunCaptcha, GeeTest, etc):__+ -- the queue of your captchas that are not distributed to workers is too long.+ -- Queue limit changes dynamically and depends on total amount of captchas awaiting solution and usually it’s between 50 and 100 captchas.+ --+ -- 2. __If you solve Normal Captcha:__ your maximum rate for normal captchas is lower than current rate on the server.+ -- You can change your maximum rate in <https://2captcha.com/setting your account's settings.>+ NoSlotAvailable+ | -- | Image size is less than 100 bytes.+ ZeroCaptchaFileSize+ | -- | Image size is more than 100 kB.+ TooBigCaptchaFileSize+ | -- | Image file has unsupported extension. Accepted extensions: jpg, jpeg, gif, png.+ WrongFileExtension+ | -- | Server can't recognize image file type.+ ImageTypeNotSupported+ | -- |+ -- Server can't get file data from your POST-request.+ -- That happens if your POST-request is malformed or base64 data is not a valid base64 image.+ UploadFailure+ | -- | The request is sent from the IP that is not on the list of your allowed IPs.+ IpNotAllowed+ | -- | Your IP address is banned due to many frequent attempts to access the server using wrong authorization keys.+ IpBanned+ | -- |+ -- You can get this error code when sending reCAPTCHA V2. This happens if your request contains invalid pair of googlekey and pageurl.+ -- The common reason for that is that reCAPTCHA is loaded inside an iframe hosted on another domain/subdomain.+ BadTokenOrPageUrl+ | -- | You can get this error code when sending reCAPTCHA V2. That means that sitekey value provided in your request is incorrect: it's blank or malformed.+ GoogleKeyInvalid+ | -- | The __googlekey__ parameter is missing in your request.+ GoogleKeyMissing+ | -- |+ -- You've sent an image that is marked in 2captcha's database as unrecognizable.+ -- Usually that happens if the website where you found the captcha stopped sending you captchas and started to send a "deny access" image.+ CaptchaImageBlocked+ | -- | You are sending too many unrecognizable images.+ TooManyBadImages+ | -- |+ -- You made more than 60 requests to in.php within 3 seconds.+ -- Your account is banned for 10 seconds. Ban will be lifted automatically.+ RateLimited+ | -- |+ -- The error code is returned if some required parameters are missing in your request or the values have incorrect format.+ -- For example if you submit <https://2captcha.com/2captcha-api#grid Grid images> but your request is missing an instruction for workers.+ --+ -- Blocking time: 5 minutes.+ BadParameters+ | -- | You can get this error code when sending a captcha via proxy server which is marked as BAD by the 2captcha API.+ BadProxy+ | -- | Your captcha is not solved yet.+ CaptchaNotReady+ | -- |+ -- 2captcha was unable to solve your captcha - three of their workers were unable solve it or they didn't get an answer within 90 seconds (300 seconds for reCAPTCHA V2).+ --+ -- You will not be charged for that request.+ CaptchaUnsolvable+ | -- | You've provided captcha ID in wrong format. The ID can contain numbers only.+ WrongIdFormat+ | -- | You provided an invalid captcha id.+ WrongCaptchaId+ | -- | Error is returned when 100% accuracy feature is enabled. The error means that max numbers of tries is reached but min number of matches not found.+ BadDuplicates+ | -- |+ -- Error is returned to your <https://2captcha.com/2captcha-api#complain report> request if you already complained lots of correctly solved captchas (more than 40%).+ -- Or if more than 15 minutes passed after you submitted the captcha.+ ReportNotRecorded+ | -- | Error is returned to your <https://2captcha.com/2captcha-api#complain report request> if you are trying to report the same captcha more than once.+ DuplicateReport+ | -- |+ -- You can receive this error code when sending <https://2captcha.com/2captcha-api#solving_geetest GeeTest>.+ -- This error means the __challenge__ value you provided is expired.+ TokenExpired+ | -- | Action parameter is missing or no value is provided for __action__ parameter.+ EmptyAction+ | -- |+ -- You can get this error code if we were unable to load a captcha through your proxy server.+ -- The proxy will be marked as BAD by our API and we will not accept requests with the proxy during 10 minutes.+ -- You will recieve ERROR_BAD_PROXY code from in.php API endpoint in such case.+ ProxyConnectionFailed+ deriving (Show, Eq, Ord, Enum, Bounded)++-- | Textual representation of a 'TwoCaptchaErrorCode'+errorCode :: TwoCaptchaErrorCode -> Text+errorCode = \case+ WrongUserKey -> "ERROR_WRONG_USER_KEY"+ KeyDoesNotExist -> "ERROR_KEY_DOES_NOT_EXIST"+ ZeroBalance -> "ERROR_ZERO_BALANCE"+ PageUrlMissing -> "ERROR_PAGEURL"+ NoSlotAvailable -> "ERROR_NO_SLOT_AVAILABLE"+ ZeroCaptchaFileSize -> "ERROR_ZERO_CAPTCHA_FILESIZE"+ TooBigCaptchaFileSize -> "ERROR_TOO_BIG_CAPTCHA_FILESIZE"+ WrongFileExtension -> "ERROR_WRONG_FILE_EXTENSION"+ ImageTypeNotSupported -> "ERROR_IMAGE_TYPE_NOT_SUPPORTED"+ UploadFailure -> "ERROR_UPLOAD"+ IpNotAllowed -> "ERROR_IP_NOT_ALLOWED"+ IpBanned -> "IP_BANNED"+ BadTokenOrPageUrl -> "ERROR_BAD_TOKEN_OR_PAGEURL"+ GoogleKeyInvalid -> "ERROR_GOOGLEKEY"+ GoogleKeyMissing -> "ERROR_WRONG_GOOGLEKEY"+ CaptchaImageBlocked -> "ERROR_CAPTCHAIMAGE_BLOCKED"+ TooManyBadImages -> "TOO_MANY_BAD_IMAGES"+ RateLimited -> "MAX_USER_TURN"+ BadParameters -> "ERROR_BAD_PARAMETERS"+ BadProxy -> "ERROR_BAD_PROXY"+ CaptchaNotReady -> "CAPCHA_NOT_READY"+ CaptchaUnsolvable -> "ERROR_CAPTCHA_UNSOLVABLE"+ WrongIdFormat -> "ERROR_WRONG_ID_FORMAT"+ WrongCaptchaId -> "ERROR_WRONG_CAPTCHA_ID"+ BadDuplicates -> "ERROR_BAD_DUPLICATES"+ ReportNotRecorded -> "ERROR_REPORT_NOT_RECORDED"+ DuplicateReport -> "ERROR_DUPLICATE_REPORT"+ TokenExpired -> "ERROR_TOKEN_EXPIRED"+ EmptyAction -> "ERROR_EMPTY_ACTION"+ ProxyConnectionFailed -> "ERROR_PROXY_CONNECTION_FAILED"++-- | Parse an error code into its equivalent 'TwoCaptchaErrorCode'.+parseError :: Text -> Maybe TwoCaptchaErrorCode+parseError code = find ((== code) . errorCode) [minBound .. maxBound]
+ src/Captcha/TwoCaptcha/Internal/Types/FunCaptcha.hs view
@@ -0,0 +1,38 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wno-orphans #-}++-- |+-- Module: Captcha.TwoCaptcha.Internal.Types.FunCaptcha+-- Copyright: (c) 2022 Edward Yang+-- License: MIT+--+-- This module is for internal-use and does not follow pvp versioning policies.+module Captcha.TwoCaptcha.Internal.Types.FunCaptcha where++import Captcha.Internal.Monad (HasCaptchaEnv)+import Captcha.Internal.Monad.Class (CaptchaRequest (request))+import Captcha.Internal.Request (post)+import Captcha.Internal.Types (FunCaptcha, HasApiKey (apiKey), HasCaptchaKey (captchaKey), HasCaptchaUrl (captchaUrl), HasServiceUrl (serviceUrl), HasUserAgent (userAgent))+import Captcha.TwoCaptcha.Internal (TwoCaptcha, defaultOptions, parseProxy, parseProxyType)+import Control.Lens ((&), (.~), (^.))+import Control.Monad.Cont (MonadIO)+import Control.Monad.Reader (MonadReader)+import Data.Aeson (object)+import Data.Maybe (maybeToList)+import Network.Wreq (param)++instance (HasCaptchaEnv r, MonadReader r m, MonadIO m) => CaptchaRequest TwoCaptcha FunCaptcha r m where+ request captcha = flip (post options) (object [])+ where+ options =+ defaultOptions+ & param "key" .~ [captcha ^. apiKey]+ & param "method" .~ ["funcaptcha"]+ & param "publickey" .~ [captcha ^. captchaKey]+ & param "pageurl" .~ [captcha ^. captchaUrl]+ & param "surl" .~ maybeToList (captcha ^. serviceUrl)+ & param "userAgent" .~ maybeToList (captcha ^. userAgent)+ & param "proxy" .~ parseProxy captcha+ & param "proxytype" .~ parseProxyType captcha
+ src/Captcha/TwoCaptcha/Internal/Types/HCaptcha.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wno-orphans #-}++-- |+-- Module: Captcha.TwoCaptcha.Internal.Types.HCaptcha+-- Copyright: (c) 2022 Edward Yang+-- License: MIT+--+-- This module is for internal-use and does not follow pvp versioning policies.+module Captcha.TwoCaptcha.Internal.Types.HCaptcha where++import Captcha.Internal.Monad (HasCaptchaEnv)+import Captcha.Internal.Monad.Class (CaptchaRequest (request))+import Captcha.Internal.Request (post)+import Captcha.Internal.Types (HCaptcha, HasApiKey (apiKey), HasCaptchaKey (captchaKey), HasCaptchaUrl (captchaUrl), HasInvisible (invisible), HasRqData (rqData), HasUserAgent (userAgent))+import Captcha.TwoCaptcha.Internal (TwoCaptcha, defaultOptions, parseProxy, parseProxyType)+import Control.Lens ((&), (.~), (^.))+import Control.Monad.Cont (MonadIO)+import Control.Monad.Reader (MonadReader)+import Data.Aeson (object)+import Data.Maybe (maybeToList)+import Data.String.Conversions (cs)+import Network.Wreq (param)++instance (HasCaptchaEnv r, MonadReader r m, MonadIO m) => CaptchaRequest TwoCaptcha HCaptcha r m where+ request captcha = flip (post options) (object [])+ where+ options =+ defaultOptions+ & param "key" .~ [captcha ^. apiKey]+ & param "method" .~ ["hcaptcha"]+ & param "sitekey" .~ [captcha ^. captchaKey]+ & param "pageurl" .~ [captcha ^. captchaUrl]+ & param "invisible" .~ [cs . show $ captcha ^. invisible]+ & param "data" .~ maybeToList (captcha ^. rqData)+ & param "userAgent" .~ maybeToList (captcha ^. userAgent)+ & param "proxy" .~ parseProxy captcha+ & param "proxytype" .~ parseProxyType captcha
+ src/Captcha/TwoCaptcha/Internal/Types/Image.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wno-orphans #-}++-- |+-- Module: Captcha.TwoCaptcha.Internal.Types.Image+-- Copyright: (c) 2022 Edward Yang+-- License: MIT+--+-- This module is for internal-use and does not follow pvp versioning policies.+module Captcha.TwoCaptcha.Internal.Types.Image where++import Captcha.Internal.Monad (HasCaptchaEnv)+import Captcha.Internal.Monad.Class (CaptchaRequest (request))+import Captcha.Internal.Request (post)+import Captcha.Internal.Types (HasApiKey (apiKey), HasBody (body), ImageCaptcha)+import Captcha.TwoCaptcha.Internal (TwoCaptcha, defaultOptions)+import Control.Lens ((^.))+import Control.Monad.Cont (MonadIO)+import Control.Monad.Reader (MonadReader)+import Data.Text (Text)+import Network.Wreq (FormParam ((:=)))++instance (HasCaptchaEnv r, MonadReader r m, MonadIO m) => CaptchaRequest TwoCaptcha ImageCaptcha r m where+ request captcha = flip (post defaultOptions) payload+ where+ payload =+ [ "key" := (captcha ^. apiKey),+ "method" := ("base64" :: Text),+ "body" := (captcha ^. body)+ ]
+ src/Captcha/TwoCaptcha/Internal/Types/ReCaptchaV2.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wno-orphans #-}++-- |+-- Module: Captcha.TwoCaptcha.Internal.Types.ReCaptchaV2+-- Copyright: (c) 2022 Edward Yang+-- License: MIT+--+-- This module is for internal-use and does not follow pvp versioning policies.+module Captcha.TwoCaptcha.Internal.Types.ReCaptchaV2 where++import Captcha.Internal (renderCookies)+import Captcha.Internal.Monad (HasCaptchaEnv)+import Captcha.Internal.Monad.Class (CaptchaRequest (request))+import Captcha.Internal.Request (post)+import Captcha.Internal.Types (HasApiKey (apiKey), HasCaptchaKey (captchaKey), HasCaptchaUrl (captchaUrl), HasDataS (dataS), HasInvisible (invisible), HasUserAgent (userAgent), ReCaptchaV2)+import Captcha.TwoCaptcha.Internal (TwoCaptcha, defaultOptions, parseProxy, parseProxyType)+import Control.Lens ((&), (.~), (^.))+import Control.Monad.Cont (MonadIO)+import Control.Monad.Reader (MonadReader)+import Data.Aeson (object)+import Data.Maybe (maybeToList)+import Data.String.Conversions (cs)+import Network.Wreq (param)++instance (HasCaptchaEnv r, MonadReader r m, MonadIO m) => CaptchaRequest TwoCaptcha ReCaptchaV2 r m where+ request captcha = flip (post options) (object [])+ where+ options =+ defaultOptions+ & param "key" .~ [captcha ^. apiKey]+ & param "method" .~ ["userrecaptcha"]+ & param "googlekey" .~ [captcha ^. captchaKey]+ & param "pageurl" .~ [captcha ^. captchaUrl]+ & param "data-s" .~ maybeToList (captcha ^. dataS)+ & param "invisible" .~ [cs . show $ captcha ^. invisible]+ & param "userAgent" .~ maybeToList (captcha ^. userAgent)+ & param "cookies" .~ [renderCookies captcha]+ & param "proxy" .~ parseProxy captcha+ & param "proxytype" .~ parseProxyType captcha
+ src/Captcha/TwoCaptcha/Internal/Types/ReCaptchaV3.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wno-orphans #-}++-- |+-- Module: Captcha.TwoCaptcha.Internal.Types.ReCaptchaV3+-- Copyright: (c) 2022 Edward Yang+-- License: MIT+--+-- This module is for internal-use and does not follow pvp versioning policies.+module Captcha.TwoCaptcha.Internal.Types.ReCaptchaV3 where++import Captcha.Internal.Monad (HasCaptchaEnv)+import Captcha.Internal.Monad.Class (CaptchaRequest (request))+import Captcha.Internal.Request (post)+import Captcha.Internal.Types (HasApiKey (apiKey), HasCaptchaKey (captchaKey), HasCaptchaUrl (captchaUrl), HasMinScore (minScore), ReCaptchaV3)+import Captcha.TwoCaptcha.Internal (TwoCaptcha, defaultOptions)+import Control.Lens ((&), (.~), (^.))+import Control.Monad.Cont (MonadIO)+import Control.Monad.Reader (MonadReader)+import Data.Aeson (object)+import Data.String.Conversions (cs)+import Network.Wreq (param)++instance (HasCaptchaEnv r, MonadReader r m, MonadIO m) => CaptchaRequest TwoCaptcha ReCaptchaV3 r m where+ request captcha = flip (post options) (object [])+ where+ options =+ defaultOptions+ & param "key" .~ [captcha ^. apiKey]+ & param "method" .~ ["userrecaptcha"]+ & param "version" .~ ["v3"]+ & param "googlekey" .~ [captcha ^. captchaKey]+ & param "pageurl" .~ [captcha ^. captchaUrl]+ & param "min_score" .~ [cs . show $ captcha ^. minScore]
+ src/Captcha/TwoCaptcha/Internal/Types/Text.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wno-orphans #-}++-- |+-- Module: Captcha.TwoCaptcha.Internal.Types.Text+-- Copyright: (c) 2022 Edward Yang+-- License: MIT+--+-- This module is for internal-use and does not follow pvp versioning policies.+module Captcha.TwoCaptcha.Internal.Types.Text where++import Captcha.Internal.Monad (HasCaptchaEnv)+import Captcha.Internal.Monad.Class (CaptchaRequest (request))+import Captcha.Internal.Request (post)+import Captcha.Internal.Types (HasApiKey (apiKey), HasBody (body), TextCaptcha)+import Captcha.TwoCaptcha.Internal (TwoCaptcha, defaultOptions)+import Control.Lens ((&), (.~), (^.))+import Control.Monad.Cont (MonadIO)+import Control.Monad.Reader (MonadReader)+import Data.Aeson (object)+import Network.Wreq (param)++instance (HasCaptchaEnv r, MonadReader r m, MonadIO m) => CaptchaRequest TwoCaptcha TextCaptcha r m where+ request captcha = flip (post options) (object [])+ where+ options =+ defaultOptions+ & param "key" .~ [captcha ^. apiKey]+ & param "textcaptcha" .~ [captcha ^. body]
+ test/Captcha/TwoCaptcha.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MonoLocalBinds #-}+{-# LANGUAGE TypeApplications #-}++module Captcha.TwoCaptcha where++import Captcha.Internal.Monad (Captcha (runCaptcha), CaptchaEnv, mkCaptchaEnv)+import Captcha.Internal.Monad.Class (CaptchaRequest, CaptchaResponse, MonadCaptcha (solve))+import Captcha.Internal.Types+import Captcha.TwoCaptcha.Internal (TwoCaptcha)+import Captcha.TwoCaptcha.Internal.Error (TwoCaptchaError (TimeoutError, TwoCaptchaResponseError), TwoCaptchaErrorCode (NoSlotAvailable))+import Captcha.TwoCaptcha.Internal.Types.Image ()+import Control.Lens ((&), (?~))+import Data.Default (Default (def))+import Data.Text (Text)+import Test.Tasty (TestTree)+import Test.Tasty.HUnit (assertFailure, testCase)+import Time (Millisecond, Time (Time))++type ApiKey = Text++assertCaptcha ::+ ( CaptchaRequest TwoCaptcha ctx CaptchaEnv Captcha,+ CaptchaResponse TwoCaptcha ctx,+ HasApiKey ctx Text,+ HasPollingInterval ctx (Maybe (Time Millisecond)),+ HasTimeoutDuration ctx (Maybe (Time Millisecond))+ ) =>+ ctx ->+ IO ()+assertCaptcha captcha =+ mkCaptchaEnv >>= runCaptcha (solve @TwoCaptcha captcha) >>= \case+ Left (TwoCaptchaResponseError NoSlotAvailable) -> pure ()+ Right _ -> pure ()+ Left exception -> assertFailure $ show exception++testTimeout :: TestTree+testTimeout =+ testCase "Captcha Timeout" $ do+ let captcha = def @ImageCaptcha & timeoutDuration ?~ Time @Millisecond 0+ mkCaptchaEnv >>= runCaptcha (solve @TwoCaptcha captcha) >>= \case+ Left TimeoutError -> pure ()+ Left exception -> assertFailure $ show exception+ Right _ -> assertFailure "Captcha did not timeout."
+ test/Captcha/TwoCaptcha/Test/FunCaptcha.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}++module Captcha.TwoCaptcha.Test.FunCaptcha where++import Captcha.Internal.Types (FunCaptcha, HasApiKey (apiKey), HasCaptchaKey (captchaKey), HasCaptchaUrl (captchaUrl))+import Captcha.TwoCaptcha (ApiKey, assertCaptcha)+import Captcha.TwoCaptcha.Internal.Types.FunCaptcha ()+import Control.Lens ((&), (.~))+import Data.Default (Default (def))+import Test.Tasty (TestTree)+import Test.Tasty.HUnit (testCase)++test :: ApiKey -> TestTree+test key = do+ testCase "FunCaptcha" $+ let captcha =+ def @FunCaptcha+ & apiKey .~ key+ & captchaUrl .~ "https://api.funcaptcha.com/fc/api/nojs"+ & captchaKey .~ "69A21A01-CC7B-B9C6-0F9A-E7FA06677FFC"+ in assertCaptcha captcha
+ test/Captcha/TwoCaptcha/Test/HCaptcha.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}++module Captcha.TwoCaptcha.Test.HCaptcha where++import Captcha.Internal.Types (HCaptcha, HasApiKey (apiKey), HasCaptchaKey (captchaKey), HasCaptchaUrl (captchaUrl))+import Captcha.TwoCaptcha (ApiKey, assertCaptcha)+import Captcha.TwoCaptcha.Internal.Types.HCaptcha ()+import Control.Lens ((&), (.~))+import Data.Default (Default (def))+import Test.Tasty (TestTree)+import Test.Tasty.HUnit (testCase)++test :: ApiKey -> TestTree+test key =+ testCase "HCaptcha" $+ let captcha =+ def @HCaptcha+ & apiKey .~ key+ & captchaUrl .~ "https://2captcha.com/demo/hcaptcha"+ & captchaKey .~ "3ceb8624-1970-4e6b-91d5-70317b70b651"+ in assertCaptcha captcha
+ test/Captcha/TwoCaptcha/Test/Image.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE TypeApplications #-}++module Captcha.TwoCaptcha.Test.Image where++import Captcha.Internal.Types (HasApiKey (apiKey), HasBody (body), ImageCaptcha)+import Captcha.TwoCaptcha (ApiKey, assertCaptcha)+import Control.Lens ((&), (.~))+import Data.Default (Default (def))+import Data.String.Conversions (cs)+import Test.Tasty (TestTree)+import Test.Tasty.HUnit (testCase)++test :: ApiKey -> TestTree+test key =+ testCase "Image Captcha" $ do+ -- Captcha image: Td4eva+ captchaBody <- cs <$> readFile "../assets/image-captcha.txt"+ let captcha =+ def @ImageCaptcha+ & apiKey .~ key+ & body .~ captchaBody+ assertCaptcha captcha
+ test/Captcha/TwoCaptcha/Test/ReCaptchaV2.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}++module Captcha.TwoCaptcha.Test.ReCaptchaV2 where++import Captcha.Internal.Types (HasApiKey (apiKey), HasCaptchaKey (captchaKey), HasCaptchaUrl (captchaUrl), ReCaptchaV2)+import Captcha.TwoCaptcha (ApiKey, assertCaptcha)+import Captcha.TwoCaptcha.Internal.Types.ReCaptchaV2 ()+import Control.Lens ((&), (.~))+import Data.Default (Default (def))+import Test.Tasty (TestTree)+import Test.Tasty.HUnit (testCase)++test :: ApiKey -> TestTree+test key =+ testCase "ReCaptchaV2" $+ let captcha =+ def @ReCaptchaV2+ & apiKey .~ key+ & captchaUrl .~ "https://2captcha.com/demo/recaptcha-v2"+ & captchaKey .~ "6LfD3PIbAAAAAJs_eEHvoOl75_83eXSqpPSRFJ_u"+ in assertCaptcha captcha
+ test/Captcha/TwoCaptcha/Test/ReCaptchaV3.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}++module Captcha.TwoCaptcha.Test.ReCaptchaV3 where++import Captcha.Internal.Types (HasApiKey (apiKey), HasCaptchaKey (captchaKey), HasCaptchaUrl (captchaUrl), ReCaptchaV3)+import Captcha.TwoCaptcha (ApiKey, assertCaptcha)+import Captcha.TwoCaptcha.Internal.Types.ReCaptchaV3 ()+import Control.Lens ((&), (.~))+import Data.Default (Default (def))+import Test.Tasty (TestTree)+import Test.Tasty.HUnit (testCase)++test :: ApiKey -> TestTree+test key =+ testCase "ReCaptchaV3" $+ let captcha =+ def @ReCaptchaV3+ & apiKey .~ key+ & captchaUrl .~ "https://2captcha.com/demo/recaptcha-v3"+ & captchaKey .~ "6LfB5_IbAAAAAMCtsjEHEHKqcB9iQocwwxTiihJu"+ in assertCaptcha captcha
+ test/Captcha/TwoCaptcha/Test/Text.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}++module Captcha.TwoCaptcha.Test.Text where++import Captcha.Internal.Types (HasApiKey (apiKey), HasBody (body), TextCaptcha)+import Captcha.TwoCaptcha (ApiKey, assertCaptcha)+import Captcha.TwoCaptcha.Internal.Types.Text ()+import Control.Lens ((&), (.~))+import Data.Default (Default (def))+import Test.Tasty (TestTree)+import Test.Tasty.HUnit (testCase)++test :: ApiKey -> TestTree+test key =+ testCase "Text Captcha" $+ let captcha =+ def @TextCaptcha+ & apiKey .~ key+ & body .~ "Hello world!"+ in assertCaptcha captcha
+ test/Main.hs view
@@ -0,0 +1,25 @@+import qualified Captcha.TwoCaptcha as TwoCaptcha+import qualified Captcha.TwoCaptcha.Test.FunCaptcha as FunCaptcha+import qualified Captcha.TwoCaptcha.Test.HCaptcha as HCaptcha+import qualified Captcha.TwoCaptcha.Test.Image as ImageCaptcha+import qualified Captcha.TwoCaptcha.Test.ReCaptchaV2 as ReCaptchaV2+import qualified Captcha.TwoCaptcha.Test.ReCaptchaV3 as ReCaptchaV3+import qualified Captcha.TwoCaptcha.Test.Text as TextCaptcha+import Data.String.Conversions (cs)+import System.Environment (getEnv)+import Test.Tasty (defaultMain, testGroup)++main :: IO ()+main = defaultMain . tests . cs =<< getEnv "TWOCAPTCHA_API_KEY"+ where+ tests apiKey =+ testGroup+ "2Captcha"+ [ TwoCaptcha.testTimeout,+ TextCaptcha.test apiKey,+ ImageCaptcha.test apiKey,+ ReCaptchaV2.test apiKey,+ ReCaptchaV3.test apiKey,+ HCaptcha.test apiKey,+ FunCaptcha.test apiKey+ ]