account.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. # mautrix-instagram - A Matrix-Instagram puppeting bridge.
  2. # Copyright (C) 2022 Tulir Asokan
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU Affero General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. from __future__ import annotations
  17. from typing import Type, TypeVar
  18. import json
  19. from ..types import CurrentUserResponse
  20. from .base import BaseAndroidAPI
  21. T = TypeVar("T")
  22. class AccountAPI(BaseAndroidAPI):
  23. async def current_user(self) -> CurrentUserResponse:
  24. return await self.std_http_get(
  25. f"/api/v1/accounts/current_user/",
  26. query={"edit": "true"},
  27. response_type=CurrentUserResponse,
  28. )
  29. async def set_biography(self, text: str) -> CurrentUserResponse:
  30. # TODO entities?
  31. return await self.__command("set_biography", device_id=self.state.device.id, raw_text=text)
  32. async def set_profile_picture(self, upload_id: str) -> CurrentUserResponse:
  33. return await self.__command(
  34. "change_profile_picture", use_fbuploader="true", upload_id=upload_id
  35. )
  36. async def remove_profile_picture(self) -> CurrentUserResponse:
  37. return await self.__command("remove_profile_picture")
  38. async def set_private(self, private: bool) -> CurrentUserResponse:
  39. return await self.__command("set_private" if private else "set_public")
  40. async def confirm_email(self, slug: str) -> CurrentUserResponse:
  41. # slug can contain slashes, but it shouldn't start or end with one
  42. return await self.__command(f"confirm_email/{slug}")
  43. async def send_recovery_flow_email(self, query: str):
  44. req = {
  45. "_csrftoken": self.state.cookies.csrf_token,
  46. "adid": "",
  47. "guid": self.state.device.uuid,
  48. "device_id": self.state.device.id,
  49. "query": query,
  50. }
  51. # TODO parse response content
  52. return await self.std_http_post(f"/api/v1/accounts/send_recovery_flow_email/", data=req)
  53. async def edit_profile(
  54. self,
  55. external_url: str | None = None,
  56. gender: str | None = None,
  57. phone_number: str | None = None,
  58. username: str | None = None,
  59. # TODO should there be a last_name?
  60. first_name: str | None = None,
  61. biography: str | None = None,
  62. email: str | None = None,
  63. ) -> CurrentUserResponse:
  64. return await self.__command(
  65. "edit_profile",
  66. device_id=self.state.device.id,
  67. email=email,
  68. external_url=external_url,
  69. first_name=first_name,
  70. username=username,
  71. phone_number=phone_number,
  72. gender=gender,
  73. biography=biography,
  74. )
  75. async def __command(
  76. self, command: str, response_type: Type[T] = CurrentUserResponse, **kwargs: str
  77. ) -> T:
  78. req = {
  79. "_csrftoken": self.state.cookies.csrf_token,
  80. "_uid": self.state.cookies.user_id,
  81. "_uuid": self.state.device.uuid,
  82. **kwargs,
  83. }
  84. return await self.std_http_post(
  85. f"/api/v1/accounts/{command}/",
  86. data=req,
  87. filter_nulls=True,
  88. response_type=response_type,
  89. )
  90. async def read_msisdn_header(self, usage: str = "default"):
  91. req = {
  92. "mobile_subno_usage": usage,
  93. "device_id": self.state.device.uuid,
  94. }
  95. return await self.std_http_post("/api/v1/accounts/read_msisdn_header/", data=req)
  96. async def msisdn_header_bootstrap(self, usage: str = "default"):
  97. req = {
  98. "mobile_subno_usage": usage,
  99. "device_id": self.state.device.uuid,
  100. }
  101. return await self.std_http_post("/api/v1/accounts/msisdn_header_bootstrap/", data=req)
  102. async def contact_point_prefill(self, usage: str = "default"):
  103. req = {
  104. "mobile_subno_usage": usage,
  105. "device_id": self.state.device.uuid,
  106. }
  107. return await self.std_http_post("/api/v1/accounts/contact_point_prefill/", data=req)
  108. async def get_prefill_candidates(self):
  109. req = {
  110. "android_device_id": self.state.device.id,
  111. "usages": json.dumps(["account_recovery_omnibox"]),
  112. "device_id": self.state.device.uuid,
  113. }
  114. # TODO parse response content
  115. return await self.std_http_post("/api/v1/accounts/get_prefill_candidates/", data=req)
  116. async def process_contact_point_signals(self):
  117. req = {
  118. "phone_id": self.state.device.phone_id,
  119. "_csrftoken": self.state.cookies.csrf_token,
  120. "_uid": self.state.cookies.user_id,
  121. "device_id": self.state.device.uuid,
  122. "_uuid": self.state.device.uuid,
  123. "google_tokens": json.dumps([]),
  124. }
  125. return await self.std_http_post(
  126. "/api/v1/accounts/process_contact_point_signals/", data=req
  127. )