Skip to content

Commit

Permalink
Captcha fix + participate in raffles
Browse files Browse the repository at this point in the history
  • Loading branch information
the-laziest-coder committed Apr 30, 2024
1 parent 578751c commit 642f4e5
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 44 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- Mystery Boxes
- Gas-less OATs
- Gas OATs and NFTs
- Participate in raffles

### Follow: https://t.me/thelaziestcoder

Expand Down
4 changes: 4 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
WAIT_BETWEEN_ACCOUNTS = [0, 0]
MAX_TRIES = 2

CAP_MONSTER_API_KEY = ""
TWO_CAPTCHA_API_KEY = ""
CAP_SOLVER_API_KEY = ""

THREADS_NUM = 1
DISABLE_SSL = false

Expand Down
1 change: 1 addition & 0 deletions internal/captcha/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .captcha import solve_recaptcha_v2, solve_recaptcha_v3, solve_cloudflare_challenge, solve_geetest
192 changes: 192 additions & 0 deletions internal/captcha/captcha.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import json
import asyncio
import aiohttp
from enum import Enum
from loguru import logger
from urllib.parse import urlparse

from ..utils import async_retry, get_proxy_url
from ..config import TWO_CAPTCHA_API_KEY, CAP_MONSTER_API_KEY, CAP_SOLVER_API_KEY, DISABLE_SSL
from ..vars import USER_AGENT

from .constants import TWO_CAPTCHA_API_URL, CAP_MONSTER_API_URL, CAP_SOLVER_API_URL


def solve_captcha_retry(async_func):
async def wrapper(idx, *args, **kwargs):
last_exc = None
for _ in range(5):
try:
return await async_func(idx, *args, **kwargs)
except Exception as e:
last_exc = e
if last_exc is not None:
raise last_exc

return wrapper


class TaskType(Enum):
RECAPTCHA_V2 = 'RecaptchaV2Task'
RECAPTCHA_V3 = 'RecaptchaV3Task'
RECAPTCHA_V3_PROXY_LESS = 'RecaptchaV3TaskProxyless'
TURNSTILE_TASK = 'TurnstileTask'
ANTI_CLOUDFLARE_TASK = 'AntiCloudflareTask'
GEETEST = 'GeeTestTask'


@solve_captcha_retry
async def solve_recaptcha_v2(idx, url, site_key, proxy=None, **kwargs):
if CAP_SOLVER_API_KEY:
return await _solve_captcha(
CAP_SOLVER_API_URL, CAP_SOLVER_API_KEY, TaskType.RECAPTCHA_V2,
idx, url, site_key, proxy, proxy_one_line=True,
userAgent=USER_AGENT, **kwargs,
)
elif TWO_CAPTCHA_API_KEY:
return await _solve_captcha(
TWO_CAPTCHA_API_URL, TWO_CAPTCHA_API_KEY, TaskType.RECAPTCHA_V2,
idx, url, site_key, proxy, userAgent=USER_AGENT, **kwargs,
)
elif CAP_MONSTER_API_KEY:
return await _solve_captcha(
CAP_MONSTER_API_URL, CAP_MONSTER_API_KEY, TaskType.RECAPTCHA_V2,
idx, url, site_key, proxy, userAgent=USER_AGENT, **kwargs,
)
else:
raise Exception('No captcha service API keys specified for recaptcha v2')


@solve_captcha_retry
async def solve_recaptcha_v3(idx, url, site_key, page_action, proxy=None, **kwargs):
if CAP_SOLVER_API_KEY:
return await _solve_captcha(
CAP_SOLVER_API_URL, CAP_SOLVER_API_KEY, TaskType.RECAPTCHA_V3,
idx, url, site_key, proxy, proxy_one_line=True,
pageAction=page_action, minScore=0.9, userAgent=USER_AGENT, **kwargs,
)
elif TWO_CAPTCHA_API_KEY:
return await _solve_captcha(
TWO_CAPTCHA_API_URL, TWO_CAPTCHA_API_KEY, TaskType.RECAPTCHA_V3_PROXY_LESS,
idx, url, site_key, proxy, pageAction=page_action, minScore=0.9, userAgent=USER_AGENT, **kwargs,
)
elif CAP_MONSTER_API_KEY:
return await _solve_captcha(
CAP_MONSTER_API_URL, CAP_MONSTER_API_KEY, TaskType.RECAPTCHA_V3_PROXY_LESS,
idx, url, site_key, proxy, pageAction=page_action, minScore=0.9, userAgent=USER_AGENT, **kwargs,
)
else:
raise Exception('No captcha service API keys specified for recaptcha v3')


