tweepy-self 1.10.0b6__py3-none-any.whl → 1.10.0b8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
| @@ -10,14 +10,14 @@ twitter/account.py,sha256=joAB5Zw-Le5E3kOZ-1nb4DPGlTqWYv2Vs6gJ3cwu7is,3175 | |
| 10 10 | 
             
            twitter/base/__init__.py,sha256=Q2ko0HeOS5tiBnDVKxxaZYetwRR3YXJ67ujL3oThGd4,141
         | 
| 11 11 | 
             
            twitter/base/client.py,sha256=J_iL4ZGfwTbZ2gpjtFCbBxNgt7weJ55EeMGzYsLtjf4,500
         | 
| 12 12 | 
             
            twitter/base/session.py,sha256=JFPS-9Qae1iY3NfNcywxvWWmRDijaU_Rjs3WaQ00iFA,2071
         | 
| 13 | 
            -
            twitter/client.py,sha256= | 
| 13 | 
            +
            twitter/client.py,sha256=3eCld11wiZPOKF561RlTcJ_Re-lupvGSPz7MPKrJl1k,75103
         | 
| 14 14 | 
             
            twitter/enums.py,sha256=-OH6Ibxarq5qt4E2AhkProVawcEyIf5YG_h_G5xiV9Y,270
         | 
| 15 15 | 
             
            twitter/errors.py,sha256=oNa0Neos80ZK4-0FBzqgxXonH564qFnoN-kavHalfR4,5274
         | 
| 16 | 
            -
            twitter/models.py,sha256= | 
| 16 | 
            +
            twitter/models.py,sha256=CrGb3dvA0U4PfPTkUtuprPKXpqkLpM8AR_-De4D3efM,5677
         | 
| 17 17 | 
             
            twitter/utils/__init__.py,sha256=usxpfcRQ7zxTTgZ-i425tT7hIz73Pwh9FDj4t6O3dYg,663
         | 
| 18 18 | 
             
            twitter/utils/file.py,sha256=Sz2KEF9DnL04aOP1XabuMYMMF4VR8dJ_KWMEVvQ666Y,1120
         | 
| 19 19 | 
             
            twitter/utils/html.py,sha256=nrOJw0vUKfBaHgFaQSQIdXfvfZ8mdu84MU_s46kJTJ4,2087
         | 
| 20 20 | 
             
            twitter/utils/other.py,sha256=9RIYF2AMdmNKIwClG3jBP7zlvxZPEgYfuHaIiOhURzM,1061
         | 
| 21 | 
            -
            tweepy_self-1.10. | 
| 22 | 
            -
            tweepy_self-1.10. | 
| 23 | 
            -
            tweepy_self-1.10. | 
| 21 | 
            +
            tweepy_self-1.10.0b8.dist-info/METADATA,sha256=gghI1xBSOt1w8QajiIFg6H93LAydql3haQ3tk0YghoU,13153
         | 
| 22 | 
            +
            tweepy_self-1.10.0b8.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
         | 
