tweepy-self 1.10.0b6__py3-none-any.whl → 1.10.0b8__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tweepy-self
3
- Version: 1.10.0b6
3
+ Version: 1.10.0b8
4
4
  Summary: Twitter (selfbot) for Python!
5
5
  Home-page: https://github.com/alenkimov/tweepy-self
6
6
  Author: Alen
@@ -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=zcW31OYf4Y9NvfgH2V5rnmdHXSa6tRFdxVuKvy_JyEw,70579
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=7yObMPUUEwJEbraHzFwmUKd91UhR2-zyfJTm4xIqrSQ,4834
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.0b6.dist-info/METADATA,sha256=9RkGszuiywuEgKLozQNVo_usGEgJ3ismzwnsVGqZkc4,13153
22
- tweepy_self-1.10.0b6.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
23
- tweepy_self-1.10.0b6.dist-info/RECORD,,
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} Failed to solve funcaptcha:"
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 _task(self, **kwargs):
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, subtasks
1433
+ :return: flow_token and subtasks
1428
1434
  """
1429
1435
  url = "https://api.twitter.com/1.1/onboarding/task.json"
1430
- response, response_json = await self.request("POST", url, **kwargs)
1431
- return response_json["flow_token"], response_json["subtasks"]
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 _request_login_tasks(self):
1434
- """
1435
- :return: flow_token, subtask_ids
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._task(params=params, json=payload, auth=False)
1525
+ return await self._send_raw_subtask(params=params, json=payload, auth=False)
1492
1526
 
1493
- async def _send_task(self, flow_token: str, subtask_inputs: list[dict], **kwargs):
1494
- payload = kwargs["json"] = kwargs.get("json") or {}
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._send_task(flow_token, subtask_inputs, auth=False)
1547
+ return await self._complete_subtask(flow_token, inputs, auth=False)
1531
1548
 
1532
- async def _login_enter_password(self, flow_token):
1533
- subtask_inputs = [
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._send_task(flow_token, subtask_inputs, auth=False)
1559
+ return await self._complete_subtask(flow_token, inputs, auth=False)
1543
1560
 
1544
1561
  async def _account_duplication_check(self, flow_token):
1545
- subtask_inputs = [
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._send_task(flow_token, subtask_inputs, auth=False)
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
- subtask_inputs = [
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._send_task(flow_token, subtask_inputs, auth=False)
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": to_json(features),
1586
- "fieldToggles": to_json(field_toggles),
1587
- "variables": to_json(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 = [subtask["subtask_id"] for subtask in subtasks]
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 ValueError("No username to relogin")
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 = [subtask["subtask_id"] for subtask in subtasks]
1625
-
1626
- # TODO IMAP Обработчик
1661
+ subtask_ids = {subtask.id for subtask in subtasks}
1627
1662
  if "LoginAcid" in subtask_ids:
1628
- raise TwitterException(f"Failed to login: email verification!")
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
- flow_token, subtasks = await self._login_two_factor_auth_challenge(
1632
- flow_token
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
- await self._finish_task(flow_token)
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
- return "Totp" in [
1669
- method_data["twoFactorType"] for method_data in data["methods"]
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, subtask_ids
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._task(params=query, json=payload)
1832
+ return await self._send_raw_subtask(params=query, json=payload)
1731
1833
 
1732
- async def _two_factor_enrollment_verify_password_subtask(self, flow_token: str):
1733
- subtask_inputs = [
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._send_task(flow_token, subtask_inputs)
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
- subtask_inputs = [
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._send_task(flow_token, subtask_inputs)
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, flow_token: str
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._send_task(flow_token, subtask_inputs)
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
- return await self._send_task(flow_token, subtask_inputs)
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
- await self._two_factor_enrollment_verify_password_subtask(flow_token)
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["subtask_id"]
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["subtask_id"]
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)