check email before login; update logs

Этот коммит содержится в:
Vlad Pronsky 2023-07-14 23:50:32 +03:00
родитель 895ec22e22
Коммит 95886e6b50
5 изменённых файлов: 51 добавлений и 31 удалений

Просмотреть файл

@ -42,6 +42,7 @@ class AccountsPool:
if not required.issubset(tokens): if not required.issubset(tokens):
raise ValueError(f"Invalid line format: {line_format}") raise ValueError(f"Invalid line format: {line_format}")
accounts = []
with open(filepath, "r") as f: with open(filepath, "r") as f:
lines = f.read().split("\n") lines = f.read().split("\n")
lines = [x.strip() for x in lines if x.strip()] lines = [x.strip() for x in lines if x.strip()]
@ -49,12 +50,14 @@ class AccountsPool:
for line in lines: for line in lines:
data = [x.strip() for x in line.split(line_delim)] data = [x.strip() for x in line.split(line_delim)]
if len(data) < len(tokens): if len(data) < len(tokens):
logger.warning(f"Invalid line format: {line}") raise ValueError(f"Invalid line: {line}")
continue
data = data[: len(tokens)] data = data[: len(tokens)]
vals = {k: v for k, v in zip(tokens, data) if k != "_"} vals = {k: v for k, v in zip(tokens, data) if k != "_"}
await self.add_account(**vals) accounts.append(vals)
for x in accounts:
await self.add_account(**x)
async def add_account( async def add_account(
self, self,

Просмотреть файл

@ -50,7 +50,7 @@ def search_email_code(imap: imaplib.IMAP4_SSL, count: int, min_t: datetime | Non
msg_time = datetime.strptime(msg.get("Date", ""), "%a, %d %b %Y %H:%M:%S %z") msg_time = datetime.strptime(msg.get("Date", ""), "%a, %d %b %Y %H:%M:%S %z")
msg_from = str(msg.get("From", "")).lower() msg_from = str(msg.get("From", "")).lower()
msg_subj = str(msg.get("Subject", "")).lower() msg_subj = str(msg.get("Subject", "")).lower()
logger.debug(f"({i} of {count}) {msg_from} - {msg_time} - {msg_subj}") logger.info(f"({i} of {count}) {msg_from} - {msg_time} - {msg_subj}")
if min_t is not None and msg_time < min_t: if min_t is not None and msg_time < min_t:
return None return None
@ -62,17 +62,11 @@ def search_email_code(imap: imaplib.IMAP4_SSL, count: int, min_t: datetime | Non
return None return None
async def get_email_code(email: str, password: str, min_t: datetime | None = None) -> str: async def imap_get_email_code(
domain = get_imap_domain(email) imap: imaplib.IMAP4_SSL, email: str, min_t: datetime | None = None
start_time = time.time() ) -> str:
with imaplib.IMAP4_SSL(domain) as imap: try:
try: start_time, was_count = time.time(), 0
imap.login(email, password)
except imaplib.IMAP4.error as e:
logger.error(f"Error logging into {email}: {e}")
raise EmailLoginError() from e
was_count = 0
while True: while True:
_, rep = imap.select("INBOX") _, rep = imap.select("INBOX")
now_count = int(rep[0].decode("utf-8")) if len(rep) > 0 and rep[0] is not None else 0 now_count = int(rep[0].decode("utf-8")) if len(rep) > 0 and rep[0] is not None else 0
@ -81,8 +75,28 @@ async def get_email_code(email: str, password: str, min_t: datetime | None = Non
if code is not None: if code is not None:
return code return code
logger.debug(f"Waiting for confirmation code for {email}, msg_count: {now_count}") logger.info(f"Waiting for confirmation code for {email}, msg_count: {now_count}")
if MAX_WAIT_SEC < time.time() - start_time: if MAX_WAIT_SEC < time.time() - start_time:
logger.error(f"Timeout waiting for confirmation code for {email}") logger.info(f"Timeout waiting for confirmation code for {email}")
raise EmailCodeTimeoutError() raise EmailCodeTimeoutError()
await asyncio.sleep(5) await asyncio.sleep(5)
except Exception as e:
imap.select("INBOX")
imap.close()
logger.error(f"Error getting confirmation code for {email}: {e}")
raise e
async def imap_try_login(email: str, password: str):
domain = get_imap_domain(email)
imap = imaplib.IMAP4_SSL(domain)
try:
imap.login(email, password)
except imaplib.IMAP4.error as e:
logger.error(f"Error logging into {email} on {domain}: {e}")
imap.select("INBOX")
imap.close()
raise EmailLoginError() from e
return imap

Просмотреть файл

@ -4,7 +4,7 @@ from httpx import AsyncClient, HTTPStatusError, Response
from .account import Account from .account import Account
from .constants import LOGIN_URL from .constants import LOGIN_URL
from .imap import get_email_code from .imap import imap_get_email_code, imap_try_login
from .logger import logger from .logger import logger
from .utils import raise_for_status from .utils import raise_for_status
@ -100,7 +100,7 @@ async def login_duplication_check(client: AsyncClient, acc: Account, prev: dict)
return rep return rep
async def login_confirm_email(client: AsyncClient, acc: Account, prev: dict) -> Response: async def login_confirm_email(client: AsyncClient, acc: Account, prev: dict, imap) -> Response:
payload = { payload = {
"flow_token": prev["flow_token"], "flow_token": prev["flow_token"],
"subtask_inputs": [ "subtask_inputs": [
@ -116,9 +116,9 @@ async def login_confirm_email(client: AsyncClient, acc: Account, prev: dict) ->
return rep return rep
async def login_confirm_email_code(client: AsyncClient, acc: Account, prev: dict): async def login_confirm_email_code(client: AsyncClient, acc: Account, prev: dict, imap):
now_time = datetime.now(timezone.utc) - timedelta(seconds=30) now_time = datetime.now(timezone.utc) - timedelta(seconds=30)
value = await get_email_code(acc.email, acc.email_password, now_time) value = await imap_get_email_code(imap, acc.email, now_time)
payload = { payload = {
"flow_token": prev["flow_token"], "flow_token": prev["flow_token"],
@ -146,7 +146,7 @@ async def login_success(client: AsyncClient, acc: Account, prev: dict) -> Respon
return rep return rep
async def next_login_task(client: AsyncClient, acc: Account, rep: Response): async def next_login_task(client: AsyncClient, acc: Account, rep: Response, imap):
ct0 = client.cookies.get("ct0", None) ct0 = client.cookies.get("ct0", None)
if ct0: if ct0:
client.headers["x-csrf-token"] = ct0 client.headers["x-csrf-token"] = ct0
@ -164,9 +164,8 @@ async def next_login_task(client: AsyncClient, acc: Account, rep: Response):
return await login_success(client, acc, prev) return await login_success(client, acc, prev)
if task_id == "LoginAcid": if task_id == "LoginAcid":
is_code = x["enter_text"]["hint_text"].lower() == "confirmation code" is_code = x["enter_text"]["hint_text"].lower() == "confirmation code"
# logger.debug(f"is login code: {is_code}")
fn = login_confirm_email_code if is_code else login_confirm_email fn = login_confirm_email_code if is_code else login_confirm_email
return await fn(client, acc, prev) return await fn(client, acc, prev, imap)
if task_id == "AccountDuplicationCheck": if task_id == "AccountDuplicationCheck":
return await login_duplication_check(client, acc, prev) return await login_duplication_check(client, acc, prev)
if task_id == "LoginEnterPassword": if task_id == "LoginEnterPassword":
@ -186,10 +185,12 @@ async def next_login_task(client: AsyncClient, acc: Account, rep: Response):
async def login(acc: Account, fresh=False) -> Account: async def login(acc: Account, fresh=False) -> Account:
log_id = f"{acc.username} - {acc.email}" log_id = f"{acc.username} - {acc.email}"
if acc.active and not fresh: if acc.active and not fresh:
logger.debug(f"account already active {log_id}") logger.info(f"account already active {log_id}")
return acc return acc
logger.debug(f"logging in {log_id}") # check if email is valid first
imap = await imap_try_login(acc.email, acc.email_password)
client = acc.make_client() client = acc.make_client()
guest_token = await get_guest_token(client) guest_token = await get_guest_token(client)
client.headers["x-guest-token"] = guest_token client.headers["x-guest-token"] = guest_token
@ -200,7 +201,7 @@ async def login(acc: Account, fresh=False) -> Account:
break break
try: try:
rep = await next_login_task(client, acc, rep) rep = await next_login_task(client, acc, rep, imap)
except HTTPStatusError as e: except HTTPStatusError as e:
if e.response.status_code == 403: if e.response.status_code == 403:
logger.error(f"403 error {log_id}") logger.error(f"403 error {log_id}")

Просмотреть файл

@ -105,7 +105,8 @@ class QueueClient:
return self.ctx return self.ctx
async def _check_rep(self, rep: httpx.Response): async def _check_rep(self, rep: httpx.Response):
dump_rep(rep) if self.debug:
dump_rep(rep)
try: try:
res = rep.json() res = rep.json()
@ -116,8 +117,9 @@ class QueueClient:
if "errors" in res: if "errors" in res:
msg = "; ".join([f'({x.get("code", -1)}) {x["message"]}' for x in res["errors"]]) msg = "; ".join([f'({x.get("code", -1)}) {x["message"]}' for x in res["errors"]])
fn = logger.info if rep.status_code == 200 else logger.warning if self.debug:
fn(f"{rep.status_code:3d} - {req_id(rep)} - {msg}") fn = logger.info if rep.status_code == 200 else logger.warning
fn(f"{rep.status_code:3d} - {req_id(rep)} - {msg}")
if msg.startswith("The following features cannot be null"): if msg.startswith("The following features cannot be null"):
logger.error(f"Invalid request: {msg}") logger.error(f"Invalid request: {msg}")

Просмотреть файл

@ -22,7 +22,7 @@ def raise_for_status(rep: Response, label: str):
try: try:
rep.raise_for_status() rep.raise_for_status()
except HTTPStatusError as e: except HTTPStatusError as e:
logger.debug(f"{label} - {rep.status_code} - {rep.text}") logger.info(f"{label} - {rep.status_code} - {rep.text}")
raise e raise e