| 23 | 
            +
            tweepy_self-1.10.0b8.dist-info/RECORD,,
         | 
    
        twitter/client.py
    CHANGED
    
    | @@ -30,7 +30,7 @@ from .errors import ( | |
| 30 30 | 
             
            from .utils import to_json
         | 
| 31 31 | 
             
            from .base import BaseHTTPClient
         | 
| 32 32 | 
             
            from .account import Account, AccountStatus
         | 
| 33 | 
            -
            from .models import User, Tweet, Media
         | 
| 33 | 
            +
            from .models import User, Tweet, Media, Subtask
         | 
| 34 34 | 
             
            from .utils import (
         | 
| 35 35 | 
             
                parse_oauth_html,
         | 
| 36 36 | 
             
                parse_unlock_html,
         | 
| @@ -190,7 +190,6 @@ class Client(BaseHTTPClient): | |
| 190 190 | 
             
                                raise Locked(exc, self.account)
         | 
| 191 191 | 
             
                            raise exc
         | 
| 192 192 |  | 
| 193 | 
            -
                        self.account.status = AccountStatus.GOOD
         | 
| 194 193 | 
             
                        return response, data
         | 
| 195 194 |  | 
| 196 195 | 
             
                    if response.status_code == 400:
         | 
| @@ -1184,6 +1183,7 @@ class Client(BaseHTTPClient): | |
| 1184 1183 | 
             
                    url = "https://twitter.com/i/api/1.1/account/update_profile.json"
         | 
| 1185 1184 | 
             
                    try:
         | 
| 1186 1185 | 
             
                        await self.request("POST", url, auto_unlock=False, auto_relogin=False)
         | 
| 1186 | 
            +
                        self.account.status = AccountStatus.GOOD
         | 
| 1187 1187 | 
             
                    except BadAccount:
         | 
| 1188 1188 | 
             
                        pass
         | 
| 1189 1189 |  | 
| @@ -1377,7 +1377,8 @@ class Client(BaseHTTPClient): | |
| 1377 1377 | 
             
                        solution = await FunCaptcha(**funcaptcha).aio_captcha_handler()
         | 
| 1378 1378 | 
             
                        if solution.errorId:
         | 
| 1379 1379 | 
             
                            logger.warning(
         | 
| 1380 | 
            -
                                f"{self.account}  | 
| 1380 | 
            +
                                f"(auth_token={self.account.hidden_auth_token}, id={self.account.id}, username={self.account.username})"
         | 
| 1381 | 
            +
                                f"Failed to solve funcaptcha:"
         | 
| 1381 1382 | 
             
                                f"\n\tUnlock attempt: {attempt}/{self.max_unlock_attempts}"
         | 
| 1382 1383 | 
             
                                f"\n\tError ID: {solution.errorId}"
         | 
| 1383 1384 | 
             
                                f"\n\tError code: {solution.errorCode}"
         | 
| @@ -1422,18 +1423,51 @@ class Client(BaseHTTPClient): | |
| 1422 1423 |  | 
| 1423 1424 | 
             
                    await self.establish_status()
         | 
| 1424 1425 |  | 
| 1425 | 
            -
                async def  | 
| 1426 | 
            +
                async def update_backup_code(self):
         | 
| 1427 | 
            +
                    url = "https://api.twitter.com/1.1/account/backup_code.json"
         | 
| 1428 | 
            +
                    response, response_json = await self.request("GET", url)
         | 
| 1429 | 
            +
                    self.account.backup_code = response_json["codes"][0]
         | 
| 1430 | 
            +
             | 
| 1431 | 
            +
                async def _send_raw_subtask(self, **request_kwargs) -> tuple[str, list[Subtask]]:
         | 
| 1426 1432 | 
             
                    """
         | 
| 1427 | 
            -
                    :return: flow_token | 
| 1433 | 
            +
                    :return: flow_token and subtasks
         | 
| 1428 1434 | 
             
                    """
         | 
| 1429 1435 | 
             
                    url = "https://api.twitter.com/1.1/onboarding/task.json"
         | 
| 1430 | 
            -
                    response,  | 
| 1431 | 
            -
                     | 
| 1436 | 
            +
                    response, data = await self.request("POST", url, **request_kwargs)
         | 
| 1437 | 
            +
                    subtasks = [
         | 
| 1438 | 
            +
                        Subtask.from_raw_data(subtask_data) for subtask_data in data["subtasks"]
         | 
| 1439 | 
            +
                    ]
         | 
| 1440 | 
            +
                    log_message = (
         | 
| 1441 | 
            +
                        f"(auth_token={self.account.hidden_auth_token}, id={self.account.id}, username={self.account.username})"
         | 
| 1442 | 
            +
                        f" Requested subtasks:"
         | 
| 1443 | 
            +
                    )
         | 
| 1444 | 
            +
                    for subtask in subtasks:
         | 
| 1445 | 
            +
                        log_message += f"\n\t{subtask.id}"
         | 
| 1446 | 
            +
                        if subtask.primary_text:
         | 
| 1447 | 
            +
                            log_message += f"\n\tPrimary text: {subtask.primary_text}"
         | 
| 1448 | 
            +
                        if subtask.secondary_text:
         | 
| 1449 | 
            +
                            log_message += f"\n\tSecondary text: {subtask.secondary_text}"
         | 
| 1450 | 
            +
                        if subtask.detail_text:
         | 
| 1451 | 
            +
                            log_message += f"\n\tDetail text: {subtask.detail_text}"
         | 
| 1452 | 
            +
                    logger.debug(log_message)
         | 
| 1453 | 
            +
                    return data["flow_token"], subtasks
         | 
| 1432 1454 |  | 
| 1433 | 
            -
                async def  | 
| 1434 | 
            -
                     | 
| 1435 | 
            -
                    : | 
| 1436 | 
            -
                     | 
| 1455 | 
            +
                async def _complete_subtask(
         | 
| 1456 | 
            +
                    self,
         | 
| 1457 | 
            +
                    flow_token: str,
         | 
| 1458 | 
            +
                    inputs: list[dict],
         | 
| 1459 | 
            +
                    **request_kwargs,
         | 
| 1460 | 
            +
                ) -> tuple[str, list[Subtask]]:
         | 
| 1461 | 
            +
                    payload = request_kwargs["json"] = request_kwargs.get("json") or {}
         | 
| 1462 | 
            +
                    payload.update(
         | 
| 1463 | 
            +
                        {
         | 
| 1464 | 
            +
                            "flow_token": flow_token,
         | 
| 1465 | 
            +
                            "subtask_inputs": inputs,
         | 
| 1466 | 
            +
                        }
         | 
| 1467 | 
            +
                    )
         | 
| 1468 | 
            +
                    return await self._send_raw_subtask(**request_kwargs)
         | 
| 1469 | 
            +
             | 
| 1470 | 
            +
                async def _request_login_tasks(self) -> tuple[str, list[Subtask]]:
         | 
| 1437 1471 | 
             
                    params = {
         | 
| 1438 1472 | 
             
                        "flow_name": "login",
         | 
| 1439 1473 | 
             
                    }
         | 
| @@ -1488,30 +1522,14 @@ class Client(BaseHTTPClient): | |
| 1488 1522 | 
             
                            "web_modal": 1,
         | 
| 1489 1523 | 
             
                        },
         | 
| 1490 1524 | 
             
                    }
         | 
| 1491 | 
            -
                    return await self. | 
| 1525 | 
            +
                    return await self._send_raw_subtask(params=params, json=payload, auth=False)
         | 
| 1492 1526 |  | 
| 1493 | 
            -
                async def  | 
| 1494 | 
            -
                     | 
| 1495 | 
            -
                    payload.update(
         | 
| 1496 | 
            -
                        {
         | 
| 1497 | 
            -
                            "flow_token": flow_token,
         | 
| 1498 | 
            -
                            "subtask_inputs": subtask_inputs,
         | 
| 1499 | 
            -
                        }
         | 
| 1500 | 
            -
                    )
         | 
| 1501 | 
            -
                    return await self._task(**kwargs)
         | 
| 1502 | 
            -
             | 
| 1503 | 
            -
                async def _finish_task(self, flow_token):
         | 
| 1504 | 
            -
                    payload = {
         | 
| 1505 | 
            -
                        "flow_token": flow_token,
         | 
| 1506 | 
            -
                        "subtask_inputs": [],
         | 
| 1507 | 
            -
                    }
         | 
| 1508 | 
            -
                    return await self._task(json=payload)
         | 
| 1509 | 
            -
             | 
| 1510 | 
            -
                async def _login_enter_user_identifier(self, flow_token):
         | 
| 1511 | 
            -
                    subtask_inputs = [
         | 
| 1527 | 
            +
                async def _login_enter_user_identifier(self, flow_token: str):
         | 
| 1528 | 
            +
                    inputs = [
         | 
| 1512 1529 | 
             
                        {
         | 
| 1513 1530 | 
             
                            "subtask_id": "LoginEnterUserIdentifierSSO",
         | 
| 1514 1531 | 
             
                            "settings_list": {
         | 
| 1532 | 
            +
                                "link": "next_link",
         | 
| 1515 1533 | 
             
                                "setting_responses": [
         | 
| 1516 1534 | 
             
                                    {
         | 
| 1517 1535 | 
             
                                        "key": "user_identifier",
         | 
| @@ -1523,49 +1541,70 @@ class Client(BaseHTTPClient): | |
| 1523 1541 | 
             
                                        },
         | 
| 1524 1542 | 
             
                                    }
         | 
| 1525 1543 | 
             
                                ],
         | 
| 1526 | 
            -
                                "link": "next_link",
         | 
| 1527 1544 | 
             
                            },
         | 
| 1528 1545 | 
             
                        }
         | 
| 1529 1546 | 
             
                    ]
         | 
| 1530 | 
            -
                    return await self. | 
| 1547 | 
            +
                    return await self._complete_subtask(flow_token, inputs, auth=False)
         | 
| 1531 1548 |  | 
| 1532 | 
            -
                async def _login_enter_password(self, flow_token):
         | 
| 1533 | 
            -
                     | 
| 1549 | 
            +
                async def _login_enter_password(self, flow_token: str):
         | 
| 1550 | 
            +
                    inputs = [
         | 
| 1534 1551 | 
             
                        {
         | 
| 1535 1552 | 
             
                            "subtask_id": "LoginEnterPassword",
         | 
| 1536 1553 | 
             
                            "enter_password": {
         | 
| 1537 | 
            -
                                "password": self.account.password,
         | 
| 1538 1554 | 
             
                                "link": "next_link",
         | 
| 1555 | 
            +
                                "password": self.account.password,
         | 
| 1539 1556 | 
             
                            },
         | 
| 1540 1557 | 
             
                        }
         | 
| 1541 1558 | 
             
                    ]
         | 
| 1542 | 
            -
                    return await self. | 
| 1559 | 
            +
                    return await self._complete_subtask(flow_token, inputs, auth=False)
         | 
| 1543 1560 |  | 
| 1544 1561 | 
             
                async def _account_duplication_check(self, flow_token):
         | 
| 1545 | 
            -
                     | 
| 1562 | 
            +
                    inputs = [
         | 
| 1546 1563 | 
             
                        {
         | 
| 1547 1564 | 
             
                            "subtask_id": "AccountDuplicationCheck",
         | 
| 1548 1565 | 
             
                            "check_logged_in_account": {"link": "AccountDuplicationCheck_false"},
         | 
| 1549 1566 | 
             
                        }
         | 
| 1550 1567 | 
             
                    ]
         | 
| 1551 | 
            -
                    return await self. | 
| 1552 | 
            -
             | 
| 1553 | 
            -
                async def _login_two_factor_auth_challenge(self, flow_token):
         | 
| 1554 | 
            -
                    if not self.account.totp_secret:
         | 
| 1555 | 
            -
                        raise TwitterException(
         | 
| 1556 | 
            -
                            f"Failed to login. Task id: LoginTwoFactorAuthChallenge"
         | 
| 1557 | 
            -
                        )
         | 
| 1568 | 
            +
                    return await self._complete_subtask(flow_token, inputs, auth=False)
         | 
| 1558 1569 |  | 
| 1559 | 
            -
             | 
| 1570 | 
            +
                async def _login_two_factor_auth_challenge(self, flow_token, value: str):
         | 
| 1571 | 
            +
                    inputs = [
         | 
| 1560 1572 | 
             
                        {
         | 
| 1561 1573 | 
             
                            "subtask_id": "LoginTwoFactorAuthChallenge",
         | 
| 1562 1574 | 
             
                            "enter_text": {
         | 
| 1563 | 
            -
                                "text": self.account.get_totp_code(),
         | 
| 1564 1575 | 
             
                                "link": "next_link",
         | 
| 1576 | 
            +
                                "text": value,
         | 
| 1565 1577 | 
             
                            },
         | 
| 1566 1578 | 
             
                        }
         | 
| 1567 1579 | 
             
                    ]
         | 
| 1568 | 
            -
                    return await self. | 
| 1580 | 
            +
                    return await self._complete_subtask(flow_token, inputs, auth=False)
         | 
| 1581 | 
            +
             | 
| 1582 | 
            +
                async def _login_two_factor_auth_choose_method(
         | 
| 1583 | 
            +
                    self, flow_token: str, choices: Iterable[int] = (0,)
         | 
| 1584 | 
            +
                ):
         | 
| 1585 | 
            +
                    inputs = [
         | 
| 1586 | 
            +
                        {
         | 
| 1587 | 
            +
                            "subtask_id": "LoginTwoFactorAuthChooseMethod",
         | 
| 1588 | 
            +
                            "choice_selection": {
         | 
| 1589 | 
            +
                                "link": "next_link",
         | 
| 1590 | 
            +
                                "selected_choices": [str(choice) for choice in choices],
         | 
| 1591 | 
            +
                            },
         | 
| 1592 | 
            +
                        }
         | 
| 1593 | 
            +
                    ]
         | 
| 1594 | 
            +
                    return await self._complete_subtask(flow_token, inputs, auth=False)
         | 
| 1595 | 
            +
             | 
| 1596 | 
            +
                async def _login_acid(
         | 
| 1597 | 
            +
                    self,
         | 
| 1598 | 
            +
                    flow_token: str,
         | 
| 1599 | 
            +
                    value: str,
         | 
| 1600 | 
            +
                ):
         | 
| 1601 | 
            +
                    inputs = [
         | 
| 1602 | 
            +
                        {
         | 
| 1603 | 
            +
                            "subtask_id": "LoginAcid",
         | 
| 1604 | 
            +
                            "enter_text": {"text": value, "link": "next_link"},
         | 
| 1605 | 
            +
                        }
         | 
| 1606 | 
            +
                    ]
         | 
| 1607 | 
            +
                    return await self._complete_subtask(flow_token, inputs, auth=False)
         | 
| 1569 1608 |  | 
| 1570 1609 | 
             
                async def _viewer(self):
         | 
| 1571 1610 | 
             
                    url, query_id = self._action_to_url("Viewer")
         | 
| @@ -1582,9 +1621,9 @@ class Client(BaseHTTPClient): | |
| 1582 1621 | 
             
                    }
         | 
