A RedBot cog for sending notifications when there are new founderless regions in NationStates.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

198 rivejä
7.7 KiB

  1. from redbot.core import commands, checks, Config
  2. from redbot.core.utils.chat_formatting import pagify
  3. import discord
  4. import asyncio
  5. import requests
  6. import xml.etree.cElementTree as et
  7. import time
  8. import datetime
  9. import gzip
  10. R_HEADERS = {'User-Agent': '<Nation: Haku>'}
  11. RATE_LIMIT = 0.7
  12. def canonicalize(in_str):
  13. return in_str.lower().replace(' ', '_')
  14. async def get_founderless_regions():
  15. """Return the list of founderless regions."""
  16. await asyncio.sleep(RATE_LIMIT)
  17. r = requests.get('https://www.nationstates.net/cgi-bin/api.cgi?q=regionsbytag;tags=founderless',
  18. headers=R_HEADERS)
  19. tree = et.fromstring(r.text)
  20. return [canonicalize(region) for region in tree[0].text.split(',')]
  21. async def download_region_dump():
  22. """Download the latest region dump from the NS API."""
  23. await asyncio.sleep(RATE_LIMIT)
  24. r = requests.get('https://www.nationstates.net/pages/regions.xml.gz',
  25. headers=R_HEADERS)
  26. with open('regions.xml.gz', 'wb') as f:
  27. f.write(r.content)
  28. with gzip.open('regions.xml.gz', 'rb') as f_in:
  29. with open('regions.xml', 'wb') as f_out:
  30. f_out.write(f_in.read())
  31. def get_region_endos(regions):
  32. """Using the region dump, return a Dict with the number of endos for each region."""
  33. tree = et.parse('regions.xml')
  34. endo_dict = dict()
  35. root = tree.getroot()
  36. for region in root:
  37. if region[0].text in regions:
  38. region_name = canonicalize(region[0].text)
  39. endo_dict[region_name] = int(region[5].text) - 1
  40. return endo_dict
  41. class FounderlessNotify(commands.Cog):
  42. """Notifies when a region changes in founderless status."""
  43. def __init__(self, bot, *args, **kwargs):
  44. super().__init__(*args, **kwargs)
  45. self.bot = bot
  46. self.config = Config.get_conf(self, identifier=82732344, force_registration=True)
  47. default_global_settings = {'update_time': 0, 'previous_founderless': [], 'notify_channel': 0}
  48. self.config.register_global(**default_global_settings)
  49. def cog_unload(self):
  50. pass
  51. # @commands.command()
  52. # @checks.is_owner()
  53. # async def is_task_running(self, ctx):
  54. # """Check if the main loop is running."""
  55. # if self.bg_loop_task:
  56. # await ctx.send('True')
  57. # else:
  58. # await ctx.send('False')
  59. # @commands.command()
  60. # @checks.is_owner()
  61. # async def start_task(self, ctx):
  62. # """Start the main loop if it is not running."""
  63. # if self.bg_loop_task:
  64. # await ctx.send('Task is already running')
  65. # else:
  66. # self.bg_loop_task = asyncio.create_task(self.bg_loop())
  67. @commands.command()
  68. @checks.is_owner()
  69. async def force_check(self, ctx, update_type):
  70. """Force perform a check in the differences of founderless regions."""
  71. if update_type.lower() == 'major' or update_type.lower() == 'minor':
  72. await self.update_founderless(update_type)
  73. else:
  74. return
  75. async def update_founderless(self, update_type):
  76. """Check for the differences in the founderless regions and then output that to a channel."""
  77. channel_id = await self.config.notify_channel()
  78. channel = self.bot.get_channel(channel_id)
  79. await channel.send(f'Beginning {update_type} check...')
  80. new_founderless_regions = await get_founderless_regions()
  81. previous_founderless_regions = await self.config.previous_founderless()
  82. # A region is now founderless if it is in the current list, but wasn't in the previous list
  83. now_founderless = list()
  84. for region in new_founderless_regions:
  85. if region in previous_founderless_regions:
  86. continue
  87. now_founderless.append(region)
  88. # Get the region endos for both lists
  89. now_founderless_endos = get_region_endos(now_founderless)
  90. # Create an ordered set with each region's name and delegate endorsement level
  91. out = list()
  92. for region in now_founderless:
  93. try:
  94. region_endos = now_founderless_endos[region]
  95. except KeyError:
  96. region_endos = 0
  97. out.append((region_endos, region))
  98. # Sort the output by delegate endos in descending order
  99. out.sort()
  100. out.reverse()
  101. # Prep output
  102. message_content = "The following regions are now **Founderless**:\n"
  103. for region in out:
  104. if region[0] == 0:
  105. message_content += f"https://www.nationstates.net/region={region[1]} (N/A)\n"
  106. else:
  107. message_content += f"https://www.nationstates.net/region={region[1]} ({region[0]})\n"
  108. for page in pagify(message_content):
  109. await channel.send(page)
  110. await self.config.previous_founderless.set(new_founderless_regions)
  111. async def bg_loop(self):
  112. """Main background loop."""
  113. while True:
  114. # Only check once every 5 minutes
  115. await asyncio.sleep(300)
  116. current_time = datetime.datetime.utcnow()
  117. major_time = await self.config.update_time()
  118. minor_time = major_time + 12
  119. # Major Update
  120. if current_time.hour == (major_time + 2):
  121. await download_region_dump()
  122. await self.update_founderless('major')
  123. await asyncio.sleep(3600)
  124. # Minor Update
  125. elif current_time.hour == (minor_time + 1):
  126. await self.update_founderless('minor')
  127. await asyncio.sleep(3600)
  128. @commands.command()
  129. @checks.is_owner()
  130. async def update_channel(self, ctx):
  131. """Set the notification channel."""
  132. await self.config.notify_channel.set(ctx.channel.id)
  133. await ctx.send(f'Founderless notify channel set to {ctx.channel.mention}')
  134. @commands.command()
  135. @checks.is_owner()
  136. async def update_time(self, ctx, update):
  137. """Set the update time."""
  138. update = int(update)
  139. await self.config.update_time.set(update)
  140. await ctx.send(f'Major update set to: {await self.config.update_time()}:00 UTC'
  141. f'\nMinor update set to: {await self.config.update_time() + 12}:00 UTC')
  142. @commands.command()
  143. @checks.is_owner()
  144. async def get_settings(self, ctx):
  145. """Get the current settings from config."""
  146. stored_channel = await self.config.notify_channel()
  147. major_time = await self.config.update_time()
  148. minor_time = major_time + 12
  149. founderless = await self.config.previous_founderless()
  150. channel = ctx.guild.get_channel(stored_channel)
  151. await ctx.send(f'Current Channel: {channel.mention}\nCurrent Major Update Time: {major_time}:00 UTC\n'
  152. f'Current Minor Update Time: {minor_time}:00 UTC\nCurrent # Founderless: {len(founderless)}')
  153. @commands.command()
  154. @checks.is_owner()
  155. async def force_update_founderless(self, ctx):
  156. """Manually update the founderless region list."""
  157. founderless_regions = await get_founderless_regions()
  158. await self.config.previous_founderless.set(founderless_regions)
  159. await ctx.send(f'Manually updated founderless regions list. There are now '
  160. f'{len(founderless_regions)} founderless regions.')
  161. @commands.command()
  162. @checks.is_owner()
  163. async def clear_founderless(self, ctx):
  164. """Clear the cached founderless regions."""
  165. await self.config.previous_founderless.set([])
  166. await ctx.send("Cleared cached founderless regions.")
  167. @commands.command()
  168. @checks.is_owner()
  169. async def force_download_dump(self, ctx):
  170. """Manually download the region dump."""
  171. await ctx.send('Downloading region dump...')
  172. await download_region_dump()
  173. await ctx.send('Region dump successfully updated.')