@solve_captcha_retry
async def solve_cloudflare_challenge(idx, url, site_key, proxy):
if TWO_CAPTCHA_API_KEY:
return await _solve_captcha(
TWO_CAPTCHA_API_URL, TWO_CAPTCHA_API_KEY, TaskType.TURNSTILE_TASK,
idx, url, site_key, proxy=proxy, userAgent=USER_AGENT,
)
else:
raise Exception('No captcha service API keys specified for cloudflare')


async def solve_geetest(idx, url, proxy, gt, challenge, version, init_parameters):
if CAP_SOLVER_API_KEY:
return await _solve_captcha(
CAP_SOLVER_API_URL, CAP_SOLVER_API_KEY, TaskType.GEETEST,
idx, url, proxy=proxy, proxy_one_line=True, gt=gt, challenge=challenge, captchaId=gt,
)
elif TWO_CAPTCHA_API_KEY:
return await _solve_captcha(
TWO_CAPTCHA_API_URL, TWO_CAPTCHA_API_KEY, TaskType.GEETEST,
idx, url, proxy=proxy, userAgent=USER_AGENT, gt=gt, challenge=challenge,
version=version, initParameters=init_parameters,
)
elif CAP_MONSTER_API_KEY:
return await _solve_captcha(
CAP_MONSTER_API_URL, CAP_MONSTER_API_KEY, TaskType.GEETEST,
idx, url, proxy=proxy, userAgent=USER_AGENT, gt=gt, challenge=challenge,
version=version, initParameters=init_parameters,
)
else:
raise Exception('No captcha service API keys specified for geetest')


async def _solve_captcha(api_url, client_key,
task_type, idx, url, site_key='',
proxy=None, proxy_one_line=False,
**additional_task_properties):
create_task_req = {
'clientKey': client_key,
'task': {
'type': task_type.value,
'websiteURL': url,
**additional_task_properties,
},
}
if site_key:
create_task_req['task']['websiteKey'] = site_key
proxy = get_proxy_url(proxy)
if proxy and 'Proxyless' not in task_type.value:
if proxy_one_line:
create_task_req['task'].update({
'proxy': proxy,
})
else:
parsed_proxy = urlparse(proxy)
create_task_req['task'].update({
'proxyType': parsed_proxy.scheme,
'proxyAddress': parsed_proxy.hostname,
'proxyPort': parsed_proxy.port,
'proxyLogin': parsed_proxy.username,
'proxyPassword': parsed_proxy.password,
})

req_kwargs = {}
if DISABLE_SSL:
req_kwargs['ssl'] = False

@async_retry
async def create_task():
async with aiohttp.ClientSession() as sess:
async with sess.post(f'{api_url}/createTask', json=create_task_req, **req_kwargs) as resp:
result = json.loads(await resp.text())
if result['errorId'] != 0:
raise Exception(f'Create task error {result.get("errorCode")}: '
f'{result.get("errorDescription")}')
return result['taskId']

async def get_task_result(tid):
async with aiohttp.ClientSession() as sess:
async with sess.post(f'{api_url}/getTaskResult', json={
'clientKey': client_key,
'taskId': tid,
}, **req_kwargs) as resp:
result = json.loads(await resp.text())
if result['errorId'] != 0:
raise Exception(f'Get task result error {result.get("errorCode")}: '
f'{result.get("errorDescription")}')
return result.get('status'), result.get('solution')

logger.info(f'{idx}) Creating captcha task')
task_id = await create_task()
logger.info(f'{idx}) Waiting for captcha solution: {task_id}')
waited, response = 0, None
while waited <= 180:
await asyncio.sleep(10)
waited += 10
status, solution = await get_task_result(task_id)
logger.info(f'{idx}) Captcha task status: {status}')
if solution is None:
continue
if 'GeeTestTask' in task_type.value:
response = solution
elif 'TurnstileTask' in task_type.value:
response = solution['token']
else:
response = solution['gRecaptchaResponse']
break
if response is None:
raise Exception(f'Captcha solving takes too long')
logger.success(f'{idx}) Captcha solution received')
return response
3 changes: 3 additions & 0 deletions internal/captcha/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
TWO_CAPTCHA_API_URL = 'https://api.2captcha.com'
CAP_MONSTER_API_URL = 'https://api.capmonster.cloud'
CAP_SOLVER_API_URL = 'https://api.capsolver.com'
3 changes: 3 additions & 0 deletions internal/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@

