wynncraft-bot/cogs/wynncraft/wynncraftcog.py
2020-09-07 20:10:18 +02:00

341 lines
18 KiB
Python

import asyncio
import itertools
import math
from redbot.core import commands
from redbot.core import Config, checks
from discord import TextChannel
import discord
import traceback
import requests
from datetime import datetime, timezone, timedelta
def utc_to_local(utc_dt):
return utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None)
class WynncraftCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, identifier=48775419874)
default_guild = {
"guild_name": None, # OK
"ping_levels": False, # OK
"ping_channel": None, # OK
"role_levels": [
750098524976185525, # 0-9
750098542802239488, # 10-19
750098546883166292, # 20-29
750098551756947496, # 30-39
750098557444292740, # 40-49
750098560732889088, # 50-59
750098568194424872, # 60-69
750098573353418822, # 70-79
750098579535822918, # 80-89
750098584082448434, # 90-99
750098593976811550, # 100+
],
"role_class": {
"mage": 750097053840965752,
"assassin": 750097058362425417,
"shaman": 750097056093438012,
"archer": 750097050867204246,
"warrior": 750097044433272833
},
"update_msg": 750124413529358467,
"role_professions": {
"farming": [
743544309059551292, # 0-9
750103671077077004, # 10-19
750103673178423397, # 20-29
750103675195752528, # 30-39
750103677011755080, # 40-49
750103681046675527, # 50-59
750103683349479452, # 60-69
750103692182552667, # 70-79
750103687589920789, # 80-89
750103685295767734, # 90-99
750103689536077966, # 100-109
750103679314558980, # 110
],
"mining": [
743544348821553305, # 0-9
750105003464851508, # 10-19
750105011173851176, # 20-29
750105019260469288, # 30-39
750104996204380211, # 40-49
750105001522888874, # 50-59
750105005759135805, # 60-69
750105016433377380, # 70-79
750105008644554783, # 80-89
750105013749153792, # 90-99
750104998670762075, # 100-109
750104994392309801, # 110
],
"fishing": [
743544397152518146, # 0-9
750105742127923303, # 10-19
750105731482779779, # 20-29
750105744774529124, # 30-39
750105734615662672, # 40-49
750105754739933345, # 50-59
750105729284964472, # 60-69
750105749899837551, # 70-79
750105752374345769, # 80-89
750105747219546122, # 90-99
750105737254011051, # 100-109
750105739716067488, # 110
],
"woodcutting": [
743920688607264818, # 0-9
750106425174261771, # 10-19
750106428253012048, # 20-29
750106431033835570, # 30-39
750106433785430126, # 40-49
750106436419452949, # 50-59
750106438931579032, # 60-69
750106441557475430, # 70-79
750106444493488129, # 80-89
750106446883979326, # 90-99
750106449790631937, # 100-109
750106452277985301, # 110
]
}
}
default_global = {
"log": True
}
self.config.register_guild(**default_guild)
self.config.register_global(**default_global)
@commands.command()
@checks.admin_or_permissions(manage_guild=True)
async def ping_channel(self, ctx, channel: TextChannel):
i = channel.id
await self.config.guild(ctx.guild).ping_channel.set(i)
await self.config.guild(ctx.guild).ping_levels.set(True)
await ctx.send("Les messages de montée de niveau seront désormais envoyés dans %s" % channel.mention)
@commands.command()
@checks.admin_or_permissions(manage_guild=True)
async def ping_levels(self, ctx, state):
if state == "true" or state == "on":
await self.config.ping_levels.set(True)
await ctx.send(":white_check_mark: Activé!")
elif state == "false" or state == "off":
await self.config.ping_levels.set(False)
await ctx.send(":white_check_mark: Désactivé!")
else:
await ctx.send(":x:")
@commands.command()
@checks.admin_or_permissions(manage_guild=True)
async def set_guild(self, ctx, guild_name):
await self.config.guild(ctx.guild).guild_name.set(guild_name)
await ctx.send(":white_check_mark: Votre guilde est désormais **%s**" % guild_name)
@commands.command()
async def player(self, ctx, player_name):
req = requests.get("https://api.wynncraft.com/v2/player/%s/stats" % player_name).json()
if not req["data"]:
await ctx.send(":x: joueur non trouvé")
return
player_data = req["data"][0]
real_player_name = player_data["username"]
output = "**__%s__**\n" % real_player_name
output += ":black_square_button: **Joueur depuis:** %s\n" % player_data["meta"]["firstJoin"]
output += ":black_square_button: **Niveau total:** %s\n" % player_data["global"]["totalLevel"]["combat"]
output += ":black_square_button: **__Classes__**:\n"
for c in player_data["classes"]:
output += "\t:diamond_shape_with_a_dot_inside: **%s:** level %s\n" % (c["name"], c["professions"]["combat"]["level"])
await ctx.send(output)
@commands.command()
@checks.is_owner()
async def log(self, ctx, state):
state = state.lower()
if state == "true" or state == "on":
await self.config.log.set(True)
await ctx.send(":white_check_mark:")
print("Logs on")
elif state == "false" or state == "off":
await self.config.log.set(False)
await ctx.send(":white_check_mark:")
print("Logs off")
else:
await ctx.send(":x:")
async def _log(self, s):
if await self.config.log():
print(s)
async def loop(self):
await self.bot.wait_until_ready()
last_status = {}
while self is self.bot.get_cog("WynncraftCog"):
await self._log("Looping guilds")
for guild in self.bot.guilds:
roles_combat_level = await self.config.guild(guild).role_levels()
roles_classes = await self.config.guild(guild).role_class()
roles_professions = await self.config.guild(guild).role_professions()
guild_id = guild.id
online_players = []
almost_online_players = []
await self._log("Loop id %s" % guild_id)
try:
guild_name = await self.config.guild(guild).guild_name()
if not guild_name:
continue
r = requests.get("https://api.wynncraft.com/public_api.php?action=guildStats&command=%s" % guild_name).json()
for member in r["members"]:
member_name = member["name"]
await self._log("calling API for player %s" % member_name)
r = requests.get("https://api.wynncraft.com/v2/player/%s/stats" % member_name).json()
max_combat_lvl_class = None
best_professions = dict.fromkeys(list(roles_professions.keys()), 0)
if not r["data"]:
await self._log("%s had empty data %s" % (member_name, r["data"]))
continue
for cl in r["data"][0]["classes"]:
if guild_id not in last_status:
last_status[guild_id] = {}
if member_name not in last_status[guild_id]:
last_status[guild_id][member_name] = {}
if cl["name"] not in last_status[guild_id][member_name]:
last_status[guild_id][member_name][cl["name"]] = cl
# ping level up
ping_channel_id = await self.config.guild(guild).ping_channel()
if await self.config.guild(guild).ping_levels() and ping_channel_id:
await self._log("Checking for level up")
if cl["professions"]["combat"]["level"] > last_status[guild_id][member_name][cl["name"]]["professions"]["combat"]["level"]:
ping_channel = self.bot.get_channel(ping_channel_id)
await ping_channel.send(":high_brightness: %s a level up au niveau %s! GG!" % (member_name, cl["professions"]["combat"]["level"]))
else:
await self._log("Player %s had last level %s and current %s" % (member_name, last_status[guild_id][member_name][cl["name"]]["professions"]["combat"]["level"], cl["professions"]["combat"]["level"]))
last_status[guild_id][member_name][cl["name"]] = cl
# get max_combat_lvl_class
if not max_combat_lvl_class or cl["professions"]["combat"]["level"] > max_combat_lvl_class["professions"]["combat"]["level"]:
max_combat_lvl_class = cl
# set max_lvl_professions
for prof_name in list(roles_professions.keys()):
if cl["professions"][prof_name]["level"] > best_professions[prof_name]:
best_professions[prof_name] = cl["professions"][prof_name]["level"]
if r["data"][0]["meta"]["location"]["online"]:
await self._log("%s is online" % member_name)
online_players.append(r)
else:
last_join_dt_utc = datetime.strptime(r["data"][0]["meta"]["lastJoin"], "%Y-%m-%dT%H:%M:%S.%fZ")
last_join_dt_local = utc_to_local(last_join_dt_utc)
if last_join_dt_local + timedelta(minutes=10) > datetime.now(last_join_dt_local.tzinfo):
await self._log("%s is ALMOST online" % member_name)
almost_online_players.append(r)
else:
await self._log("%s was offline: %s" % (member_name, r["data"][0]["meta"]["location"]))
await self._log("Setting roles")
# set max_combat_lvl_class role
discord_member = discord.utils.find(lambda m: m.display_name.lower() == member_name.lower(), guild.members)
if max_combat_lvl_class and discord_member:
await self._log("max_combat_lvl_class and discord_member True")
# update level
max_combat_lvl = max_combat_lvl_class["professions"]["combat"]["level"]
combat_role_index = max_combat_lvl // 10
if combat_role_index > 10: combat_role_index = 10
combat_role_id = roles_combat_level[combat_role_index]
combat_role = guild.get_role(combat_role_id)
if combat_role not in discord_member.roles:
await self._log("Add role: %s" % combat_role)
await discord_member.add_roles(combat_role)
roles_to_remove = list(filter(lambda role: role.id in roles_combat_level and role.id != combat_role_id, discord_member.roles))
if roles_to_remove:
await self._log("remove roles: %s" % roles_to_remove)
await discord_member.remove_roles(*roles_to_remove)
# update class
class_name = max_combat_lvl_class["name"]
class_role_id = None
if class_name.startswith("archer") or class_name.startswith("hunter"):
class_role_id = roles_classes["archer"]
if class_name.startswith("mage") or class_name.startswith("darkwizard"):
class_role_id = roles_classes["mage"]
if class_name.startswith("assassin") or class_name.startswith("ninja"):
class_role_id = roles_classes["assassin"]
if class_name.startswith("warrior") or class_name.startswith("knight"):
class_role_id = roles_classes["warrior"]
if class_name.startswith("shaman") or class_name.startswith("skyseer"):
class_role_id = roles_classes["shaman"]
class_role = guild.get_role(class_role_id)
if class_role not in discord_member.roles:
await self._log("Add role: %s" % class_role)
await discord_member.add_roles(class_role)
roles_to_remove = list(filter(lambda role: role.id in list(roles_classes.values()) and role.id != class_role_id, discord_member.roles))
if roles_to_remove:
await self._log("remove roles: %s" % roles_to_remove)
await discord_member.remove_roles(*roles_to_remove)
# update professions
role_ids_to_add = []
for prof_name in best_professions:
prof_level = best_professions[prof_name]
if prof_level < 5: continue # do not add roles for professions < lvl 5
prof_role_index = prof_level // 10
if prof_role_index > 10: prof_role_index = 10
role_ids_to_add.append(roles_professions[prof_name][prof_role_index])
roles_to_add = []
for role_id_to_add in role_ids_to_add:
roles_to_add.append(guild.get_role(role_id_to_add))
await self._log("Add role: %s" % roles_to_add)
await discord_member.add_roles(*roles_to_add)
all_prof_ids = list(itertools.chain.from_iterable([roles_professions[b] for b in roles_professions]))
roles_to_remove = list(filter(lambda role: role.id in all_prof_ids and role.id not in role_ids_to_add, discord_member.roles))
if roles_to_remove:
await self._log("remove roles: %s" % roles_to_remove)
await discord_member.remove_roles(*roles_to_remove)
else:
await self._log("max_combat_lvl_class: %s, discord_member: %s (name: %s)" % (max_combat_lvl_class, discord_member, member_name))
# update online players
await self._log("Updating online list")
full_text = ""
for online_player in online_players:
s = ":green_circle: **%s** connecté sur %s" % (online_player["data"][0]["username"], online_player["data"][0]["meta"]["location"]["server"])
full_text += s + "\n"
for almost_online_player in almost_online_players:
last_join_dt_utc = datetime.strptime(almost_online_player["data"][0]["meta"]["lastJoin"], "%Y-%m-%dT%H:%M:%S.%fZ")
last_join_dt_local = utc_to_local(last_join_dt_utc)
mins = math.ceil((datetime.now(last_join_dt_local.tzinfo) - last_join_dt_local).total_seconds() // 60)
s = ":grey_question: **%s** était connecté il y a %s minutes" % (almost_online_player["data"][0]["username"], mins)
full_text += s + "\n"
if not online_players and not almost_online_players:
full_text = "Personne n'est connecté :disappointed_relieved:\n"
full_text += "\n*Dernière mise à jour: %s*\n*Les vraies informations peuvent prendre jusqu'à 5 minutes de plus*" % datetime.now().strftime("%d/%m/%Y %H:%M:%S")
online_channel = self.bot.get_channel(750100968766701708)
update_msg = await online_channel.fetch_message(await self.config.guild(guild).update_msg())
await update_msg.edit(content=full_text)
except:
if await self.config.log():
traceback.print_exc()
await asyncio.sleep(30)