A RedBot cog for sending notifications when there are new founderless regions in NationStates.
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

194 rader
7.6 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. def get_founderless_regions():
  15. """Return the list of founderless regions."""
  16. time.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. def download_region_dump():
  22. """Download the latest region dump from the NS API."""
  23. time.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. self.bg_loop_task = asyncio.create_task(self.bg_loop())
  50. def cog_unload(self):
  51. if self.bg_loop_task:
  52. self.bg_loop_task.cancel()
  53. @commands.command()
  54. @checks.is_owner()
  55. async def is_task_running(self, ctx):
  56. """Check if the main loop is running."""
  57. if self.bg_loop_task:
  58. await ctx.send('True')
  59. else:
  60. await ctx.send('False')
  61. @commands.command()
  62. @checks.is_owner()
  63. async def start_task(self, ctx):
  64. """Start the main loop if it is not running."""
  65. if self.bg_loop_task:
  66. await ctx.send('Task is already running')
  67. else:
  68. self.bg_loop_task = asyncio.create_task(self.bg_loop())
  69. @commands.command()
  70. @checks.is_owner()
  71. async def force_check(self, ctx, update_type):
  72. """Force perform a check in the differences of founderless regions."""
  73. if update_type.lower() == 'major' or update_type.lower() == 'minor':
  74. await self.update_founderless(update_type)
  75. else:
  76. return
  77. async def update_founderless(self, update_type):
  78. """Check for the differences in the founderless regions and then output that to a channel."""
  79. channel_id = await self.config.notify_channel()
  80. channel = self.bot.get_channel(channel_id)
  81. await channel.send(f'Beginning {update_type} check...')
  82. new_founderless_regions = get_founderless_regions()
  83. previous_founderless_regions = await self.config.previous_founderless()
  84. # A region is now founderless if it is in the current list, but wasn't in the previous list
  85. now_founderless = [region for region in new_founderless_regions if (region not in previous_founderless_regions)]
  86. # Get the region endos for both lists
  87. now_founderless_endos = get_region_endos(now_founderless)
  88. # Create an ordered set with each region's name and delegate endorsement level
  89. out = []
  90. for region in now_founderless:
  91. try:
  92. region_endos = now_founderless_endos[region]
  93. except KeyError:
  94. region_endos = -1
  95. out.append((region_endos, region))
  96. print(out)
  97. # Sort the output by delegate endos in descending order
  98. out.sort()
  99. out.reverse()
  100. # Prep output
  101. message_content = "The following regions are now **Founderless**:\n"
  102. for region in out:
  103. message_content += f"https://www.nationstates.net/region={region[1]} ({region[0]})\n"
  104. for page in pagify(message_content):
  105. await channel.send(page)
  106. await self.config.previous_founderless.set(new_founderless_regions)
  107. async def bg_loop(self):
  108. """Main background loop."""
  109. while True:
  110. # Only check once every 5 minutes
  111. await asyncio.sleep(300)
  112. current_time = datetime.datetime.utcnow()
  113. major_time = await self.config.update_time()
  114. minor_time = major_time + 12
  115. # Major Update
  116. if current_time.hour == (major_time + 2):
  117. download_region_dump()
  118. await self.update_founderless('major')
  119. await asyncio.sleep(3600)
  120. # Minor Update
  121. elif current_time.hour == (minor_time + 1):
  122. await self.update_founderless('minor')
  123. await asyncio.sleep(3600)
  124. @commands.command()
  125. @checks.is_owner()
  126. async def update_channel(self, ctx):
  127. """Set the notification channel."""
  128. await self.config.notify_channel.set(ctx.channel.id)
  129. await ctx.send(f'Founderless notify channel set to {ctx.channel.mention}')
  130. @commands.command()
  131. @checks.is_owner()
  132. async def update_time(self, ctx, update):
  133. """Set the update time."""
  134. update = int(update)
  135. await self.config.update_time.set(update)
  136. await ctx.send(f'Major update set to: {await self.config.update_time()}:00 UTC'
  137. f'\nMinor update set to: {await self.config.update_time() + 12}:00 UTC')
  138. @commands.command()
  139. @checks.is_owner()
  140. async def get_settings(self, ctx):
  141. """Get the current settings from config."""
  142. stored_channel = await self.config.notify_channel()
  143. major_time = await self.config.update_time()
  144. minor_time = major_time + 12
  145. founderless = await self.config.previous_founderless()
  146. channel = ctx.guild.get_channel(stored_channel)
  147. await ctx.send(f'Current Channel: {channel.mention}\nCurrent Major Update Time: {major_time}:00 UTC\n'
  148. f'Current Minor Update Time: {minor_time}:00 UTC\nCurrent # Founderless: {len(founderless)}')
  149. @commands.command()
  150. @checks.is_owner()
  151. async def force_update_founderless(self, ctx):
  152. """Manually update the founderless region list."""
  153. founderless_regions = get_founderless_regions()
  154. await self.config.previous_founderless.set(founderless_regions)
  155. await ctx.send(f'Manually updated founderless regions list. There are now '
  156. f'{len(founderless_regions)} founderless regions.')
  157. @commands.command()
  158. @checks.is_owner()
  159. async def clear_founderless(self, ctx):
  160. """Clear the cached founderless regions."""
  161. await self.config.previous_founderless.clear()
  162. await ctx.send("Cleared cached founderless regions.")
  163. @commands.command()
  164. @checks.is_owner()
  165. async def force_download_dump(self, ctx):
  166. """Manually download the region dump."""
  167. await ctx.send('Downloading region dump...')
  168. download_region_dump()
  169. await ctx.send('Region dump successfully updated.')