__init__.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import datetime
  2. import json
  3. import os
  4. import ssl
  5. import subprocess
  6. import sys
  7. import urllib.parse
  8. import urllib.request
  9. """
  10. official api documentation:
  11. https://github.com/ToontownRewritten/api-doc/blob/master/login.md
  12. https://github.com/ToontownRewritten/api-doc/blob/master/invasions.md
  13. """
  14. INVASIONS_API_URL = 'https://www.toontownrewritten.com/api/invasions?format=json'
  15. LOGIN_API_URL = 'https://www.toontownrewritten.com/api/login?format=json'
  16. if sys.platform == 'darwin':
  17. TOONTOWN_LIBRARY_PATH = os.path.join(
  18. os.path.expanduser('~'), 'Library',
  19. 'Application Support', 'Toontown Rewritten',
  20. )
  21. TOONTOWN_ENGINE_DEFAULT_PATH = os.path.join(
  22. TOONTOWN_LIBRARY_PATH,
  23. 'Toontown Rewritten',
  24. )
  25. else:
  26. TOONTOWN_LIBRARY_PATH = None
  27. TOONTOWN_ENGINE_DEFAULT_PATH = None
  28. def start_engine(engine_path, gameserver, playcookie, **kwargs):
  29. env = {
  30. 'TTR_GAMESERVER': gameserver,
  31. 'TTR_PLAYCOOKIE': playcookie,
  32. }
  33. if sys.platform == 'darwin':
  34. env['DYLD_LIBRARY_PATH'] = os.path.join(
  35. TOONTOWN_LIBRARY_PATH,
  36. 'Libraries.bundle',
  37. )
  38. env['DYLD_FRAMEWORK_PATH'] = os.path.join(
  39. TOONTOWN_LIBRARY_PATH,
  40. 'Frameworks',
  41. )
  42. return subprocess.Popen(
  43. args=[engine_path],
  44. cwd=os.path.dirname(engine_path),
  45. env=env,
  46. **kwargs,
  47. )
  48. def api_request(url, params=None, validate_ssl_cert=True):
  49. resp = urllib.request.urlopen(
  50. url=url,
  51. data=urllib.parse.urlencode(params).encode('ascii')
  52. if params else None,
  53. context=None if validate_ssl_cert
  54. else ssl._create_unverified_context(),
  55. )
  56. return json.loads(resp.read().decode('ascii'))
  57. class LoginSuccessful:
  58. def __init__(self, playcookie, gameserver):
  59. self.playcookie = playcookie
  60. self.gameserver = gameserver
  61. class LoginDelayed:
  62. def __init__(self, queue_token):
  63. self.queue_token = queue_token
  64. def login(username=None, password=None,
  65. queue_token=None, validate_ssl_cert=True):
  66. if username is not None and queue_token is None:
  67. assert password is not None
  68. req_params = {
  69. 'username': username,
  70. 'password': password,
  71. }
  72. elif username is None and queue_token is not None:
  73. req_params = {
  74. 'queueToken': queue_token,
  75. }
  76. else:
  77. raise Exception('either specify username or queue token')
  78. resp_data = api_request(
  79. url=LOGIN_API_URL,
  80. params=req_params,
  81. validate_ssl_cert=validate_ssl_cert,
  82. )
  83. if resp_data['success'] == 'true':
  84. return LoginSuccessful(
  85. playcookie=resp_data['cookie'],
  86. gameserver=resp_data['gameserver'],
  87. )
  88. elif resp_data['success'] == 'delayed':
  89. return LoginDelayed(
  90. queue_token=resp_data['queueToken'],
  91. )
  92. else:
  93. raise Exception(repr(resp_data))
  94. def launch(engine_path, username, password, validate_ssl_certs=True):
  95. result = login(
  96. username=username,
  97. password=password,
  98. validate_ssl_cert=validate_ssl_certs,
  99. )
  100. if isinstance(result, LoginDelayed):
  101. result = login(
  102. queue_token=result.queue_token,
  103. validate_ssl_cert=validate_ssl_certs,
  104. )
  105. if isinstance(result, LoginSuccessful):
  106. p = start_engine(
  107. engine_path=engine_path,
  108. gameserver=result.gameserver,
  109. playcookie=result.playcookie,
  110. )
  111. p.wait()
  112. else:
  113. raise Exception(repr(result))
  114. class InvasionProgress:
  115. def __init__(self, district, date, cog_type,
  116. despawned_number, total_number):
  117. self.district = district
  118. self.date = date
  119. self.cog_type = cog_type
  120. self.despawned_number = despawned_number
  121. self.total_number = total_number
  122. @property
  123. def remaining_number(self):
  124. return self.total_number - self.despawned_number
  125. def request_active_invasions(validate_ssl_certs=True):
  126. resp_data = api_request(INVASIONS_API_URL)
  127. if resp_data['error'] is not None:
  128. raise Exception(resp_data['error'])
  129. else:
  130. invs = {}
  131. for district, inv_data in resp_data['invasions'].items():
  132. despawned_number, total_number = inv_data['progress'].split('/')
  133. invs[district] = InvasionProgress(
  134. district=district,
  135. date=datetime.datetime.utcfromtimestamp(inv_data['asOf']),
  136. cog_type=inv_data['type'],
  137. despawned_number=int(despawned_number),
  138. total_number=int(total_number),
  139. )
  140. return invs