WAIT_BETWEEN_ACCOUNTS = cfg.get('WAIT_BETWEEN_ACCOUNTS')
MAX_TRIES = cfg.get('MAX_TRIES')
CAP_MONSTER_API_KEY = cfg.get('CAP_MONSTER_API_KEY')
TWO_CAPTCHA_API_KEY = cfg.get('TWO_CAPTCHA_API_KEY')
CAP_SOLVER_API_KEY = cfg.get('CAP_SOLVER_API_KEY')
THREADS_NUM = cfg.get('THREADS_NUM')
DISABLE_SSL = cfg.get('DISABLE_SSL')
CHECKER_UPDATE_STORAGE = cfg.get('CHECKER_UPDATE_STORAGE')
Expand Down
70 changes: 27 additions & 43 deletions internal/galxe/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from ..storage import Storage
from ..twitter import Twitter
from ..onchain import OnchainAccount
from ..captcha import solve_geetest
from ..config import FAKE_TWITTER, HIDE_UNSUPPORTED, MAX_TRIES, FORCE_LINK_EMAIL, REFERRAL_LINKS, SURVEYS
from ..utils import wait_a_bit, get_query_param, get_proxy_url, async_retry, log_long_exc, plural_str

Expand Down Expand Up @@ -59,44 +60,23 @@ async def __aenter__(self) -> "GalxeAccount":
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
await self.close()

@async_retry
async def get_captcha(self):
try:
call = int(time.time() * 1e3)
params = {
'captcha_id': GALXE_CAPTCHA_ID,
'challenge': str(uuid4()),
'client_type': 'web',
'lang': 'en-us',
'callback': 'geetest_{}'.format(call),
}
resp_text = await self.client.get('https://gcaptcha4.geetest.com/load', with_text=True, params=params)
try:
js_data = json.loads(resp_text.strip('geetest_{}('.format(call)).strip(')'))['data']
except Exception:
raise Exception('Captcha load: ' + resp_text)

params = {
'captcha_id': GALXE_CAPTCHA_ID,
'client_type': 'web',
'lot_number': js_data['lot_number'],
'payload': js_data['payload'],
'process_token': js_data['process_token'],
'payload_protocol': '1',
'pt': '1',
'w': await fingerprints.get(),
'callback': 'geetest_{}'.format(call),
}
resp_text = await self.client.get('https://gcaptcha4.geetest.com/verify', with_text=True, params=params)
try:
data = json.loads(resp_text.strip('geetest_{}('.format(call)).strip(')'))['data']
except Exception:
raise Exception('Captcha verify: ' + resp_text)

solution = await solve_geetest(
self.idx, 'https://app.galxe.com/quest', self.account.proxy,
GALXE_CAPTCHA_ID, str(uuid4()), 4,
{
'captcha_id': GALXE_CAPTCHA_ID,
'client_type': 'web',
'lang': 'en-us',
}
)
return {
'lotNumber': data['lot_number'],
'captchaOutput': data['seccode']['captcha_output'],
'passToken': data['seccode']['pass_token'],
'genTime': data['seccode']['gen_time'],
'lotNumber': solution['lot_number'],
'captchaOutput': solution['captcha_output'],
'passToken': solution['pass_token'],
'genTime': solution['gen_time'],
}
except Exception as e:
raise Exception(f'Failed to solve captcha: {str(e)}')
Expand Down Expand Up @@ -396,8 +376,11 @@ async def _complete_cred_group(self, campaign_id: str, cred_group) -> bool:
raise exc
await wait_a_bit()
except Exception as e:
if ('try again in 30 seconds' in str(e) or 'please verify after 1 minutes' in str(e) or
'Message: "None": Status = 200' in str(e)):
s_e = str(e)
if ('try again in 30 seconds' in s_e or 'please verify after 1 minutes' in s_e or
('Message: "None": Status = 200' in s_e and
'Galxe Web3 Score - Humanity Score' not in credential["name"])):
print(f'im here |{s_e}|')
try_again = True
await log_long_exc(self.idx, f'Failed to complete "{credential["name"]}"', e, warning=True)
return try_again
Expand Down Expand Up @@ -498,7 +481,7 @@ async def _complete_eth(self, campaign_id: str, credential) -> bool:
await self.add_typed_credential(campaign_id, credential)
return True
case CredSource.CSV:
raise Exception(f'{self.idx}) CSV cred source for EVM task is not supported yet')
raise Exception(f'{self.idx}) It seems like you are not eligible for custom project requirements')
logger.warning(f'{self.idx}) {credential["name"]} is not done or not updated yet. Trying to verify it anyway')
return True

