from utils.translate import get_text from time import time import typing import discord import schedule from os import remove from bot import db from loguru import logger from random import randint as rint from discord import app_commands from discord.ext import commands, tasks from discord.app_commands import Choice from datetime import datetime from matplotlib import ticker from matplotlib import pyplot as plt from utils.emojis import chocolate class Economic(commands.Cog, name="Экономика"): def __init__(self, bot): self.bot = bot plt.style.use(['default', "dark.mplstyle"]) def time_translation(self, secs): def two_digit(number): if len(str(number)) == 1: return '0'+str(number) return number time = f"{secs}s " if 60 <= secs < 60*60: time = f"{secs//60}:{two_digit(secs%60)}" elif 60*60 <= secs < 24*60*60: time = f"{secs//60//60}:{two_digit(secs//60%60)}:{two_digit(secs%60)}" elif 24*60*60 <= secs: time = f"{secs//60//60//24}d {two_digit(secs//60//60%24)}:{two_digit(secs//60%60)}:{two_digit(secs%60)}" return time def exp_translation(self, exp): if exp < 1000: return exp if exp < 1000000: exp /= 1000 if exp.is_integer(): return str(int(exp))+'k' return "%.1fk" % exp @commands.Cog.listener() async def on_message(self, message): if not message.author.bot and message.guild is not None and not message.content.startswith(tuple(await self.bot.command_prefix(self.bot, message))): data = await db.members.find_one({"id": message.author.id}) flood_channels = await db.guild_settings.find_one({"id": message.guild.id}) if flood_channels is None: flood_channels = [] else: flood_channels = flood_channels['flood_channels'] if time() - 3 >= data["last_mess_time"] and message.channel.id not in flood_channels: delta_exp = len(message.content) + len(message.attachments)*100 await db.history.update_one( {'type': 'server', 'id': message.guild.id}, {'$inc': {f'current.{datetime.now().hour}': delta_exp}} ) if message.author.voice is not None: delta_exp //= 10 delta_money = rint(1, 5) # Глобальные опыт/уроень await db.members.update_one({"id": message.author.id}, {"$inc": {"exp": delta_exp}}) # Изменяем exp data = await db.members.find_one({"id": message.author.id}) if data is not None: level = data["level"] if level ** 2 * 50 + 5 <= data["exp"]: await db.members.update_one({"id": message.author.id}, {"$inc": {"level": 1}}) # Изменяем level if data["level"]+1 >= data["max_level"]: await db.members.update_one({"id": message.author.id}, {"$inc": {"money": (level + 1) * delta_money}}) # Изменяем money # Локальные опыт/уровень prefix = f"guild_stat.{message.guild.id}" if (await db.members.find_one({"id": message.author.id, f'guild_stat.{message.guild.id}': {'$exists': 0}})): await db.members.update_many({"id": message.author.id}, {"$set": { f"{prefix}.exp": 0, f"{prefix}.level": 0, f"{prefix}.secs_in_voice": 0, f"{prefix}.history": { "hour": {}, "day": {}, "siv_h": {}, "siv_d": {} } }}) # Создаем в guild_stat поле для сервера data = (await db.members.find_one({"id": message.author.id}))["guild_stat"][str(message.guild.id)] await db.members.update_one({"id": message.author.id}, {"$inc": {f"{prefix}.exp": delta_exp}}) data = (await db.members.find_one({"id": message.author.id}))["guild_stat"][str(message.guild.id)] level = data["level"] if level ** 2 * 50 + 5 <= data["exp"]: await db.members.update_one({"id": message.author.id}, {"$inc": {f"{prefix}.level": 1}}) data = await db.guild_settings.find_one({"id": message.guild.id}) if data is not None and data['levelup'] == "send": await message.reply( embed=discord.Embed( title="LEVEL UP", description=f"{message.author.mention} достиг {level+1} уровня!" ), delete_after=10, mention_author=False ) await db.members.update_one({"id": message.author.id}, {"$set": {"last_mess_time": time()}}) @commands.Cog.listener() async def on_voice_state_update(self, member, before, after): if not member.bot: # При входе if before.channel is None: humans = list(filter(lambda x: not x.bot, after.channel.members)) #logger.info(f"{member.name}#{member.discriminator} зашел. В канале теперь {len(humans)} человек") if len(humans) == 1: pass elif len(humans) == 2: self.bot.voice_counter[str(humans[0].id)] = datetime.now() self.bot.voice_counter[str(humans[1].id)] = datetime.now() else: self.bot.voice_counter[str(member.id)] = datetime.now() else: humans = list(filter(lambda x: not x.bot, before.channel.members)) if len(humans) == 1: await self.voice_register(humans[0], before) if len(humans) != 0: await self.voice_register(member, before) # При выходе if after.channel is None: try: del self.bot.voice_counter[str(member.id)] except: pass async def voice_register(self, member, voice_state): if member.id in self.bot.voice_counter.keys(): secs = (datetime.now() - self.bot.voice_counter[member.id]).seconds else: secs = 0 # TODO: Убрать нахуй эти ифы if voice_state.self_deaf: k = 0 elif voice_state.self_mute: k = .5 else: k = 1 if voice_state.self_deaf and (voice_state.self_stream or voice_state.self_video): k = 0.1 elif voice_state.self_stream or voice_state.self_video: k *= 2 exp = int(secs // 5 * k) money = exp * rint(1, 5) await db.members.update_one({"id": member.id}, { "$inc": { f"guild_stat.{member.guild.id}.secs_in_voice": secs, f"guild_stat.{member.guild.id}.exp": exp, "exp": exp, "money": money }}) await db.history.update_one( {'type': 'server', 'id': member.guild.id}, {"$inc": {f'current.{datetime.now().hour}': exp}} ) self.bot.voice_counter[member.id] = datetime.now() logger.info( f"{member.name}#{member.discriminator}\n" f"\tСекунд: +{secs}\n" f"\tОпыт: +{exp}\n" f"\tДенег: +{money}" ) @commands.Cog.listener() async def on_member_join(self, member): member_data = await db.members.find_one({"id": member.id}) if member_data is None: logger.warning("Пользователь не найден") return if str(member.guild.id) in member_data["guild_stat"].keys(): logger.debug(member_data["guild_stat"][str(member.guild.id)]["exp"], end="\t") await db.members.update_one( {"id": member.id}, {"$inc": {"exp": member_data["guild_stat"][str(member.guild.id)]["exp"]}} ) @commands.Cog.listener() async def on_member_remove(self, member): member_data = await db.members.find_one({"id": member.id}) if member_data is None: logger.warning("Пользователь не найден") return if str(member.guild.id) in member_data["guild_stat"].keys(): logger.debug(member_data["guild_stat"][str(member.guild.id)]["exp"], end="\t") await db.members.update_one( {"id": member.id}, {"$dec": {"exp": member_data["guild_stat"][str(member.guild.id)]["exp"]} }) @commands.Cog.listener() async def on_guild_join(self, guild): async for m in db.members.find({f"guild_stat.{guild.id}": {"$exists": True}}): logger.debug(m["guild_stat"][str(guild.id)]["exp"], end="\t") await db.members.update_one( {"id": m['id']}, {"$inc": {"exp": m["guild_stat"][str(guild.id)]["exp"]} }) logger.debug(m["guild_stat"][str(guild.id)]["exp"]) @commands.Cog.listener() async def on_guild_remove(self, guild): async for m in db.members.find({f"guild_stat.{guild.id}": {"$exists": True}}): logger.debug(m["guild_stat"][str(guild.id)]["exp"], end="\t") await db.members.update_one( {"id": m['id']}, {"$dec": {"exp": m["guild_stat"][str(guild.id)]["exp"]} }) logger.debug(m["guild_stat"][str(guild.id)]["exp"]) @app_commands.command(description="View balance and level") async def rank(self, inter: discord.Interaction, user: discord.Member = None): if user is None: user = inter.user if self.bot.get_user(user.id).bot: await inter.response.send_message(await get_text(inter, "Bot hasn't experience", "rank")) return user_data = await db.members.find_one({"id": user.id}) if user_data is None or str(inter.guild.id) not in user_data['guild_stat'].keys(): await inter.response.send_message("Об этом пользователе информации пока нет") return if str(user.id) in self.bot.voice_counter.keys(): prefix = f"guild_stat.{inter.guild.id}" if str(inter.guild.id) not in user_data["guild_stat"].keys(): await db.members.update_many( {"id": user.id}, {"$set": { f"{prefix}.exp": 0, f"{prefix}.level": 0, f"{prefix}.secs_in_voice": 0, f"{prefix}.history": { "hour": {}, "day": {} } }} ) # Создаем в guild_stat поле для сервера await self.voice_register(user, user.voice) user_data = await db.members.find_one({"id": user.id}) if user_data is None: return if inter.guild is not None: color = inter.guild.me.color if color == discord.Color.default(): color = discord.Color(0xaaffaa) else: color = discord.Color(0xaaffaa) history = user_data['history'] if len(history['hour']) >= 1: per_hour = user_data['exp'] - history['hour'][list(history['hour'].keys())[-1]] else: per_hour = "???" if len(history['hour']) >= 2: last_hour = history['hour'][list(history['hour'].keys())[-1]] - history['hour'][list(history['hour'].keys())[-2]] else: last_hour = "???" if len(history['day']) >= 1: per_day = user_data['exp'] - history['day'][list(history['day'].keys())[-1]] else: per_day = "???" if len(history['day']) >= 2: last_day = history['day'][list(history['day'].keys())[-1]] - history['day'][list(history['day'].keys())[-2]] else: last_day = "???" description = f"{await get_text(inter, 'Money', 'rank')}: {user_data['money']}{chocolate}\n\n" \ f"__{await get_text(inter, 'Global stats', 'rank')}:__\n" \ f"{await get_text(inter, 'Level', 'rank')}: {user_data['level']}\n" \ f"{await get_text(inter, 'Exp', 'rank')}: {user_data['exp']} / {user_data['level'] ** 2 * 50 + 5}" \ f" ({(user_data['level'] ** 2 * 50 + 5) - user_data['exp']})\n" \ f"{await get_text(inter, 'Per hour', 'rank')}: {per_hour}, {await get_text(inter, 'per the past hour', 'rank')}: {last_hour}\n" \ f"{await get_text(inter, 'Per day', 'rank')}: {per_day}, {await get_text(inter, 'per the past day', 'rank')}: {last_day}\n" secs = user_data['guild_stat'][str(inter.guild.id)]['secs_in_voice'] if "guild_stat" in list(user_data.keys()): if inter.guild is not None and str(inter.guild.id) in list(user_data['guild_stat'].keys()): description += f"\n__{await get_text(inter, 'rank', 'On this guild')}:__\n" \ f"{await get_text(inter, 'Level', 'rank')}: {user_data['guild_stat'][str(inter.guild.id)]['level']}\n" \ f"{await get_text(inter, 'Exp', 'rank')}: {user_data['guild_stat'][str(inter.guild.id)]['exp']} / " \ f"{user_data['guild_stat'][str(inter.guild.id)]['level'] ** 2 * 50 + 5}" \ f" ({(user_data['guild_stat'][str(inter.guild.id)]['level'] ** 2 * 50 + 5) - user_data['guild_stat'][str(inter.guild.id)]['exp']})\n" \ f"{await get_text(inter, 'Time in voice channels', 'rank')}: {self.time_translation(secs)}" e = discord.Embed(title=f"{await get_text(inter, 'Info about', 'rank')} {self.bot.get_user(user.id).name}", description=description, color=color) await inter.response.send_message(embed=e) @app_commands.command(description="Top members of the guild") @app_commands.describe(category='Category') @app_commands.choices(category=[ Choice(name='Balance', value="Баланс"), Choice(name='Experience', value="Опыт"), Choice(name='Time in voice channel', value="Время в войсе") ]) # @logger.catch async def top(self, inter: discord.Interaction, category: Choice[str] = None): if category is None : category = "Опыт" else: category = category.value categories = { 'Уровень': f"guild_stat.{inter.guild.id}.level", 'Баланс': "money", 'Опыт': f"guild_stat.{inter.guild.id}.exp", 'Время в войсе': f"guild_stat.{inter.guild.id}.secs_in_voice" } if inter.guild is not None: color = inter.guild.me.color if color == discord.Color.default(): color = discord.Color(0xaaffaa) else: color = discord.Color(0xaaffaa) e = discord.Embed(title="Топ", description=category, color=color) data_ = await db.members.find({f"guild_stat.{inter.guild.id}": {"$exists": True}}).sort(categories[category], -1).to_list(10) if not data_: await inter.response.send_message(await get_text(inter, "Not enough data. Try later", "rank")) return l = min(len(data_), 10) MAX_COLONS = 25 if category == "Опыт": max_val = data_[0]["guild_stat"][str(inter.guild.id)]["exp"] elif category == "Время в войсе": max_val = data_[0]['guild_stat'][str(inter.guild.id)]['secs_in_voice'] elif category == "Баланс": max_val = data_[0]['money'] max_val = max(max_val, 1) for place in range(l): m = data_[place] if 'level' not in m['guild_stat'][str(inter.guild.id)].keys(): await db.members.update_one( {'id': m['id']}, {'$set': {f'guild_stat.{inter.guild.id}.level': 0}} ) m['guild_stat'][str(inter.guild.id)]['level'] = 0 data = {"Уровень": ["Ур:", m['guild_stat'][str(inter.guild.id)]['level']], "Опыт": ["Опыт:", self.exp_translation(m['guild_stat'][str(inter.guild.id)]["exp"]), m['guild_stat'][str(inter.guild.id)]["exp"]], "Время в войсе": [":sound:", self.time_translation(m['guild_stat'][str(inter.guild.id)]['secs_in_voice']), m['guild_stat'][str(inter.guild.id)]['secs_in_voice']], "Баланс": [":moneybag:", m["money"], m["money"]] } u = self.bot.get_user(m['id']) if u is None: name = str(place + 1) + '. ' + m["name"] else: name = str(place + 1) + '. ' + u.name + '#' + str(u.discriminator) e.add_field(name=name, inline=False, value=f"{data[category][0]} {data[category][1]} | " + " | ".join([f"{data[k][0]} {data[k][1]}" for k in data.keys() if k != category]) + "\n" + str(int(data[category][2]/max_val*100)) + '% '+ "█" * int(data[category][2]/max_val*MAX_COLONS) + "▌" * int((data[category][2]/max_val*MAX_COLONS)%2)) await inter.response.send_message(embed=e) @app_commands.command(description="Comparison of exp with other members") @app_commands.choices(period=[ Choice(name='Per the entire period', value=-1), Choice(name='Per month', value=24*30), Choice(name='Per week', value=24*7), Choice(name='Per day', value=24) ]) async def dif_graph(self, inter: discord.Interaction, user1: discord.Member, user2: discord.Member = None, period: Choice[int] = -1): if period != -1: period = period.value ts = datetime.now().timestamp() # if user1 is None: user1 = user2 user1 = user1.id if user2 is None: user2 = inter.user.id else: user2 = user2.id if self.bot.get_user(user1).bot or self.bot.get_user(user2).bot: await inter.response.send_message("У ботов нет опыта. Они всемогущи") return db_ = db.members info1 = await db_.find_one({"id": user1})['guild_stat'][str(inter.guild.id)]['history']['hour'] info2 = await db_.find_one({"id": user2})['guild_stat'][str(inter.guild.id)]['history']['hour'] info1[str(int(ts))] = await db_.find_one({"id": user1})['guild_stat'][str(inter.guild.id)]['exp'] info2[str(int(ts))] = await db_.find_one({"id": user2})['guild_stat'][str(inter.guild.id)]['exp'] if period == -1: data1 = list(info1.values()) data2 = list(info2.values()) else: data1 = [info1[key] for key in info1.keys() if int(key) >= ts-period*60*60] data2 = [info2[key] for key in info2.keys() if int(key) >= ts-period*60*60] fig, ax = plt.subplots(figsize=(8, 5)) ax.plot(list(map(int, info1.keys()))[-len(data1):], data1, marker='.', label=self.bot.get_user(user1).name) ax.plot(list(map(int, info2.keys()))[-len(data2):], data2, marker='.', label=self.bot.get_user(user2).name) # ax.plot([i for info1.keys()], data2, label="Разница") ax.grid(True) ax.set_ylabel('Опыт') ax.set_xlabel('Время (ч)') ax.legend(loc='upper left') ax.xaxis.set_major_formatter(ticker.FuncFormatter(lambda x, pos: datetime.fromtimestamp(x).strftime('%d.%m'))) if period == 24: ax.xaxis.set_major_locator(ticker.IndexLocator(base=60*60*5, offset=0)) ax.xaxis.set_major_formatter(ticker.FuncFormatter(lambda x, pos: datetime.fromtimestamp(x).strftime('%Hh'))) elif period == 24*7: ax.xaxis.set_major_locator(ticker.IndexLocator(base=60*60*24, offset=0)) ax.xaxis.set_major_formatter(ticker.FuncFormatter(lambda x, pos: datetime.fromtimestamp(x).strftime('%d.%m'))) else: ax.xaxis.set_major_locator(ticker.IndexLocator(base=60*60*24*7, offset=0)) ax.xaxis.set_minor_locator(ticker.IndexLocator(base=60*60*24, offset=0)) ax.xaxis.set_minor_formatter(ticker.FuncFormatter(lambda x, pos: datetime.fromtimestamp(x).strftime('%d'))) ax.legend().get_frame().set_boxstyle('Round', pad=0.2, rounding_size=1) ax.legend().get_frame().set_linewidth(0.0) ax.xaxis.set_ticks_position('none') ax.yaxis.set_ticks_position('none') ax.spines['bottom'].set_visible(False) ax.spines['top'].set_visible(False) ax.spines['left'].set_color('#303030') ax.spines['right'].set_color('#303030') fig.savefig(f'tmp/{inter.id}.png') with open(f'tmp/{inter.id}.png', 'rb') as f: await inter.response.send_message(file=discord.File(f)) remove(f'tmp/{inter.id}.png') @app_commands.command() @app_commands.choices(period=[ Choice(name='Per the entire period', value=-1), Choice(name='Per month', value=24*30), Choice(name='Per week', value=24*7), Choice(name='Per day', value=24) ]) async def top_graph(self, inter: discord.Interaction, period: Choice[int]=-1): if period != -1: period = period.value ts = datetime.now().timestamp() db_mem = db.members data = await db_mem.find({f"guild_stat.{inter.guild.id}": {"$exists": True}}).sort(f"guild_stat.{inter.guild.id}.exp", -1).to_list(10) if not data: await inter.response.send_message("Недостаточно данных. Попробуйте завтра") return fig, ax = plt.subplots(figsize=(8, 5)) ax.grid(True) ax.set_ylabel('Опыт', color=(1., 1., 1.)) ax.set_xlabel('Время', color=(1., 1., 1.)) # ax.set_facecolor((.12, .12, .12)) marker = '.' if -1 < period <= 24 else '' for user in data: info = user['guild_stat'][str(inter.guild.id)]['history']['hour'] if period == -1: vals = list(info.values()) else: vals = [info[key] for key in info.keys() if int(key) >= ts-period*60**2] ax.plot(list(map(int, info.keys()))[-len(vals):], vals, marker=marker, label=self.bot.get_user(user['id']).name) ax.xaxis.set_major_formatter(ticker.FuncFormatter(lambda x, pos: datetime.fromtimestamp(x).strftime('%d.%m'))) if period == 24: ax.xaxis.set_major_locator(ticker.IndexLocator(base=60*60*5, offset=0)) ax.xaxis.set_major_formatter(ticker.FuncFormatter(lambda x, pos: datetime.fromtimestamp(x).strftime('%Hh'))) elif period == 24*7: ax.xaxis.set_major_locator(ticker.IndexLocator(base=60*60*24, offset=0)) ax.xaxis.set_major_formatter(ticker.FuncFormatter(lambda x, pos: datetime.fromtimestamp(x).strftime('%d.%m'))) else: ax.xaxis.set_major_locator(ticker.IndexLocator(base=60*60*24*7, offset=0)) ax.xaxis.set_minor_locator(ticker.IndexLocator(base=60*60*24, offset=0)) ax.xaxis.set_minor_formatter(ticker.FuncFormatter(lambda x, pos: datetime.fromtimestamp(x).strftime('%d'))) # fig.autofmt_xdate() ax.legend().get_frame().set_boxstyle('Round', pad=.2, rounding_size=1) ax.legend().get_frame().set_linewidth(.0) ax.xaxis.set_ticks_position('none') ax.yaxis.set_ticks_position('none') ax.spines['bottom'].set_visible(False) ax.spines['top'].set_visible(False) ax.spines['left'].set_visible('#303030') ax.spines['right'].set_visible('#303030') fig.savefig(f'tmp/{inter.id}.png') with open(f'tmp/{inter.id}.png', 'rb') as f: await inter.response.send_message(file=discord.File(f)) remove(f'tmp/{inter.id}.png') # @logger.catch async def setup(bot): await bot.add_cog(Economic(bot))