| 1583 1622 | 
             
                    variables = {"withCommunitiesMemberships": True}
         | 
| 1584 1623 | 
             
                    params = {
         | 
| 1585 | 
            -
                        "features":  | 
| 1586 | 
            -
                        "fieldToggles":  | 
| 1587 | 
            -
                        "variables":  | 
| 1624 | 
            +
                        "features": features,
         | 
| 1625 | 
            +
                        "fieldToggles": field_toggles,
         | 
| 1626 | 
            +
                        "variables": variables,
         | 
| 1588 1627 | 
             
                    }
         | 
| 1589 1628 | 
             
                    return await self.request("GET", url, params=params)
         | 
| 1590 1629 |  | 
| @@ -1601,38 +1640,92 @@ class Client(BaseHTTPClient): | |
| 1601 1640 | 
             
                    guest_token = re.search(r"gt\s?=\s?\d+", response.text)[0].split("=")[1]
         | 
| 1602 1641 | 
             
                    return guest_token
         | 
| 1603 1642 |  | 
| 1604 | 
            -
                async def _login(self):
         | 
| 1643 | 
            +
                async def _login(self) -> bool:
         | 
| 1644 | 
            +
                    update_backup_code = False
         | 
| 1645 | 
            +
             | 
| 1605 1646 | 
             
                    guest_token = await self._request_guest_token()
         | 