Expand Down Expand Up @@ -597,14 +580,12 @@ async def _complete_survey(self, campaign_id, survey):
logger.success(f'{self.idx}) "{survey_name}" submitted')

@captcha_retry
@async_retry
async def add_typed_credential(self, campaign_id: str, credential):
captcha = await self.get_captcha()
await self.client.add_typed_credential_items(campaign_id, credential['id'], captcha)
await wait_a_bit(3)

@captcha_retry
@async_retry
async def _sync_credential(self, campaign_id: str, credential_id: str, cred_type: str):
sync_options = self._default_sync_options(credential_id)
match cred_type:
Expand Down Expand Up @@ -662,7 +643,7 @@ def already_claimed(self, campaign) -> bool:
return self._campaign_points_claimed(campaign)
case Gamification.OAT | Gamification.DROP:
return self._campaign_points_claimed(campaign) and self._campaign_nft_claimed(campaign)
case Gamification.POINTS_MYSTERY_BOX:
case Gamification.POINTS_MYSTERY_BOX | Gamification.BOUNTY:
return self._campaign_nft_claimed(campaign)
case unexpected:
if HIDE_UNSUPPORTED:
Expand All @@ -677,8 +658,10 @@ async def _claim_campaign_process(self, campaign):
if self.already_claimed(campaign):
nft_cnt = self.account.nfts.get(campaign["id"])
nft_info = f' and {plural_str(nft_cnt, "NFT")}' if nft_cnt is not None else ''
bounty_info = ' and participated in bounty' if self._get_gamification_type(campaign) == Gamification.BOUNTY \
else ''
logger.info(f'{self.idx}) {campaign["name"]} already claimed '
f'{self.account.points[campaign["id"]][1]} points{nft_info}')
f'{self.account.points[campaign["id"]][1]} points{nft_info}{bounty_info}')
return
logger.info(f'{self.idx}) Starting claim {campaign["name"]}')
claimable = False
Expand Down Expand Up @@ -745,6 +728,8 @@ async def _claim_campaign_rewards(self, campaign):
await self._claim_gas_reward(campaign, claim_data)
claimed_nfts = len(claim_data['mintFuncInfo']['verifyIDs'])
claimed_log = plural_str(claimed_nfts, nft_type)
case Gamification.BOUNTY:
claimed_log = '[Participated in Bounty]'

case unexpected:
raise Exception(f'{unexpected} reward type is not supported for claim yet')
Expand All @@ -766,7 +751,6 @@ def get_referral_code(cls, campaign):
return None

@captcha_retry
@async_retry
async def _get_claim_data(self, campaign):
chain = campaign['chain']
if chain == 'APTOS':
Expand Down
1 change: 1 addition & 0 deletions internal/galxe/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Gamification(StrEnum):
OAT = 'Oat'
POINTS_MYSTERY_BOX = 'PointsMysteryBox'
DROP = 'Drop'
BOUNTY = 'Bounty'


class GasType(StrEnum):
Expand Down
4 changes: 3 additions & 1 deletion internal/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from aiohttp_socks import ProxyConnector
from urllib.parse import urlparse, parse_qs

from ..config import MAX_TRIES
from ..config import MAX_TRIES, DISABLE_SSL

from .async_web3 import AsyncHTTPProviderWithUA

Expand Down Expand Up @@ -112,4 +112,6 @@ def to_bytes(hex_str):
def get_w3(rpc_url: str, proxy: str = None):
proxy = get_proxy_url(proxy)
req_kwargs = {} if proxy is None else {'proxy': proxy}
if DISABLE_SSL:
req_kwargs['ssl'] = False
return AsyncWeb3(AsyncHTTPProviderWithUA(rpc_url, req_kwargs))

0 comments on commit 642f4e5

Please sign in to comment.