Skip to content

Commit

Permalink
feat: add truecaptcha (#18)
Browse files Browse the repository at this point in the history
* feat: add truecaptcha
fix: detect incorrect captcha on armory
  • Loading branch information
matthewnbrown authored Oct 7, 2023
1 parent 2e6f5ba commit cbe77d1
Show file tree
Hide file tree
Showing 13 changed files with 595 additions and 299 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
cookies
*.settings
logs
2captcha_settings.json
truecaptcha_settings.json

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down Expand Up @@ -162,4 +164,4 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
#.idea/
38 changes: 20 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ Start with run.py, or install with `pip install .` and run using roc-toolkit-cli

If this is your first run, or you're missing settings files, they will be created for you

Auto captcha solving using [2Captcha](https://2captcha.com/)

Auto captcha solving using [2Captcha](https://2captcha.com/) and [TrueCaptcha](https://truecaptcha.org/)

Windows installation notes:
`pip install .` should install everything


Ubuntu installation notes:
Install pillow and ImageTK:
`apt-get install python3-pil python3-pil.imagetk`
Expand All @@ -20,53 +18,57 @@ Install lxml:
`apt-get install python3-lxml`
`pip install .` for the rest


## User Settings

#### Login Details
**email:** email<span>@email.</span>com
**password:** password123
#### Login Details

**email:** email<span>@email.</span>com
**password:** password123

**auto_solve_captchas:** False or True
**auto_captcha_key:** replace_with_2captcha_apikey

**auto_solve_captchas:** False or 2captcha or truecaptcha

#### Range of time before checking for captchas

**min_checktime_secs:** 300
**max_checktime_secs:** 600
**max_checktime_secs:** 600

#### Switch to a second check range during a certain period of the day

**enable_nightmode:** False
**nightmode_minwait_mins:** 60
**nightmode_maxwait_mins:** 120
**nightmode_begin:** 20:00 (Must be a time in format HH:MM or HH:MM:SS)
**nightmode_end:** 09:00 (Must be a time in format HH:MM or HH:MM:SS)
**nightmode_begin:** 20:00 (Must be a time in format HH:MM or HH:MM:SS)
**nightmode_end:** 09:00 (Must be a time in format HH:MM or HH:MM:SS)

#### Program exits/times out if this many failures occur consecutively

#### Program exits/times out if this many failures occur consecutively
**max_consecutive_login_failures:** 2
**max_consecutive_captcha_attempts:** 3 (Actual failed captcha attempts)
**max_consecutive_answer_errors:** 5 (Receiving impossible answers i.e., letters)
**max_consecutive_answer_errors:** 5 (Receiving impossible answers i.e., letters)
**captcha_failure_timeout:** 0 (How long to wait in minutes after failures. 0 Exits)

**captcha_save_path:** captcha_img/ (path to save captcha images to)
**captcha_save_path:** captcha_img/ (path to save captcha images to)

#### Pull cookie from a browser you already use to login

**load_cookies_from_browser:** True or False
**browser:** chrome or firefox

#### Remote Lookup

**remote_captcha_lookup:** None (API url to lookup captcha answer based on hash)
**remote_captcha_add:** None (API call to add captcha answer to database)

## Buyer Settings

Gold is divided by the folloing formula for each weapon:

(gold) * (weapon number) / (sum of all weapon numbers)
(gold) \* (weapon number) / (sum of all weapon numbers)

## Trainer settings

**train_soldiers:** True/False
**soldier_weapon_match:** True/False (Should soldiers be purchased to match weapons)
**soldier_dump_type:** attack/defense/spies/sentries (All excess untrained dumped to this category)
**soldier_round_amount:** Integer > 0 (Instead of matching weaponcount exactly, round up this amount (nearest 100, 1000 etc)
**min_train_purchase_size:** Integer > 0 (Minimum size to complete a training purchase)
**min_train_purchase_size:** Integer > 0 (Minimum size to complete a training purchase)
175 changes: 107 additions & 68 deletions rocalert/__main__.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
from datetime import datetime, timedelta
import random
import time
from datetime import datetime, timedelta

from .roc_settings import SettingsError
from rocalert.pyrocalert import RocAlert
from rocalert.services.remote_lookup import RemoteCaptcha
import rocalert.services.captchaservices as captchaservices
from rocalert.rocpurchases import ROCBuyer, SimpleRocTrainer
from rocalert.roc_settings import BuyerSettings,\
SettingsSetupHelper, UserSettings, TrainerSettings
from rocalert.captcha.captcha_logger import CaptchaLogger
from rocalert.pyrocalert import RocAlert
from rocalert.roc_settings import (
BuyerSettings,
SettingsSetupHelper,
TrainerSettings,
UserSettings,
)
from rocalert.roc_settings._settings import is_negative_string
from rocalert.roc_web_handler import RocWebHandler
from rocalert.rocpurchases import ROCBuyer, SimpleRocTrainer
from rocalert.services.remote_lookup import RemoteCaptcha
from rocalert.services.urlgenerator import ROCDecryptUrlGenerator
from rocalert.services.useragentgenerator import UserAgentGenerator, Browser, OperatingSystem
from rocalert.services.useragentgenerator import (
Browser,
OperatingSystem,
UserAgentGenerator,
)

from .roc_settings import SettingsError

_user_settings_fp = 'user.settings'
_trainer_settings_fp = 'trainer.settings'
_buyer_settings_fp = 'buyer.settings'
_user_settings_fp = "user.settings"
_trainer_settings_fp = "trainer.settings"
_buyer_settings_fp = "buyer.settings"


def _run():
Expand All @@ -28,24 +38,24 @@ def _run():
services = _configure_services(user_settings)

a = RocAlert(
rochandler=services['rochandler'],
rochandler=services["rochandler"],
usersettings=user_settings,
buyer=services['buyer'],
trainer=services['trainer'],
correctLog=services['correct_captcha_logger'],
generalLog=services['gen_captcha_logger'],
remoteCaptcha=services['remote_captcha'],
capsolver=services['capsolver']
buyer=services["buyer"],
trainer=services["trainer"],
correctLog=services["correct_captcha_logger"],
generalLog=services["gen_captcha_logger"],
remoteCaptcha=services["remote_captcha"],
capsolver=services["capsolver"],
)

a.start()


def _settings_are_valid() -> bool:
filepaths = {
'trainer': (_trainer_settings_fp, TrainerSettings),
'user': (_user_settings_fp, UserSettings),
'buyer': (_buyer_settings_fp, BuyerSettings)
"trainer": (_trainer_settings_fp, TrainerSettings),
"user": (_user_settings_fp, UserSettings),
"buyer": (_buyer_settings_fp, BuyerSettings),
}

settings_file_error = False
Expand All @@ -54,8 +64,7 @@ def _settings_are_valid() -> bool:
path, settingtype = infotuple
if SettingsSetupHelper.needs_setup(path):
settings_file_error = True
SettingsSetupHelper.create_default_file(
path, settingtype.DEFAULT_SETTINGS)
SettingsSetupHelper.create_default_file(path, settingtype.DEFAULT_SETTINGS)
print(f"Created settings file {path}.")

if settings_file_error:
Expand All @@ -68,77 +77,107 @@ def _settings_are_valid() -> bool:
def _configure_services(user_settings: UserSettings) -> dict[str, object]:
services = {}

services['gen_captcha_logger'] = CaptchaLogger(
'logs/captcha_answers.log', timestamp=True)
services["gen_captcha_logger"] = CaptchaLogger(
"logs/captcha_answers.log", timestamp=True
)

services['correct_captcha_logger'] = CaptchaLogger(
'logs/correct_ans.log', log_correctness=False)
services["correct_captcha_logger"] = CaptchaLogger(
"logs/correct_ans.log", log_correctness=False
)

services['default_headers'] = _get_default_headers()
services["default_headers"] = _get_default_headers()

services['remote_captcha'] = RemoteCaptcha(
user_settings.get_value('remote_captcha_add'),
user_settings.get_value('remote_captcha_lookup'))
services["remote_captcha"] = RemoteCaptcha(
user_settings.get_value("remote_captcha_add"),
user_settings.get_value("remote_captcha_lookup"),
)

services['urlgenerator'] = ROCDecryptUrlGenerator()
services["urlgenerator"] = ROCDecryptUrlGenerator()

if user_settings.get_setting('auto_solve_captchas').value:
savepath = user_settings.get_setting('captcha_save_path').value
apikey = user_settings.get_setting('auto_captcha_key').value
services['capsolver'] = captchaservices.TwocaptchaSolverService(
api_key=apikey, savepath=savepath)
else:
services['capsolver'] = captchaservices.ManualCaptchaSolverService()
services["capsolver"] = _get_captcha_solving_service(user_settings)

services['rochandler'] = RocWebHandler(
urlgenerator=services['urlgenerator'],
default_headers=services['default_headers'])
services["rochandler"] = RocWebHandler(
urlgenerator=services["urlgenerator"],
default_headers=services["default_headers"],
)

services['buyer'] = ROCBuyer(
services['rochandler'],
services["buyer"] = ROCBuyer(
services["rochandler"],
BuyerSettings(filepath=_buyer_settings_fp),
)

services['trainer'] = SimpleRocTrainer(
services["trainer"] = SimpleRocTrainer(
TrainerSettings(filepath=_trainer_settings_fp)
)

return services


def _get_captcha_solving_service(user_settings: UserSettings):
service = user_settings.get_setting("auto_solve_captchas").value.lower().strip()

savepath = user_settings.get_setting("captcha_save_path").value

if is_negative_string(service):
return captchaservices.ManualCaptchaSolverService()

captcha_settings = captchaservices.get_captcha_settings(service)
if captcha_settings is None:
filename = captchaservices.create_captca_settings_file(service)
print(f"Created settings file {filename}. Please fill it out and restart")
quit()

if service in ["2captcha", "twocaptcha"]:
apikey = captcha_settings["apiKey"]
return captchaservices.TwocaptchaSolverService(
api_key=apikey, savepath=savepath
)
if service in ["truecaptcha", "true captcha"]:
return captchaservices.TrueCaptchaSolverService(
userid=captcha_settings["userId"],
api_key=captcha_settings["apiKey"],
mode=captcha_settings["mode"],
savepath=savepath,
)


def _get_default_headers():
default_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' \
+ 'AppleWebKit/537.36 (KHTML, like Gecko) ' \
+ 'Chrome/114.0.0.0 Safari/537.36'
default_agent = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
+ "Chrome/114.0.0.0 Safari/537.36"
)
agentgenerator = UserAgentGenerator(default=default_agent)
useragent = agentgenerator.get_useragent(
browser=Browser.Chrome, operatingsystem=OperatingSystem.Windows)
browser=Browser.Chrome, operatingsystem=OperatingSystem.Windows
)

print(f'Using user-agent: "{useragent}"')
return {
'Accept': 'text/html,application/xhtml+xml,application/xml'
+ ';q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Connection': 'keep-alive',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-User': '?1',
'TE': 'trailers',
'Upgrade-Insecure-Requests': '1',
'User-Agent': useragent}
"Accept": "text/html,application/xhtml+xml,application/xml"
+ ";q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5",
"Connection": "keep-alive",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-User": "?1",
"TE": "trailers",
"Upgrade-Insecure-Requests": "1",
"User-Agent": useragent,
}


def _error_nap(errorcount, timebetweenerrors) -> None:
muiltiplier = 1
if timebetweenerrors < timedelta(minutes=5):
print('Very recent error, increasing sleep time')
print("Very recent error, increasing sleep time")
muiltiplier = 2

base = 5*(max(1, errorcount % 4))
base = 5 * (max(1, errorcount % 4))
sleeptime = int(muiltiplier * (base + random.uniform(0, 15)))
print(f'Sleeping for {sleeptime} minutes')
time.sleep(sleeptime*60)
print(f"Sleeping for {sleeptime} minutes")
time.sleep(sleeptime * 60)


def main():
Expand All @@ -150,10 +189,10 @@ def main():
_run()
keeprunning = False
except KeyboardInterrupt as e:
print('Detected keyboard interrupt')
print("Detected keyboard interrupt")
raise e
except SettingsError as e:
print(f'Settings error: {e}\nExiting..')
print(f"Settings error: {e}\nExiting..")
return
except Exception as e:
# TODO: Collect specific exceptions and handle them
Expand All @@ -164,8 +203,8 @@ def main():
print(e)
print(f"\nWarning: Detected exception #{errorcount}")
_error_nap(errorcount, timebetweenerrors)
print('\n\nRestarting...')
print("\n\nRestarting...")


if __name__ == '__main__':
if __name__ == "__main__":
exit(main())
2 changes: 2 additions & 0 deletions rocalert/captcha/solvers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from .manualcaptchasolver import manual_captcha_solve
from .truecaptchasolver import TrueCaptchaSolver
from .twocaptchasolver import TwoCaptchaSolver

if __name__ == "__main__":
manual_captcha_solve()
TwoCaptchaSolver()
TrueCaptchaSolver()
Loading

0 comments on commit cbe77d1

Please sign in to comment.