| 1606 1647 | 
             
                    self._session.headers["X-Guest-Token"] = guest_token
         | 
| 1607 1648 |  | 
| 1608 | 
            -
                    # Можно не устанавливать, так как твиттер сам вернет этот токен
         | 
| 1609 | 
            -
                    # self._session.cookies["gt"] = guest_token
         | 
| 1610 | 
            -
             | 
| 1611 1649 | 
             
                    flow_token, subtasks = await self._request_login_tasks()
         | 
| 1612 1650 | 
             
                    for _ in range(2):
         | 
| 1613 1651 | 
             
                        flow_token, subtasks = await self._login_enter_user_identifier(flow_token)
         | 
| 1614 1652 |  | 
| 1615 | 
            -
                    subtask_ids =  | 
| 1616 | 
            -
             | 
| 1653 | 
            +
                    subtask_ids = {subtask.id for subtask in subtasks}
         | 
| 1617 1654 | 
             
                    if "LoginEnterAlternateIdentifierSubtask" in subtask_ids:
         | 
| 1618 1655 | 
             
                        if not self.account.username:
         | 
| 1619 | 
            -
                            raise  | 
| 1656 | 
            +
                            raise TwitterException("Failed to login: no username to relogin")
         | 
| 1620 1657 |  | 
| 1621 1658 | 
             
                    flow_token, subtasks = await self._login_enter_password(flow_token)
         | 
