tweepy-self 1.10.0b6__tar.gz → 1.10.0b8__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/PKG-INFO +1 -1
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/pyproject.toml +1 -1
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/client.py +203 -112
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/models.py +22 -1
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/README.md +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/__init__.py +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/_capsolver/__init__.py +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/_capsolver/core/__init__.py +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/_capsolver/core/base.py +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/_capsolver/core/config.py +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/_capsolver/core/enum.py +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/_capsolver/core/serializer.py +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/_capsolver/fun_captcha.py +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/account.py +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/base/__init__.py +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/base/client.py +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/base/session.py +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/enums.py +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/errors.py +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/utils/__init__.py +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/utils/file.py +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/utils/html.py +0 -0
- {tweepy_self-1.10.0b6 → tweepy_self-1.10.0b8}/twitter/utils/other.py +0 -0
@@ -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()
|
@@ -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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|