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.
@@ -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)