| 1622 1659 | 
             
                    flow_token, subtasks = await self._account_duplication_check(flow_token)
         | 
| 1623 1660 |  | 
| 1624 | 
            -
                    subtask_ids =  | 
| 1625 | 
            -
             | 
| 1626 | 
            -
                    # TODO IMAP Обработчик
         | 
| 1661 | 
            +
                    subtask_ids = {subtask.id for subtask in subtasks}
         | 
| 1627 1662 | 
             
                    if "LoginAcid" in subtask_ids:
         | 
| 1628 | 
            -
                         | 
| 1663 | 
            +
                        if not self.account.email:
         | 
| 1664 | 
            +
                            raise TwitterException(
         | 
| 1665 | 
            +
                                f"Failed to login. Task id: LoginAcid. No email!"
         | 
| 1666 | 
            +
                            )
         | 
| 1667 | 
            +
             | 
| 1668 | 
            +
                        try:
         | 
| 1669 | 
            +
                            # fmt: off
         | 
| 1670 | 
            +
                            flow_token, subtasks = await self._login_acid(flow_token, self.account.email)
         | 
| 1671 | 
            +
                            # fmt: on
         | 
| 1672 | 
            +
                        except HTTPException as exc:
         | 
| 1673 | 
            +
                            if 399 in exc.api_codes:
         | 
| 1674 | 
            +
                                logger.warning(
         | 
| 1675 | 
            +
                                    f"(auth_token={self.account.hidden_auth_token}, id={self.account.id}, username={self.account.username})"
         | 
| 1676 | 
            +
                                    f" Bad email!"
         | 
| 1677 | 
            +
                                )
         | 
| 1678 | 
            +
                                raise TwitterException(
         | 
| 1679 | 
            +
                                    f"Failed to login. Task id: LoginAcid. Bad email!"
         | 
| 1680 | 
            +
                                )
         | 
| 1681 | 
            +
                            else:
         | 
| 1682 | 
            +
                                raise
         | 
| 1629 1683 |  | 
| 1630 1684 | 
             
                    if "LoginTwoFactorAuthChallenge" in subtask_ids:
         | 
| 1631 | 
            -
                         | 
| 1632 | 
            -
                             | 
| 1633 | 
            -
             | 
| 1685 | 
            +
                        if not self.account.totp_secret:
         | 
| 1686 | 
            +
                            raise TwitterException(
         | 
| 1687 | 
            +
                                f"Failed to login. Task id: LoginTwoFactorAuthChallenge. No totp_secret!"
         | 
| 1688 | 
            +
                            )
         | 
| 1634 1689 |  | 
| 1635 | 
            -
             | 
| 1690 | 
            +
                        try:
         | 
| 1691 | 
            +
                            # fmt: off
         | 
| 1692 | 
            +
                            flow_token, subtasks = await self._login_two_factor_auth_challenge(flow_token, self.account.get_totp_code())
         | 
| 1693 | 
            +
                            # fmt: on
         | 
| 1694 | 
            +
                        except HTTPException as exc:
         | 
| 1695 | 
            +
                            if 399 in exc.api_codes:
         | 
| 1696 | 
            +
                                logger.warning(
         | 
| 1697 | 
            +
                                    f"(auth_token={self.account.hidden_auth_token}, id={self.account.id}, username={self.account.username})"
         | 
| 1698 | 
            +
                                    f" Bad TOTP secret!"
         | 
| 1699 | 
            +
                                )
         | 
| 1700 | 
            +
                                if not self.account.backup_code:
         | 
| 1701 | 
            +
                                    raise TwitterException(
         | 
| 1702 | 
            +
                                        f"Failed to login. Task id: LoginTwoFactorAuthChallenge. No backup code!"
         | 
| 1703 | 
            +
                                    )
         | 
| 1704 | 
            +
             | 
| 1705 | 
            +
                                # Enter backup code
         | 
| 1706 | 
            +
                                # fmt: off
         | 
| 1707 | 
            +
                                flow_token, subtasks = await self._login_two_factor_auth_choose_method(flow_token)
         | 
| 1708 | 
            +
                                try:
         | 
| 1709 | 
            +
                                    flow_token, subtasks = await self._login_two_factor_auth_challenge(flow_token, self.account.backup_code)
         | 
| 1710 | 
            +
                                except HTTPException as exc:
         | 
| 1711 | 
            +
                                    if 399 in exc.api_codes:
         | 
