diff --git a/README.md b/README.md index 978d20e..7fdbcad 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ captcha_answer = solver.get_token() | solving_site| true| String (name of site) or int (site ID) | "capmonster"| The captcha solving site that will be used. Refer to [the site IDs](https://github.com/Matthew17-21/Captcha-Tools/tree/main/captchatools-go#site-specific-support)| | sitekey| true | String | - | Sitekey from the site where captcha is loaded| | captcha_url | true| String | - | URL where the captcha is located| -| captcha_type| false| String | "v2" | Type of captcha you are solving. Either captcha `image`, `v2`, `v3` or `hcaptcha` (`hcap` works aswell)| +| captcha_type| false| String | "v2" | Type of captcha you are solving. Either captcha `image`, `v2`, `v3`,`hcaptcha` (`hcap` works aswell) or `hcaptchaturbo`| | invisible_captcha| false | bool | false | If the captcha is invisible or not.
__This param is only required when solving invisible captchas__| | min_score | false | double |0.7 | Minimum score for v3 captchas.
__This param is only required when solving V3 and it needs a higher / lower score__| | action | false | String | "verify" | Action that is associated with the V3 captcha.
__This param is only required when solving V3 captchas__| @@ -44,6 +44,7 @@ captcha_answer = solver.get_token() | proxy| false | string | Proxy to be used to solve captchas.
This will make the captcha be solved from the proxy ip

Format: `ip:port:user:pass` | | proxy_type | false | string | Type of the proxy being used. Options are:
`HTTP`, `HTTPS`, `SOCKS4`, `SOCKS5`| | user_agent | false | string | UserAgent that will be passed to the service and used to solve the captcha | +| rq_data | false | string | Custom data that is used in some implementations of hCaptcha. Most of the times, you want to set the `invisible_captcha` param to `true`.| ### Examples ##### Example - V2 Captcha / Basic usage ```python @@ -122,7 +123,7 @@ def main(): | Capmonster | captchatools.CapmonsterSite| Image captchas,
Recaptcha V2,
Recaptcha V3,
HCaptcha | ImageToTextTask,
NoCaptchaTask,
NoCaptchaTaskProxyless,
RecaptchaV3TaskProxyless,
HCaptchaTaskProxyless | | Anticaptcha | captchatools.AnticaptchaSite| Image captchas,
Recaptcha V2,
Recaptcha V3,
HCaptcha | ImageToTextTask,
RecaptchaV2Task
RecaptchaV2TaskProxyless,
RecaptchaV3TaskProxyless,
HCaptchaTaskProxyless | | 2Captcha | captchatools.TwoCaptchaSite| Image captchas,
Recaptcha V2,
Recaptcha V3,
HCaptcha | - | -| Capsolver | captchatools.CapsolverSite| Image captchas,
Recaptcha V2,
Recaptcha V3,
HCaptcha | - | +| Capsolver | captchatools.CapsolverSite| Image captchas,
Recaptcha V2,
Recaptcha V3,
HCaptcha
HcaptchaTurbo | - | | CaptchaAI | captchatools.CaptchaAISite| Image captchas,
Recaptcha V2,
Recaptcha V3,
HCaptcha | - | diff --git a/captchatools-go/README.md b/captchatools-go/README.md index 24d30ef..c7b6c86 100644 --- a/captchatools-go/README.md +++ b/captchatools-go/README.md @@ -95,6 +95,7 @@ func main() { | Proxy| false | *Proxy | Proxy to be used to solve captchas.
This will make the captcha be solved from the proxy ip| | ProxyType | false | string | Type of the proxy being used. Options are:
`HTTP`, `HTTPS`, `SOCKS4`, `SOCKS5`| | UserAgent | false | string | UserAgent that will be passed to the service and used to solve the captcha | +| RQData | false | string | Custom data that is used in some implementations of hCaptcha. Most of the times, you want to set the `IsInvisibleCaptcha` param to `true`.| ### Examples ##### Example - V2 Captcha / Basic usage ```go @@ -269,6 +270,7 @@ func addtional_data() { | Recaptcha V2 | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Recaptcha V3 | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Hcaptcha | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| HcaptchaTurbo |:x: | :x: | :x: | :white_check_mark: | :x: | | Image Captcha | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Cloudflare Turnstile | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: | | Funcaptcha |:x: | :x: | :x: | :x: | :x: | diff --git a/captchatools-go/anticaptcha.go b/captchatools-go/anticaptcha.go index 92d292d..eca3139 100644 --- a/captchatools-go/anticaptcha.go +++ b/captchatools-go/anticaptcha.go @@ -179,27 +179,43 @@ Possible errors that can be returned: 1) ErrIncorrectCapType */ func (a Anticaptcha) createPayload(data *AdditionalData) (string, error) { + type EnterprisePayload struct { + Rqdata string `json:"rqdata,omitempty"` + Sentry bool `json:"sentry,omitempty"` + ApiEndpoint string `json:"apiEndpoint,omitempty"` + Endpoint string `json:"endpoint,omitempty"` + ReportAPI string `json:"reportapi,omitempty"` + AssetHost string `json:"assethost,omitempty"` + ImgHost string `json:"imghost,omitempty"` + } + type Task struct { + Type captchaType `json:"type"` + WebsiteURL string `json:"websiteURL"` + WebsiteKey string `json:"websiteKey"` + IsInvisible bool `json:"isInvisible,omitempty"` + MinScore float32 `json:"minScore,omitempty"` + PageAction string `json:"pageAction,omitempty"` + Body string `json:"body,omitempty"` + ProxyType string `json:"proxyType,omitempty"` + ProxyAddress string `json:"proxyAddress,omitempty"` + ProxyPort int `json:"proxyPort,omitempty"` + ProxyLogin string `json:"proxyLogin,omitempty"` + ProxyPassword string `json:"proxyPassword,omitempty"` + UserAgent string `json:"userAgent,omitempty"` + HcapEnterpriseData *EnterprisePayload `json:"enterprisePayload,omitempty"` + } + type Payload struct { + ClientKey string `json:"clientKey"` + Task Task `json:"task"` + SoftID int `json:"softId,omitempty"` + } // Define the payload we are going to send to the API - payload := capmonsterIDPayload{ + payload := Payload{ ClientKey: a.config.Api_key, - Task: struct { - WebsiteURL string "json:\"websiteURL\"" - WebsiteKey string "json:\"websiteKey\"" - Type captchaType "json:\"type\"" - IsInvisible bool "json:\"isInvisible,omitempty\"" - MinScore float32 "json:\"minScore,omitempty\"" - PageAction string "json:\"pageAction,omitempty\"" - Body string "json:\"body,omitempty\"" - ProxyType string "json:\"proxyType,omitempty\"" - ProxyAddress string "json:\"proxyAddress,omitempty\"" - ProxyPort int "json:\"proxyPort,omitempty\"" - ProxyLogin string "json:\"proxyLogin,omitempty\"" - ProxyPassword string "json:\"proxyPassword,omitempty\"" - UserAgent string "json:\"userAgent,omitempty\"" - }{ - WebsiteURL: a.config.CaptchaURL, - WebsiteKey: a.config.Sitekey, + Task: Task{ Type: a.config.CaptchaType, + WebsiteKey: a.config.Sitekey, + WebsiteURL: a.config.CaptchaURL, }, } @@ -255,6 +271,26 @@ func (a Anticaptcha) createPayload(data *AdditionalData) (string, error) { default: return "", ErrIncorrectCapType } + + // Check for any additional data about the task + if data != nil && a.config.CaptchaType != ImageCaptcha { + if data.UserAgent != "" { + payload.Task.UserAgent = data.UserAgent + } + if data.Proxy != nil { + if port, err := strconv.Atoi(data.Proxy.Port); err == nil { + payload.Task.ProxyAddress = data.Proxy.Ip + payload.Task.ProxyPort = port + payload.Task.ProxyLogin = data.Proxy.User + payload.Task.ProxyPassword = data.Proxy.Password + } + } + if data.RQData != "" { + payload.Task.HcapEnterpriseData = &EnterprisePayload{ + Rqdata: data.RQData, + } + } + } encoded, _ := json.Marshal(payload) return string(encoded), nil } diff --git a/captchatools-go/capmonster.go b/captchatools-go/capmonster.go index 519561f..c8f91d2 100644 --- a/captchatools-go/capmonster.go +++ b/captchatools-go/capmonster.go @@ -38,6 +38,7 @@ type capmonsterIDPayload struct { ProxyLogin string `json:"proxyLogin,omitempty"` ProxyPassword string `json:"proxyPassword,omitempty"` UserAgent string `json:"userAgent,omitempty"` + RqData string `json:"data,omitempty"` // Custom data that is used in some implementations of hCaptcha, mostly with isInvisible=true. In most cases you see it as rqdata inside network requests. } `json:"task"` } type capmonsterCapAnswerPayload struct { @@ -228,6 +229,7 @@ func (c Capmonster) createPayload(data *AdditionalData) (string, error) { ProxyLogin string "json:\"proxyLogin,omitempty\"" ProxyPassword string "json:\"proxyPassword,omitempty\"" UserAgent string "json:\"userAgent,omitempty\"" + RqData string "json:\"data,omitempty\"" }{ WebsiteURL: c.config.CaptchaURL, WebsiteKey: c.config.Sitekey, @@ -281,8 +283,21 @@ func (c Capmonster) createPayload(data *AdditionalData) (string, error) { } // Check for addtional data - if data != nil && data.UserAgent != "" { - payload.Task.UserAgent = data.UserAgent + if data != nil && c.config.CaptchaType != ImageCaptcha { + if data.UserAgent != "" { + payload.Task.UserAgent = data.UserAgent + } + if data.Proxy != nil { + if port, err := strconv.Atoi(data.Proxy.Port); err == nil { + payload.Task.ProxyAddress = data.Proxy.Ip + payload.Task.ProxyPort = port + payload.Task.ProxyLogin = data.Proxy.User + payload.Task.ProxyPassword = data.Proxy.Password + } + } + if data.RQData != "" { + payload.Task.RqData = data.RQData + } } encoded, _ := json.Marshal(payload) diff --git a/captchatools-go/capsolver.go b/captchatools-go/capsolver.go index c712683..24ec129 100644 --- a/captchatools-go/capsolver.go +++ b/captchatools-go/capsolver.go @@ -159,7 +159,7 @@ func (c Capsolver) getCaptchaAnswer(ctx context.Context, additional ...*Addition solution, c.Api_key, c.CaptchaType, - AnticaptchaSite, // TODO change this + CapsolverSite, ua, ), nil } @@ -167,6 +167,9 @@ func (c Capsolver) getCaptchaAnswer(ctx context.Context, additional ...*Addition } func (c Capsolver) createPayload(data *AdditionalData) (string, error) { + type EnterprisePayload struct { + Rqdata string `json:"rqdata"` + } type Task struct { Type captchaType `json:"type"` WebsiteURL string `json:"websiteURL"` @@ -183,6 +186,9 @@ func (c Capsolver) createPayload(data *AdditionalData) (string, error) { // Image Captcha data B64Image string `json:"body,omitempty"` + + // Custom data that is used in some implementations of hCaptcha Enterprise. + HcapEnterpriseData *EnterprisePayload `json:"enterprisePayload,omitempty"` } type Payload struct { ClientKey string `json:"clientKey"` @@ -222,7 +228,8 @@ func (c Capsolver) createPayload(data *AdditionalData) (string, error) { if data != nil && data.Proxy != nil { p.Task.Type = "HCaptchaTurboTask" } - + case HcaptchaTurbo: + p.Task.Type = "HCaptchaTurboTask" } // Check for any additional data about the task @@ -233,6 +240,11 @@ func (c Capsolver) createPayload(data *AdditionalData) (string, error) { if data.Proxy != nil { p.Task.Proxy = data.Proxy.StringFormatted() } + if data.RQData != "" { + p.Task.HcapEnterpriseData = &EnterprisePayload{ + Rqdata: data.RQData, + } + } } // TODO add softkey id diff --git a/captchatools-go/captchaai.go b/captchatools-go/captchaai.go index 6d04f7e..70acec8 100644 --- a/captchatools-go/captchaai.go +++ b/captchatools-go/captchaai.go @@ -213,6 +213,9 @@ func (t CaptchaAi) createUrl(data *AdditionalData) (string, error) { if data.ProxyType != "" { query.Add("proxytype", data.ProxyType) } + if data.RQData != "" { + query.Add("data", data.RQData) + } } u.RawQuery = query.Encode() diff --git a/captchatools-go/common.go b/captchatools-go/common.go index 0230e0e..32ce53b 100644 --- a/captchatools-go/common.go +++ b/captchatools-go/common.go @@ -10,11 +10,12 @@ const ( ) const ( - V2Captcha captchaType = "v2" - V3Captcha captchaType = "v3" - HCaptcha captchaType = "hcaptcha" - ImageCaptcha captchaType = "image" - CFTurnstile captchaType = "cfturnstile" + V2Captcha captchaType = "v2" + V3Captcha captchaType = "v3" + HCaptcha captchaType = "hcaptcha" + HcaptchaTurbo captchaType = "hcaptchaturbo" + ImageCaptcha captchaType = "image" + CFTurnstile captchaType = "cfturnstile" ) type ( diff --git a/captchatools-go/harvester.go b/captchatools-go/harvester.go index 76a96e7..64287a4 100644 --- a/captchatools-go/harvester.go +++ b/captchatools-go/harvester.go @@ -41,6 +41,7 @@ type AdditionalData struct { Proxy *Proxy // A proxy in correct formatting - such as user:pass@ip:port ProxyType string // Type of your proxy: HTTP, HTTPS, SOCKS4, SOCKS5 UserAgent string // UserAgent that will be passed to the service and used to solve the captcha + RQData string // Custom rqdata for hcaptcha } // Configurations for the captchas you are solving. diff --git a/captchatools-go/twocaptcha.go b/captchatools-go/twocaptcha.go index 36b0fa9..a8a9aef 100644 --- a/captchatools-go/twocaptcha.go +++ b/captchatools-go/twocaptcha.go @@ -35,6 +35,7 @@ type twoCapIDPayload struct { UserAgent string `json:"userAgent,omitempty"` // userAgent that will be used to solve the captcha Proxy string `json:"proxy,omitempty"` // Proxy to use to solve captchas from ProxyType string `json:"proxytype,omitempty"` // Type of the proxy + RQData string `json:"data,omitempty"` // Custom rqdata. mostly with invisible=1 } // Type that will be used when getting a response from 2captcha @@ -232,6 +233,9 @@ func (t Twocaptcha) createPayload(data *AdditionalData) (string, error) { if data.ProxyType != "" { payload.ProxyType = data.ProxyType } + if data.RQData != "" { + payload.RQData = data.RQData + } } encoded, _ := json.Marshal(payload) diff --git a/captchatools/__init__.py b/captchatools/__init__.py index 913b6de..d78d50d 100644 --- a/captchatools/__init__.py +++ b/captchatools/__init__.py @@ -46,7 +46,7 @@ def __init__(self, **kwargs) -> None: raise captchaExceptions.WrongAPIKeyException() if self.solving_site is None: raise captchaExceptions.NoHarvesterException("No solving site selected") - if self.captcha_type not in ["v2", "v3", "hcaptcha", "hcap", "image", "normal"]: + if self.captcha_type not in ["v2", "v3", "hcaptcha", "hcap", "image", "normal", "hcaptchaturbo"]: raise captchaExceptions.NoCaptchaType("Invalid captcha type") if self.soft_id is None: if self.solving_site == 3 or self.solving_site == "2captcha": @@ -63,7 +63,8 @@ def get_token( self, b64_img: Optional[str]=None, user_agent: Optional[str]=None, proxy: Optional[str]=None, - proxy_type: Optional[str]=None + proxy_type: Optional[str]=None, + rq_data: Optional[str] = None ): ''' Returns a captcha token diff --git a/captchatools/__init__.pyi b/captchatools/__init__.pyi index 413fe1f..06b5cc8 100644 --- a/captchatools/__init__.pyi +++ b/captchatools/__init__.pyi @@ -25,7 +25,8 @@ class Harvester(ABC): self, b64_img: Optional[str]=None, user_agent: Optional[str]=None, proxy: Optional[str]=None, - proxy_type: Optional[str]=None + proxy_type: Optional[str]=None, + rq_data: Optional[str]=None ): ... def new_harvester( diff --git a/captchatools/anticaptcha.py b/captchatools/anticaptcha.py index cc1a473..9c76ff3 100644 --- a/captchatools/anticaptcha.py +++ b/captchatools/anticaptcha.py @@ -18,13 +18,21 @@ def get_balance(self) -> float: except requests.RequestException: pass - def get_token(self, b64_img: Optional[str] = None, user_agent: Optional[str] = None, proxy: Optional[str] = None, proxy_type: Optional[str] = "HTTP"): + def get_token( + self, + b64_img: Optional[str] = None, + user_agent: Optional[str] = None, + proxy: Optional[str] = None, + proxy_type: Optional[str] = "HTTP", + rq_data: Optional[str] = None + ): # Get ID task_id = self.__get_id( b64_img=b64_img, user_agent=user_agent, proxy=proxy, - proxy_type=proxy_type + proxy_type=proxy_type, + rq_data=rq_data ) # Get Answer @@ -75,6 +83,8 @@ def __create_payload(self, **kwargs): payload["task"]["proxyPassword"] = splitted[3] if kwargs.get("user_agent", None) is not None: payload["task"]["userAgent"] = kwargs.get("user_agent") + if kwargs.get("rq_data", None) is not None: + payload["task"]["enterprisePayload"] ={"rqdata" :kwargs.get("rq_data")} return payload def __get_id(self,**kwargs): diff --git a/captchatools/capmonster.py b/captchatools/capmonster.py index bedafd5..1d14cc1 100644 --- a/captchatools/capmonster.py +++ b/captchatools/capmonster.py @@ -23,13 +23,21 @@ def get_balance(self) -> float: except requests.RequestException: pass - def get_token(self, b64_img: Optional[str] = None, user_agent: Optional[str] = None, proxy: Optional[str] = None, proxy_type: Optional[str] = "HTTP"): + def get_token( + self, + b64_img: Optional[str] = None, + user_agent: Optional[str] = None, + proxy: Optional[str] = None, + proxy_type: Optional[str] = "HTTP", + rq_data: Optional[str] = None + ): # Get ID task_id = self.__get_id( b64_img=b64_img, user_agent=user_agent, proxy=proxy, - proxy_type=proxy_type + proxy_type=proxy_type, + rq_data=rq_data ) # Get Answer @@ -80,6 +88,8 @@ def __create_payload(self, **kwargs): payload["task"]["proxyType"] = kwargs.get("proxy_type", "http") if kwargs.get("user_agent", None) is not None: payload["task"]["userAgent"] = kwargs.get("user_agent") + if kwargs.get("rq_data", None) is not None: + payload["task"]["data"] = kwargs.get("rq_data") return payload def __get_id(self,**kwargs): diff --git a/captchatools/capsolver.py b/captchatools/capsolver.py index 622bb6c..0a9184b 100644 --- a/captchatools/capsolver.py +++ b/captchatools/capsolver.py @@ -23,13 +23,21 @@ def get_balance(self) -> float: except requests.RequestException: pass - def get_token(self, b64_img: Optional[str] = None, user_agent: Optional[str] = None, proxy: Optional[str] = None, proxy_type: Optional[str] = "HTTP"): + def get_token( + self, + b64_img: Optional[str] = None, + user_agent: Optional[str] = None, + proxy: Optional[str] = None, + proxy_type: Optional[str] = "HTTP", + rq_data: Optional[str] = None + ): # Get ID task_id, already_solved = self.__get_id( b64_img=b64_img, user_agent=user_agent, proxy=proxy, - proxy_type=proxy_type + proxy_type=proxy_type, + rq_data=rq_data ) # Check if token was already retrieved @@ -68,6 +76,10 @@ def __create_payload(self, **kwargs): payload["task"]["type"] = "HCaptchaTask" payload["task"]["websiteURL"] = self.captcha_url payload["task"]["websiteKey"] = self.sitekey + elif self.captcha_type == "hcaptchaturbo": + payload["task"]["type"] = "HCaptchaTurboTask" + payload["task"]["websiteURL"] = self.captcha_url + payload["task"]["websiteKey"] = self.sitekey # Add Global Data @@ -86,6 +98,8 @@ def __create_payload(self, **kwargs): payload["task"]["proxyType"] = kwargs.get("proxy_type", "http") if kwargs.get("user_agent", None) is not None: payload["task"]["userAgent"] = kwargs.get("user_agent") + if kwargs.get("rq_data", None) is not None: + payload["task"]["enterprisePayload"] = {"rqdata": kwargs.get("rq_data")} return payload def __get_id(self,**kwargs): diff --git a/captchatools/captchaai.py b/captchatools/captchaai.py index 96ba835..ae7f23b 100644 --- a/captchatools/captchaai.py +++ b/captchatools/captchaai.py @@ -18,13 +18,21 @@ def get_balance(self) -> float: except requests.RequestException: pass - def get_token(self, b64_img: Optional[str] = None, user_agent: Optional[str] = None, proxy: Optional[str] = None, proxy_type: Optional[str] = None): + def get_token( + self, + b64_img: Optional[str] = None, + user_agent: Optional[str] = None, + proxy: Optional[str] = None, + proxy_type: Optional[str] = None, + rq_data: Optional[str] = None + ): # Get ID task_id = self.__get_id( b64_img=b64_img, user_agent=user_agent, proxy=proxy, - proxy_type=proxy_type + proxy_type=proxy_type, + rq_data=rq_data ) # Get Answer @@ -65,6 +73,8 @@ def __create_uri_parms(self, **kwargs): params["proxytype"] = pxy_type if kwargs.get("user_agent", None) is not None: params["userAgent"] = kwargs.get("user_agent") + if kwargs.get("rq_data", None) is not None: + params["data"] = kwargs.get("rq_data") return params def __get_id(self,**kwargs): diff --git a/captchatools/twocap.py b/captchatools/twocap.py index 311cbd7..299ccbc 100644 --- a/captchatools/twocap.py +++ b/captchatools/twocap.py @@ -18,13 +18,21 @@ def get_balance(self) -> float: except requests.RequestException: pass - def get_token(self, b64_img: Optional[str] = None, user_agent: Optional[str] = None, proxy: Optional[str] = None, proxy_type: Optional[str] = None): + def get_token( + self, + b64_img: Optional[str] = None, + user_agent: Optional[str] = None, + proxy: Optional[str] = None, + proxy_type: Optional[str] = None, + rq_data: Optional[str] = None + ): # Get ID task_id = self.__get_id( b64_img=b64_img, user_agent=user_agent, proxy=proxy, - proxy_type=proxy_type + proxy_type=proxy_type, + rq_data=rq_data ) # Get Answer @@ -65,6 +73,8 @@ def __create_payload(self, **kwargs): payload["proxytype"] = pxy_type if kwargs.get("user_agent", None) is not None: payload["userAgent"] = kwargs.get("user_agent") + if kwargs.get("rq_data", None) is not None: + payload["data"] = kwargs.get("rq_data") return payload def __get_id(self,**kwargs): diff --git a/setup.py b/setup.py index ef6dadd..902efc4 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from io import open PACKAGE_NAME = "captchatools" -VERSION = "1.4.1" +VERSION = "1.5.0" SHORT_DESCRIPTION = "Python module to help solve captchas with Capmonster, 2captcha and Anticaptcha API's!" GITHUB_URL = "https://github.com/Matthew17-21/Captcha-Tools" diff --git a/update_pypi/upload_to_prod.sh b/update_pypi/upload_to_prod.sh new file mode 100644 index 0000000..161fc37 --- /dev/null +++ b/update_pypi/upload_to_prod.sh @@ -0,0 +1,7 @@ +#! /bin/bash + +echo -e "Running setup.py and creating wheel..." +python setup.py sdist bdist_wheel + +echo -e "Uploading to pypi..." +python -m twine upload dist/* \ No newline at end of file diff --git a/update_pypi/upload_to_test.sh b/update_pypi/upload_to_test.sh new file mode 100644 index 0000000..27419f7 --- /dev/null +++ b/update_pypi/upload_to_test.sh @@ -0,0 +1,7 @@ +#! /bin/bash + +echo -e "Running setup.py and creating wheel..." +python setup.py sdist bdist_wheel + +echo -e "Uploading to test.pypi ..." +python -m twine upload --repository-url https://test.pypi.org/legacy/ dist/* \ No newline at end of file