TitleInfo.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. # -*- coding: utf-8 -*-
  2. import urllib.request, urllib.error, urllib.parse
  3. import xml.etree.ElementTree as ET
  4. from binascii import hexlify, unhexlify
  5. from Crypto.Cipher import AES
  6. from PIL import Image
  7. from IconManager import IconManager
  8. from datetime import datetime, timezone
  9. import sys, logging, struct, hashlib, math, unicodedata, json
  10. import common
  11. class TitleInfo:
  12. def __init__(self, id, uid = None):
  13. self.id = id.upper()
  14. self.uid = uid
  15. self.name = None
  16. self.product_code = None
  17. self.regions = 0
  18. self.country_code = None
  19. self.features = []
  20. self.feature_list = {}
  21. self.fetch_data()
  22. def take_my_flist(self):
  23. return self.feature_list
  24. @staticmethod
  25. def get_id_pairs(id_list, get_content_id = True):
  26. ret = [None] * len(id_list)
  27. from_key = 'title_id' if get_content_id else 'ns_uid'
  28. to_key = 'title_id' if not get_content_id else 'ns_uid'
  29. # URI length is limited, so need to break up large requests
  30. limit = 40
  31. if len(id_list) > limit:
  32. ret = []
  33. ret += TitleInfo.get_id_pairs(id_list[:limit], get_content_id)
  34. ret += TitleInfo.get_id_pairs(id_list[limit:], get_content_id)
  35. else:
  36. try:
  37. shop_request = urllib.request.Request(common.ninja_url + "titles/id_pair?{}[]=".format(from_key) + ','.join(id_list))
  38. shop_request.get_method = lambda: 'GET'
  39. response = urllib.request.urlopen(shop_request, context=common.ctr_context)
  40. xml = ET.fromstring(response.read().decode('UTF-8', 'replace'))
  41. for el in xml.findall('*/title_id_pair'):
  42. index = id_list.index(el.find(from_key).text)
  43. ret[index] = el.find(to_key).text
  44. except urllib.error.URLError as e:
  45. self.logger.error(e)
  46. return ret;
  47. def try_regions(self, region_list, try_all):
  48. title_response = None
  49. for code in region_list:
  50. try:
  51. if self.country_code and (code in common.region_euro_array) and (self.regions & common.region_map['EU']):
  52. continue
  53. title_request = urllib.request.Request(common.samurai_url + code + '/title/' + self.uid + '/?shop_id=1')
  54. title_response = urllib.request.urlopen(title_request, context=common.ctr_context)
  55. if not self.country_code:
  56. self.country_code = code
  57. except urllib.error.URLError as e:
  58. pass
  59. else:
  60. if code in common.region_euro_array:
  61. self.regions |= common.region_map['EU']
  62. elif code in common.region_map:
  63. self.regions |= common.region_map[code]
  64. if not try_all:
  65. break
  66. return title_response
  67. def fetch_data(self):
  68. if self.regions:
  69. if self.regions == common.region_map['US']:
  70. self.country_code = 'US'
  71. self.name = None # Use the title provided by samurai instead of icon server
  72. elif self.regions == common.region_map['JP']:
  73. self.country_code = 'JP'
  74. elif self.regions & common.region_map['EU']:
  75. if not self.regions & common.region_map['US']:
  76. title_response = self.try_regions(common.region_euro_array, False)
  77. else:
  78. # self.country_code = 'GB'
  79. title_response = self.try_regions(["GB","JP"], False)
  80. self.name = None
  81. elif self.regions & common.region_map['JP']:
  82. self.country_code = 'JP'
  83. elif self.regions & common.region_map['KO']:
  84. self.country_code = 'KR'
  85. elif self.regions & common.region_map['CN']:
  86. self.country_code = 'HK'
  87. elif self.regions & common.region_map['TW']:
  88. self.country_code = 'TW'
  89. else:
  90. self.logger.error("Region value {} for {}?".format(self.regions, self.id))
  91. return
  92. if self.country_code and not title_response:
  93. try:
  94. title_request = urllib.request.Request(common.samurai_url + self.country_code + '/title/' + self.uid + '/?shop_id=1')
  95. title_response = urllib.request.urlopen(title_request, context=common.ctr_context)
  96. except urllib.error.HTTPError:
  97. print(common.samurai_url + self.country_code + '/title/' + self.uid + '/?shop_id=1')
  98. else:
  99. # If all else fails, try all regions to see which the title is from
  100. self.regions = 0
  101. title_response = self.try_regions(common.region_array, True)
  102. if not self.regions or not title_response:
  103. raise ValueError("No region or country code for {}".format(self.id))
  104. # Use JP region for later timezone for pre-releases (this was added for Pokemon S/M lol)
  105. ec_country_code = 'JP' if (self.regions & common.region_map['JP']) else self.country_code
  106. ec_response = urllib.request.urlopen(common.ninja_url + ec_country_code + '/title/' + self.uid + '/ec_info', context=common.ctr_context)
  107. xml = ET.fromstring(title_response.read().decode('UTF-8', 'replace'))
  108. self.product_code = xml.find("*/product_code").text
  109. if not self.name:
  110. self.name = xml.find("*/name").text.replace('\n', ' ').strip()
  111. # Get features
  112. features = xml.findall(".//feature")
  113. if features:
  114. for feature in list(features):
  115. self.features.append(int(feature.find("id").text))
  116. if self.country_code == "EU" or self.country_code == "US" or self.country_code == "GB":
  117. self.feature_list[int(feature.find("id").text)] = feature.find("name").text
  118. print("Feature #{}: {}".format(feature.find("id").text, feature.find("name").text))