| 1712 | 
            +
                                        logger.warning(
         | 
| 1713 | 
            +
                                            f"(auth_token={self.account.hidden_auth_token}, id={self.account.id}, username={self.account.username})"
         | 
| 1714 | 
            +
                                            f" Bad backup code!"
         | 
| 1715 | 
            +
                                        )
         | 
| 1716 | 
            +
                                        raise TwitterException(
         | 
| 1717 | 
            +
                                            f"Failed to login. Task id: LoginTwoFactorAuthChallenge. Bad backup_code!"
         | 
| 1718 | 
            +
                                        )
         | 
| 1719 | 
            +
                                    else:
         | 
| 1720 | 
            +
                                        raise
         | 
| 1721 | 
            +
             | 
| 1722 | 
            +
                                update_backup_code = True
         | 
| 1723 | 
            +
                                # fmt: on
         | 
| 1724 | 
            +
                            else:
         | 
| 1725 | 
            +
                                raise
         | 
| 1726 | 
            +
             | 
| 1727 | 
            +
                    await self._complete_subtask(flow_token, [])
         | 
| 1728 | 
            +
                    return update_backup_code
         | 
| 1636 1729 |  | 
| 1637 1730 | 
             
                async def relogin(self):
         | 
| 1638 1731 | 
             
                    """
         | 
| @@ -1647,8 +1740,17 @@ class Client(BaseHTTPClient): | |
| 1647 1740 | 
             
                    if not self.account.password:
         | 
| 1648 1741 | 
             
                        raise ValueError("No password")
         | 
| 1649 1742 |  | 
| 1650 | 
            -
                    await self._login()
         | 
| 1743 | 
            +
                    update_backup_code = await self._login()
         | 
| 1651 1744 | 
             
                    await self._viewer()
         | 
| 1745 | 
            +
             | 
| 1746 | 
            +
                    if update_backup_code:
         | 
| 1747 | 
            +
                        await self.update_backup_code()
         | 
| 1748 | 
            +
                        logger.warning(
         | 
| 1749 | 
            +
                            f"(auth_token={self.account.hidden_auth_token}, id={self.account.id}, username={self.account.username})"
         | 
| 1750 | 
            +
                            f" Requested new backup code!"
         | 
| 1751 | 
            +
                        )
         | 
| 1752 | 
            +
                        # TODO Также обновлять totp_secret
         | 
| 1753 | 
            +
             | 
| 1652 1754 | 
             
                    await self.establish_status()
         | 
| 1653 1755 |  | 
| 1654 1756 | 
             
                async def login(self):
         | 
| @@ -1665,13 +1767,13 @@ class Client(BaseHTTPClient): | |
| 1665 1767 |  | 
| 1666 1768 | 
             
                    url = f"https://twitter.com/i/api/1.1/strato/column/User/{self.account.id}/account-security/twoFactorAuthSettings2"
         | 
| 1667 1769 | 
             
                    response, data = await self.request("GET", url)
         | 
| 1668 | 
            -
                     | 
| 1669 | 
            -
             | 
| 1670 | 
            -
                     | 
| 1770 | 
            +
                    # fmt: off
         | 
| 1771 | 
            +
                    return "Totp" in [method_data["twoFactorType"] for method_data in data["methods"]]
         | 
| 1772 | 
            +
                    # fmt: on
         | 
| 1671 1773 |  | 
| 1672 | 
            -
                async def _request_2fa_tasks(self):
         | 
| 1774 | 
            +
                async def _request_2fa_tasks(self) -> tuple[str, list[Subtask]]:
         | 
| 1673 1775 | 
             
                    """
         | 
| 1674 | 
            -
                    :return: flow_token,  | 
| 1776 | 
            +
                    :return: flow_token, tasks
         | 
| 1675 1777 | 
             
                    """
         | 
| 1676 1778 | 
             
                    query = {
         | 
| 1677 1779 | 
             
                        "flow_name": "two-factor-auth-app-enrollment",
         | 
| @@ -1727,34 +1829,37 @@ class Client(BaseHTTPClient): | |
| 1727 1829 | 
             
                            "web_modal": 1,
         | 
| 1728 1830 | 
             
                        },
         | 
| 1729 1831 | 
             
                    }
         | 
| 1730 | 
            -
                    return await self. | 
| 1832 | 
            +
                    return await self._send_raw_subtask(params=query, json=payload)
         | 
| 1731 1833 |  | 
| 1732 | 
            -
                async def _two_factor_enrollment_verify_password_subtask( | 
| 1733 | 
            -
                     | 
| 1834 | 
            +
                async def _two_factor_enrollment_verify_password_subtask(
         | 
| 1835 | 
            +
                    self, flow_token: str
         | 
| 1836 | 
            +
                ) -> tuple[str, list[Subtask]]:
         | 
| 1837 | 
            +
                    inputs = [
         | 
| 1734 1838 | 
             
                        {
         | 
| 1735 1839 | 
             
                            "subtask_id": "TwoFactorEnrollmentVerifyPasswordSubtask",
         | 
| 1736 1840 | 
             
                            "enter_password": {
         | 
| 1737 | 
            -
                                "password": self.account.password,
         | 
| 1738 1841 | 
             
                                "link": "next_link",
         | 
| 1842 | 
            +
                                "password": self.account.password,
         | 
| 1739 1843 | 
             
                            },
         | 
| 1740 1844 | 
             
                        }
         | 
| 1741 1845 | 
             
                    ]
         | 
| 1742 | 
            -
                    return await self. | 
| 1846 | 
            +
                    return await self._complete_subtask(flow_token, inputs)
         | 
| 1743 1847 |  | 
| 1744 1848 | 
             
                async def _two_factor_enrollment_authentication_app_begin_subtask(
         | 
| 1745 1849 | 
             
                    self, flow_token: str
         | 
| 1746 | 
            -
                ):
         | 
| 1747 | 
            -
                     | 
| 1850 | 
            +
                ) -> tuple[str, list[Subtask]]:
         | 
| 1851 | 
            +
                    inputs = [
         | 
| 1748 1852 | 
             
                        {
         | 
| 1749 1853 | 
             
                            "subtask_id": "TwoFactorEnrollmentAuthenticationAppBeginSubtask",
         | 
| 1750 1854 | 
             
                            "action_list": {"link": "next_link"},
         | 
| 1751 1855 | 
             
                        }
         | 
| 1752 1856 | 
             
                    ]
         | 
| 1753 | 
            -
                    return await self. | 
| 1857 | 
            +
                    return await self._complete_subtask(flow_token, inputs)
         | 
| 1754 1858 |  | 
| 1755 1859 | 
             
                async def _two_factor_enrollment_authentication_app_plain_code_subtask(
         | 
| 1756 | 
            -
                    self, | 
| 1757 | 
            -
             | 
| 1860 | 
            +
                    self,
         | 
| 1861 | 
            +
                    flow_token: str,
         | 
| 1862 | 
            +
                ) -> tuple[str, list[Subtask]]:
         | 
| 1758 1863 | 
             
                    subtask_inputs = [
         | 
| 1759 1864 | 
             
                        {
         | 
| 1760 1865 | 
             
                            "subtask_id": "TwoFactorEnrollmentAuthenticationAppPlainCodeSubtask",
         | 
| @@ -1763,12 +1868,12 @@ class Client(BaseHTTPClient): | |
| 1763 1868 | 
             
                        {
         | 
| 1764 1869 | 
             
                            "subtask_id": "TwoFactorEnrollmentAuthenticationAppEnterCodeSubtask",
         | 
| 1765 1870 | 
             
                            "enter_text": {
         | 
| 1766 | 
            -
                                "text": self.account.get_totp_code(),
         | 
| 1767 1871 | 
             
                                "link": "next_link",
         | 
| 1872 | 
            +
                                "text": self.account.get_totp_code(),
         | 
| 1768 1873 | 
             
                            },
         | 
| 1769 1874 | 
             
                        },
         | 
| 1770 1875 | 
             
                    ]
         | 
| 1771 | 
            -
                    return await self. | 
| 1876 | 
            +
                    return await self._complete_subtask(flow_token, subtask_inputs)
         | 
| 1772 1877 |  | 
| 1773 1878 | 
             
                async def _finish_2fa_task(self, flow_token: str):
         | 
| 1774 1879 | 
             
                    subtask_inputs = [
         | 
| @@ -1777,52 +1882,38 @@ class Client(BaseHTTPClient): | |
| 1777 1882 | 
             
                            "cta": {"link": "finish_link"},
         | 
| 1778 1883 | 
             
                        }
         | 
| 1779 1884 | 
             
                    ]
         | 
| 1780 | 
            -
                     | 
| 1885 | 
            +
                    await self._complete_subtask(flow_token, subtask_inputs)
         | 
| 1781 1886 |  | 
| 1782 1887 | 
             
                async def _enable_totp(self):
         | 
| 1888 | 
            +
                    # fmt: off
         | 
| 1783 1889 | 
             
                    flow_token, subtasks = await self._request_2fa_tasks()
         | 
| 1784 | 
            -
                    flow_token, subtasks = (
         | 
| 1785 | 
            -
                         | 
| 1786 | 
            -
                    )
         | 
| 1787 | 
            -
                    flow_token, subtasks = (
         | 
| 1788 | 
            -
                        await self._two_factor_enrollment_authentication_app_begin_subtask(
         | 
| 1789 | 
            -
                            flow_token
         | 
| 1790 | 
            -
                        )
         | 
| 1890 | 
            +
                    flow_token, subtasks = await self._two_factor_enrollment_verify_password_subtask(
         | 
| 1891 | 
            +
                        flow_token
         | 
| 1791 1892 | 
             
                    )
         | 
| 1893 | 
            +
                    flow_token, subtasks = (await self._two_factor_enrollment_authentication_app_begin_subtask(flow_token))
         | 
| 1792 1894 |  | 
| 1793 1895 | 
             
                    for subtask in subtasks:
         | 
| 1794 | 
            -
                        if  | 
| 1795 | 
            -
                            subtask[" | 
| 1796 | 
            -
                            == "TwoFactorEnrollmentAuthenticationAppPlainCodeSubtask"
         | 
| 1797 | 
            -
                        ):
         | 
| 1798 | 
            -
                            self.account.totp_secret = subtask["show_code"]["code"]
         | 
| 1896 | 
            +
                        if subtask.id == "TwoFactorEnrollmentAuthenticationAppPlainCodeSubtask":
         | 
| 1897 | 
            +
                            self.account.totp_secret = subtask.raw_data["show_code"]["code"]
         | 
| 1799 1898 | 
             
                            break
         | 
| 1800 1899 |  | 
| 1801 | 
            -
                    flow_token, subtasks = (
         | 
| 1802 | 
            -
                        await self._two_factor_enrollment_authentication_app_plain_code_subtask(
         | 
| 1803 | 
            -
                            flow_token
         | 
| 1804 | 
            -
                        )
         | 
| 1805 | 
            -
                    )
         | 
| 1900 | 
            +
                    flow_token, subtasks = await self._two_factor_enrollment_authentication_app_plain_code_subtask(flow_token)
         | 
| 1806 1901 |  | 
| 1807 1902 | 
             
                    for subtask in subtasks:
         | 
| 1808 | 
            -
                        if  | 
| 1809 | 
            -
                            subtask[" | 
| 1810 | 
            -
                            == "TwoFactorEnrollmentAuthenticationAppCompleteSubtask"
         | 
| 1811 | 
            -
                        ):
         | 
| 1812 | 
            -
                            result = re.search(
         | 
| 1813 | 
            -
                                r"\n[a-z0-9]{12}\n", subtask["cta"]["secondary_text"]["text"]
         | 
| 1814 | 
            -
                            )
         | 
| 1903 | 
            +
                        if subtask.id == "TwoFactorEnrollmentAuthenticationAppCompleteSubtask":
         | 
| 1904 | 
            +
                            result = re.search(r"\n[a-z0-9]{12}\n", subtask.raw_data["cta"]["secondary_text"]["text"])
         | 
| 1815 1905 | 
             
                            backup_code = result[0].strip() if result else None
         | 
| 1816 1906 | 
             
                            self.account.backup_code = backup_code
         | 
| 1817 1907 | 
             
                            break
         | 
| 1818 1908 |  | 
| 1909 | 
            +
                    # fmt: on
         | 
| 1819 1910 | 
             
                    await self._finish_2fa_task(flow_token)
         | 
| 1820 1911 |  | 
| 1821 1912 | 
             
                async def enable_totp(self):
         | 
| 1822 | 
            -
                    if not self.account.password:
         | 
| 1823 | 
            -
                        raise ValueError("Password is required for this action")
         | 
| 1824 | 
            -
             | 
| 1825 1913 | 
             
                    if await self.totp_is_enabled():
         | 
| 1826 1914 | 
             
                        return
         | 
| 1827 1915 |  | 
| 1916 | 
            +
                    if not self.account.password:
         | 
| 1917 | 
            +
                        raise ValueError("Password required to enable TOTP")
         | 
| 1918 | 
            +
             | 
| 1828 1919 | 
             
                    await self._enable_totp()
         | 
    
        twitter/models.py
    CHANGED
    
    | @@ -1,4 +1,4 @@ | |
| 1 | 
            -
            from typing import Optional
         | 
| 1 | 
            +
            from typing import Optional, Any
         | 
| 2 2 | 
             
            from datetime import datetime, timedelta
         | 
| 3 3 |  | 
| 4 4 | 
             
            from pydantic import BaseModel, Field, field_validator
         | 
| @@ -153,3 +153,24 @@ class Tweet(BaseModel): | |
| 153 153 | 
             
                        "raw_data": data,
         | 
| 154 154 | 
             
                    }
         | 
| 155 155 | 
             
                    return cls(**values)
         | 
| 156 | 
            +
             | 
| 157 | 
            +
             | 
| 158 | 
            +
            class Subtask(BaseModel):
         | 
| 159 | 
            +
                id: str
         | 
| 160 | 
            +
                primary_text: Optional[str] = None
         | 
| 161 | 
            +
                secondary_text: Optional[str] = None
         | 
| 162 | 
            +
                detail_text: Optional[str] = None
         | 
| 163 | 
            +
                raw_data: dict
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                @classmethod
         | 
| 166 | 
            +
                def from_raw_data(cls, data: dict) -> "Subtask":
         | 
| 167 | 
            +
                    task = {"id": data["subtask_id"]}
         | 
| 168 | 
            +
                    if enter_text := data.get("enter_text"):
         | 
| 169 | 
            +
                        if header := enter_text.get("header"):
         | 
| 170 | 
            +
                            if primary_text := header.get("primary_text"):
         | 
| 171 | 
            +
                                task["primary_text"] = primary_text["text"]
         | 
| 172 | 
            +
                            if secondary_text := header.get("secondary_text"):
         | 
| 173 | 
            +
                                task["secondary_text"] = secondary_text["text"]
         | 
| 174 | 
            +
                            if detail_text := header.get("detail_text"):
         | 
| 175 | 
            +
                                task["detail_text"] = detail_text["text"]
         | 
| 176 | 
            +
                    return cls(**task, raw_data=data)
         | 
| 
            File without changes
         |