From 975649995fe113a19d79a395fecb0bdc2e6d6af1 Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 21 Nov 2020 02:36:01 +0100 Subject: [PATCH 1/4] Changed client to bot --- sentdebot.py | 134 +++++++++++++++++++++++++++------------------------ 1 file changed, 72 insertions(+), 62 deletions(-) diff --git a/sentdebot.py b/sentdebot.py index e57206e..39f5fba 100644 --- a/sentdebot.py +++ b/sentdebot.py @@ -14,15 +14,22 @@ from requests_html import HTMLSession from collections import Counter from matplotlib import style +from discord.ext.commands import Bot style.use("dark_background") -path = '/sentdebot/' +token_path = os.path.abspath(os.path.join('sentdebot', 'token.txt')) +local_files_path = os.path.abspath(os.path.join('sentdebot')) + +SENTEX_GUILD_ID = 405403391410438165 +SENTDEX_ID = 324953561416859658 +DHAN_ID = 150409781595602946 # days of history to work with DAYS_BACK = 21 RESAMPLE = "60min" + # when doing counters of activity, top n users. MOST_COMMON_INT = 10 # names of channels where we want to count activity. @@ -50,9 +57,9 @@ intents = discord.Intents.all() -client = discord.Client(intents=intents) +bot = Bot(command_prefix="!", ignore_case=True, intents=intents) -token = open(f"{path}/token.txt", "r").read().split('\n')[0] +token = open(f"{token_path}", "r").read().split('\n')[0] commands_available = """```py def commands(): @@ -66,19 +73,30 @@ def commands(): } ```""" -image_chan_ids = [408713676095488000, - 412620789133606914, - 476412789184004096, - 499945870473691146, - 484406428233367562, - ] +MAIN_CH_ID = 408713676095488000 +DOGS_CH_ID = 671016601440747520 +HELP_CH_ID_0 = 412620789133606914 +HELP_CH_ID_1 = 507281643606638622 +HELP_CH_ID_2 = 682674227664388146 +VOICE2TEXT_CH_ID = 484406428233367562 +HELLO_CHARLES_CH_ID = 407958260168130570 + +image_chan_ids = [MAIN_CH_ID, + HELP_CH_ID_0, + HELP_CH_ID_1, + HELP_CH_ID_2, + 476412789184004096, # ? + 499945870473691146, # ? + VOICE2TEXT_CH_ID, + ] -chatbots = [405511726050574336, - 428904098985803776, - 414630095911780353, - 500507500962119681] +chatbots = [405511726050574336, # Charles + 428904098985803776, # Irene + 414630095911780353, # Charlene (Charles' alter ego) + 500507500962119681] # ? +SENTDEBOT_ID = 499921685051342848 admin_id = 405506750654054401 mod_id = 405520180974714891 @@ -94,9 +112,6 @@ def commands(): 501114928854204431, 479433212561719296] -channel_ids = [408713676095488000, # main - 412620789133606914] # help - def search_term(message): try: @@ -107,7 +122,7 @@ def search_term(message): if match.group(2)[check.start()-1] != '\\': return False return match.group(2) - except: + except Exception: return False @@ -135,31 +150,32 @@ def df_match(c1, c2): async def user_metrics_background_task(): - await client.wait_until_ready() + await bot.wait_until_ready() global sentdex_guild - sentdex_guild = client.get_guild(405403391410438165) + sentdex_guild = bot.get_guild(SENTEX_GUILD_ID) - while not client.is_closed(): + while not bot.is_closed(): try: online, idle, offline = community_report(sentdex_guild) - with open(f"{path}/usermetrics.csv","a") as f: + metrics_path = os.path.join(local_files_path, "usermetrics.csv") + with open(metrics_path, "a") as f: f.write(f"{int(time.time())},{online},{idle},{offline}\n") - df_msgs = pd.read_csv(f'{path}/msgs.csv', names=['time', 'uid', 'channel']) + df_msgs = pd.read_csv(os.path.join(local_files_path, "msgs.csv"), + names=['time', 'uid', 'channel'] + ) df_msgs = df_msgs[(df_msgs['time'] > time.time()-(86400*DAYS_BACK))] df_msgs['count'] = 1 df_msgs['date'] = pd.to_datetime(df_msgs['time'], unit='s') df_msgs.drop("time", 1, inplace=True) df_msgs.set_index("date", inplace=True) - df_no_dup = df_msgs.copy() df_no_dup['uid2'] = df_no_dup['uid'].shift(-1) df_no_dup['uid_rm_dups'] = list(map(df_match, df_no_dup['uid'], df_no_dup['uid2'])) df_no_dup.dropna(inplace=True) - message_volume = df_msgs["count"].resample(RESAMPLE).sum() user_id_counts_overall = Counter(df_no_dup[df_no_dup['channel'].isin(COMMUNITY_BASED_CHANNELS)]['uid'].values).most_common(MOST_COMMON_INT) @@ -168,7 +184,7 @@ async def user_metrics_background_task(): uids_in_help = Counter(df_no_dup[df_no_dup['channel'].isin(HELP_CHANNELS)]['uid'].values).most_common(MOST_COMMON_INT) #print(uids_in_help) - df = pd.read_csv(f"{path}/usermetrics.csv", names=['time', 'online', 'idle', 'offline']) + df = pd.read_csv(metrics_path, names=['time', 'online', 'idle', 'offline']) df = df[(df['time'] > time.time()-(86400*DAYS_BACK))] df['date'] = pd.to_datetime(df['time'],unit='s') df['total'] = df['online'] + df['offline'] + df['idle'] @@ -214,7 +230,7 @@ async def user_metrics_background_task(): ax1v.set_ylim(0, 3*df["count"].values.max()) #plt.show() - plt.savefig(f"{path}/online.png", facecolor = fig.get_facecolor()) + plt.savefig(os.path.join(local_files_path, "online.png"), facecolor = fig.get_facecolor()) plt.clf() @@ -264,10 +280,9 @@ async def user_metrics_background_task(): plt.yticks(y_pos, users) plt.subplots_adjust(left=0.30, bottom=0.15, right=0.99, top=0.95, wspace=0.2, hspace=0.55) - plt.savefig(f"{path}/activity.png", facecolor=fig.get_facecolor()) + plt.savefig(os.path.join(local_files_path, "activity.png"), facecolor=fig.get_facecolor()) plt.clf() - await asyncio.sleep(300) except Exception as e: @@ -275,22 +290,21 @@ async def user_metrics_background_task(): await asyncio.sleep(300) -@client.event # event decorator/wrapper +@bot.event # event decorator/wrapper async def on_ready(): - print(f"We have logged in as {client.user}") - await client.change_presence(status = discord.Status.online, activity = discord.Game('help(sentdebot)')) + print(f"We have logged in as {bot.user}") + await bot.change_presence(status = discord.Status.online, activity = discord.Game('help(sentdebot)')) -@client.event +@bot.event async def on_message(message): print(f"{message.channel}: {message.author}: {message.author.name}: {message.content}") - sentdex_guild = client.get_guild(405403391410438165) + sentdex_guild = bot.get_guild(SENTEX_GUILD_ID) author_roles = message.author.roles #print(author_roles) #author_role_ids = [r.id for r in author_roles] - if random.choice(range(500)) == 30: matches = [r for r in author_roles if r.id in vanity_role_ids] #print(matches) @@ -303,75 +317,70 @@ async def on_message(message): author_roles.append(actual_role_choice) await message.author.edit(roles=author_roles) except Exception as e: - print('EDITING ROLES ISSUE:',str(e)) + print('EDITING ROLES ISSUE:', str(e)) - - with open(f"{path}/msgs.csv","a") as f: + with open(os.path.join(local_files_path, 'msgs.csv'), "a") as f: if message.author.id not in chatbots: - f.write(f"{int(time.time())},{message.author.id},{message.channel}\n") + f.write(f"{int(time.time())}, {message.author.id}, {message.channel}\n") - with open(f"{path}/log.csv","a") as f: + with open(os.path.join(local_files_path, 'log.csv'), "a") as f: if message.author.id not in chatbots: try: - f.write(f"{int(time.time())},{message.author.id},{message.channel},{message.content}\n") + f.write(f"{int(time.time())}, {message.author.id}, {message.channel}, {message.content}\n") except Exception as e: f.write(f"{str(e)}\n") - if "sentdebot.member_count()" == message.content.lower(): await message.channel.send(f"```py\n{sentdex_guild.member_count}```") - elif "sentdebot.community_report()" == message.content.lower() and message.channel.id in image_chan_ids: online, idle, offline = community_report(sentdex_guild) - file = discord.File(f"{path}/online.png", filename=f"{path}/online.png") + file = discord.File(os.path.join(local_files_path, "online.png"), filename=f"online.png") await message.channel.send("", file=file) await message.channel.send(f'```py\n{{\n\t"Online": {online},\n\t"Idle/busy/dnd": {idle},\n\t"Offline": {offline}\n}}```') - + elif "sentdebot.p6()" == message.content.lower(): await message.channel.send(f"```\nThe Neural Networks from Scratch video series will resume when the NNFS book is completed. This means the videos will resume around Sept or Oct 2020.\n\nIf you are itching for the content, you can buy the book and get access to the draft now. The draft is over 500 pages, covering forward pass, activation functions, loss calcs, backward pass, optimization, train/test/validation for classification and regression. You can pre-order the book and get access to the draft via https://nnfs.io```") elif "sentdebot.user_activity()" == message.content.lower() and message.channel.id in image_chan_ids: # and len([r for r in author_roles if r.id in admins_mods_ids]) > 0: - - file = discord.File(f"{path}/activity.png", filename=f"{path}/activity.png") + file = discord.File(os.path.join(local_files_path, "activity.png"), filename=f"activity.png") await message.channel.send("", file=file) #await message.channel.send(f'```py\n{{\n\t"Online": {online},\n\t"Idle/busy/dnd": {idle},\n\t"Offline": {offline}\n}}```') - elif "help(sentdebot)" == message.content.lower() or "sentdebot.commands()" == message.content.lower(): await message.channel.send(commands_available) # if it doesnt work later. #elif "sentdebot.logout()" == message.content.lower() and message.author.id == 324953561416859658: - elif "sentdebot.logout()" == message.content.lower() and str(message.author).lower() == "sentdex#7777": - await client.close() - elif "sentdebot.gtfo()" == message.content.lower() and str(message.author).lower() == "sentdex#7777": - await client.close() + elif "sentdebot.logout()" == message.content.lower() and message.author.id == SENTDEX_ID: + await bot.close() + + elif "sentdebot.gtfo()" == message.content.lower() and message.author.id == SENTDEX_ID: + await bot.close() - elif "sentdebot.get_history()" == message.content.lower() and str(message.author).lower() == "sentdex#7777": + elif "sentdebot.get_history()" == message.content.lower() and message.author.id == SENTDEX_ID: - channel = sentdex_guild.get_channel(channel_ids[0]) + channel = sentdex_guild.get_channel(MAIN_CH_ID) async for message in channel.history(limit=999999999999999): - if message.author.id == 324953561416859658: - with open(f"{path}/history_out.csv", "a") as f: + if message.author.id == SENTDEX_ID: + with open(os.path.join(local_files_path, "history_out.csv"), "a") as f: f.write(f"{message.created_at},1\n") - - else: + elif message.content.lower().startswith("sentdebot.search"): query = search_term(message.content) + print(f"Query: {query}") if query: #query = match.group(1) print(query) - - qsearch = query.replace(" ","%20") + qsearch = query.replace(" ", "%20") full_link = f"https://pythonprogramming.net/search/?q={qsearch}" session = HTMLSession() r = session.get(full_link) @@ -388,7 +397,8 @@ async def on_message(message): Traceback (most recent call last): File "", line 1, in NotFoundError: {query} not found```""") + else: + await bot.process_commands(message) - -client.loop.create_task(user_metrics_background_task()) -client.run(token, reconnect=True) +bot.loop.create_task(user_metrics_background_task()) +bot.run(token) From e1ef562fc5d20255059c0dd7a62c61a565937d56 Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 21 Nov 2020 03:01:50 +0100 Subject: [PATCH 2/4] Rearange into new files --- modules/background_tasks.py | 200 ++++++++++++++++++ modules/bot.py | 153 ++++++++++++++ modules/definitions.py | 78 +++++++ sentdebot.py | 407 +----------------------------------- 4 files changed, 438 insertions(+), 400 deletions(-) create mode 100644 modules/background_tasks.py create mode 100644 modules/bot.py create mode 100644 modules/definitions.py diff --git a/modules/background_tasks.py b/modules/background_tasks.py new file mode 100644 index 0000000..449fad1 --- /dev/null +++ b/modules/background_tasks.py @@ -0,0 +1,200 @@ +import matplotlib.pyplot as plt +import matplotlib.ticker as mticker +import matplotlib.dates as mdates +import asyncio +import discord +import pandas as pd +import random +import numpy as np +import time +import os +import re + +# py -3.6 -m pip install --upgrade requests-html +from collections import Counter + +from .definitions import local_files_path +from .definitions import bot +from .definitions import SENTEX_GUILD_ID +from .definitions import DAYS_BACK, MOST_COMMON_INT, RESAMPLE, DISCORD_BG_COLOR +from .definitions import COMMUNITY_BASED_CHANNELS, HELP_CHANNELS + + +def community_report(guild): + """ + + Args: + guild: + + Returns: + online: int + idle: int + offline: int + """ + online = 0 + idle = 0 + offline = 0 + + for m in guild.members: + if str(m.status) == "online": + online += 1 + elif str(m.status) == "offline": + offline += 1 + else: + idle += 1 + + return online, idle, offline + + +def df_match(c1, c2): + if c1 == c2: + return np.nan + else: + return c1 + + +async def user_metrics_background_task(): + await bot.wait_until_ready() + global sentdex_guild + sentdex_guild = bot.get_guild(SENTEX_GUILD_ID) + + while not bot.is_closed(): + try: + online, idle, offline = community_report(sentdex_guild) + metrics_path = os.path.join(local_files_path, "usermetrics.csv") + with open(metrics_path, "a") as f: + f.write(f"{int(time.time())},{online},{idle},{offline}\n") + + df_msgs = pd.read_csv(os.path.join(local_files_path, "msgs.csv"), + names=['time', 'uid', 'channel'] + ) + df_msgs = df_msgs[(df_msgs['time'] > time.time() - (86400 * DAYS_BACK))] + df_msgs['count'] = 1 + df_msgs['date'] = pd.to_datetime(df_msgs['time'], unit='s') + df_msgs.drop("time", 1, inplace=True) + df_msgs.set_index("date", inplace=True) + + df_no_dup = df_msgs.copy() + df_no_dup['uid2'] = df_no_dup['uid'].shift(-1) + df_no_dup['uid_rm_dups'] = list(map(df_match, df_no_dup['uid'], df_no_dup['uid2'])) + + df_no_dup.dropna(inplace=True) + + message_volume = df_msgs["count"].resample(RESAMPLE).sum() + + user_id_counts_overall = Counter( + df_no_dup[df_no_dup['channel'].isin(COMMUNITY_BASED_CHANNELS)]['uid'].values).most_common( + MOST_COMMON_INT) + # print(user_id_counts_overall) + + uids_in_help = Counter(df_no_dup[df_no_dup['channel'].isin(HELP_CHANNELS)]['uid'].values).most_common( + MOST_COMMON_INT) + # print(uids_in_help) + + df = pd.read_csv(metrics_path, names=['time', 'online', 'idle', 'offline']) + df = df[(df['time'] > time.time() - (86400 * DAYS_BACK))] + df['date'] = pd.to_datetime(df['time'], unit='s') + df['total'] = df['online'] + df['offline'] + df['idle'] + df.drop("time", 1, inplace=True) + df.set_index("date", inplace=True) + + df = df.resample(RESAMPLE).mean() + df = df.join(message_volume) + + df.dropna(inplace=True) + # print(df.head()) + + fig = plt.figure(facecolor=DISCORD_BG_COLOR) + ax1 = plt.subplot2grid((2, 1), (0, 0)) + plt.ylabel("Active Users") + plt.title("Community Report") + ax1.set_facecolor(DISCORD_BG_COLOR) + ax1v = ax1.twinx() + plt.ylabel("Message Volume") + # ax1v.set_facecolor(DISCORD_BG_COLOR) + ax2 = plt.subplot2grid((2, 1), (1, 0)) + plt.ylabel("Total Users") + ax2.set_facecolor(DISCORD_BG_COLOR) + + ax1.plot(df.index, df.online, label="Active Users\n(Not Idle)") + # ax1v.bar(df.index, df["count"], width=0.01) + + ax1v.fill_between(df.index, 0, df["count"], facecolor="w", alpha=0.2, label="Message Volume") + ax1.legend(loc=2) + ax1v.legend(loc=9) + + ax2.plot(df.index, df.total, label="Total Users") + ax2.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d\n%H:%M')) + + # for label in ax2.xaxis.get_ticklabels(): + # label.set_rotation(45) + ax2.xaxis.set_major_locator(mticker.MaxNLocator(nbins=5, prune='lower')) + ax2.legend() + + plt.subplots_adjust(left=0.11, bottom=0.10, right=0.89, top=0.95, wspace=0.2, hspace=0) + ax1.get_xaxis().set_visible(False) + + ax1v.set_ylim(0, 3 * df["count"].values.max()) + + # plt.show() + plt.savefig(os.path.join(local_files_path, "online.png"), facecolor=fig.get_facecolor()) + plt.clf() + + fig = plt.figure(facecolor=DISCORD_BG_COLOR) + ax1 = plt.subplot2grid((2, 1), (0, 0)) + + plt.xlabel("Message Volume") + plt.title(f"General User Activity (past {DAYS_BACK} days)") + ax1.set_facecolor(DISCORD_BG_COLOR) + + users = [] + msgs = [] + for pair in user_id_counts_overall[::-1]: + try: + users.append(sentdex_guild.get_member(pair[0]).name) # get member name from here + if "Dhanos" in sentdex_guild.get_member(pair[0]).name: + msgs.append(pair[1] / 1.0) + else: + msgs.append(pair[1]) + except Exception as e: + print(str(e)) + y_pos = np.arange(len(users)) + ax1.barh(y_pos, msgs, align='center', alpha=0.5) + plt.yticks(y_pos, users) + + ax2 = plt.subplot2grid((2, 1), (1, 0)) + plt.title(f"Help Channel Activity (past {DAYS_BACK} days)") + plt.xlabel("Help Channel\nMsg Volume") + ax2.set_facecolor(DISCORD_BG_COLOR) + + users = [] + msgs = [] + for pair in uids_in_help[::-1]: + try: + users.append(sentdex_guild.get_member(pair[0]).name) # get member name from here + + if "Dhanos" in sentdex_guild.get_member(pair[0]).name: + msgs.append(pair[1] / 1.0) + else: + msgs.append(pair[1]) + # users.append(pair[0]) + except Exception as e: + print(str(e)) + + y_pos = np.arange(len(users)) + ax2.barh(y_pos, msgs, align='center', alpha=0.5) + plt.yticks(y_pos, users) + + plt.subplots_adjust(left=0.30, bottom=0.15, right=0.99, top=0.95, wspace=0.2, hspace=0.55) + plt.savefig(os.path.join(local_files_path, "activity.png"), facecolor=fig.get_facecolor()) + plt.clf() + + await asyncio.sleep(300) + + except Exception as e: + print(str(e)) + await asyncio.sleep(300) + + +def start_all_background_tasks(): + bot.loop.create_task(user_metrics_background_task()) diff --git a/modules/bot.py b/modules/bot.py new file mode 100644 index 0000000..a5aaf34 --- /dev/null +++ b/modules/bot.py @@ -0,0 +1,153 @@ +import discord +import random +import time +import os +import re + +# py -3.6 -m pip install --upgrade requests-html +from requests_html import HTMLSession +from matplotlib import style + +from .definitions import bot, local_files_path +from .definitions import SENTDEX_ID, SENTEX_GUILD_ID +from .definitions import MAIN_CH_ID, IMAGE_CHAN_IDS, VANITY_ROLE_IDS, CHATBOTS +from .background_tasks import community_report + +style.use("dark_background") + +commands_available = """```py +def commands(): + return { + sentdebot.member_count(): 'get member count', + sentdebot.community_report(): 'get some stats on community', + sentdebot.search("QUERY"): 'search for a string', + sentdebot.commands(): 'get commands', + sentdebot.logout(): 'Sentdex-only command to log Sentdebot off' + sentdebot.user_activity(): 'See some stats on top users', + } +```""" + + +def search_term(message): + try: + match = re.fullmatch('sentdebot.search\((["|\'])([^\)]+)(["|\'])\)', message) + if match.group(1) != match.group(3): + return False + for check in re.finditer(match.group(1), match.group(2)): + if match.group(2)[check.start() - 1] != '\\': + return False + return match.group(2) + except Exception: + return False + + +@bot.event # event decorator/wrapper +async def on_ready(): + print(f"We have logged in as {bot.user}") + await bot.change_presence(status=discord.Status.online, activity=discord.Game('help(sentdebot)')) + + +@bot.event +async def on_message(message): + print(f"{message.channel}: {message.author}: {message.author.name}: {message.content}") + sentdex_guild = bot.get_guild(SENTEX_GUILD_ID) + author_roles = message.author.roles + # print(author_roles) + # author_role_ids = [r.id for r in author_roles] + + if random.choice(range(500)) == 30: + matches = [r for r in author_roles if r.id in VANITY_ROLE_IDS] + # print(matches) + + if len(matches) == 0: + try: + role_id_choice = random.choice(VANITY_ROLE_IDS) + actual_role_choice = sentdex_guild.get_role(role_id_choice) + # print(type(message.author)) + author_roles.append(actual_role_choice) + await message.author.edit(roles=author_roles) + except Exception as e: + print('EDITING ROLES ISSUE:', str(e)) + + with open(os.path.join(local_files_path, 'msgs.csv'), "a") as f: + if message.author.id not in CHATBOTS: + f.write(f"{int(time.time())}, {message.author.id}, {message.channel}\n") + + with open(os.path.join(local_files_path, 'log.csv'), "a") as f: + if message.author.id not in CHATBOTS: + try: + f.write(f"{int(time.time())}, {message.author.id}, {message.channel}, {message.content}\n") + except Exception as e: + f.write(f"{str(e)}\n") + + if "sentdebot.member_count()" == message.content.lower(): + await message.channel.send(f"```py\n{sentdex_guild.member_count}```") + + + elif "sentdebot.community_report()" == message.content.lower() and message.channel.id in IMAGE_CHAN_IDS: + online, idle, offline = community_report(sentdex_guild) + + file = discord.File(os.path.join(local_files_path, "online.png"), filename=f"online.png") + await message.channel.send("", file=file) + + await message.channel.send( + f'```py\n{{\n\t"Online": {online},\n\t"Idle/busy/dnd": {idle},\n\t"Offline": {offline}\n}}```') + + elif "sentdebot.p6()" == message.content.lower(): + await message.channel.send( + f"```\nThe Neural Networks from Scratch video series will resume when the NNFS book is completed. This means the videos will resume around Sept or Oct 2020.\n\nIf you are itching for the content, you can buy the book and get access to the draft now. The draft is over 500 pages, covering forward pass, activation functions, loss calcs, backward pass, optimization, train/test/validation for classification and regression. You can pre-order the book and get access to the draft via https://nnfs.io```") + + elif "sentdebot.user_activity()" == message.content.lower() and message.channel.id in IMAGE_CHAN_IDS: # and len([r for r in author_roles if r.id in admins_mods_ids]) > 0: + file = discord.File(os.path.join(local_files_path, "activity.png"), filename=f"activity.png") + await message.channel.send("", file=file) + + # await message.channel.send(f'```py\n{{\n\t"Online": {online},\n\t"Idle/busy/dnd": {idle},\n\t"Offline": {offline}\n}}```') + + elif "help(sentdebot)" == message.content.lower() or "sentdebot.commands()" == message.content.lower(): + await message.channel.send(commands_available) + + # if it doesnt work later. + # elif "sentdebot.logout()" == message.content.lower() and message.author.id == 324953561416859658: + elif "sentdebot.logout()" == message.content.lower() and message.author.id == SENTDEX_ID: + await bot.close() + + elif "sentdebot.gtfo()" == message.content.lower() and message.author.id == SENTDEX_ID: + await bot.close() + + elif "sentdebot.get_history()" == message.content.lower() and message.author.id == SENTDEX_ID: + + channel = sentdex_guild.get_channel(MAIN_CH_ID) + + async for message in channel.history(limit=999999999999999): + if message.author.id == SENTDEX_ID: + with open(os.path.join(local_files_path, "history_out.csv"), "a") as f: + f.write(f"{message.created_at},1\n") + + elif message.content.lower().startswith("sentdebot.search"): + query = search_term(message.content) + print(f"Query: {query}") + if query: + # query = match.group(1) + print(query) + + qsearch = query.replace(" ", "%20") + full_link = f"https://pythonprogramming.net/search/?q={qsearch}" + session = HTMLSession() + r = session.get(full_link) + + specific_tutorials = [(tut.text, list(tut.links)[0]) for tut in r.html.find("a") if + "collection-item" in tut.html] + + if len(specific_tutorials) > 0: + return_str = "\n---------------------------------------\n".join( + f'{tut[0]}: ' for tut in specific_tutorials[:3]) + return_str = f"```Searching for '{query}'```\n" + return_str + f"\n----\n...More results: <{full_link}>" + + await message.channel.send(return_str) + else: + await message.channel.send(f"""```py +Traceback (most recent call last): + File "", line 1, in +NotFoundError: {query} not found```""") + else: + await bot.process_commands(message) diff --git a/modules/definitions.py b/modules/definitions.py new file mode 100644 index 0000000..7d58410 --- /dev/null +++ b/modules/definitions.py @@ -0,0 +1,78 @@ +import discord +import os + +from discord.ext.commands import Bot + +local_files_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'sentdebot')) + +SENTEX_GUILD_ID = 405403391410438165 +SENTDEX_ID = 324953561416859658 +DHAN_ID = 150409781595602946 +MAIN_CH_ID = 408713676095488000 +DOGS_CH_ID = 671016601440747520 +HELP_CH_ID_0 = 412620789133606914 +HELP_CH_ID_1 = 507281643606638622 +HELP_CH_ID_2 = 682674227664388146 +VOICE2TEXT_CH_ID = 484406428233367562 +HELLO_CHARLES_CH_ID = 407958260168130570 + +IMAGE_CHAN_IDS = [MAIN_CH_ID, + HELP_CH_ID_0, + HELP_CH_ID_1, + HELP_CH_ID_2, + 476412789184004096, # ? + 499945870473691146, # ? + VOICE2TEXT_CH_ID, + ] + +CHATBOTS = [405511726050574336, # Charles + 428904098985803776, # Irene + 414630095911780353, # Charlene (Charles' alter ego) + 500507500962119681] # ? + +SENTDEBOT_ID = 499921685051342848 + +admin_id = 405506750654054401 +mod_id = 405520180974714891 + +admins_mods_ids = [admin_id, mod_id] +# condition to restrict a command to admins/mods: len([r for r in author_roles if r.id in admins_mods_ids]) > 0 + +VANITY_ROLE_IDS = [479433667576332289, + 501115401577431050, + 501115079572324352, + 501114732057460748, + 501115820273958912, + 501114928854204431, + 479433212561719296] + + +MOST_COMMON_INT = 10 # when doing counters of activity, top n users. +DAYS_BACK = 21 # days of history to work with +RESAMPLE = "60min" + +# names of channels where we want to count activity. +COMMUNITY_BASED_CHANNELS = ["__main__", + "main", + "help", + "help_0", + "help_1", + "help_2", + "help_3", + "voice-channel-text", + "__init__", + "hello_technical_questions", + "help_overflow"] + +HELP_CHANNELS = ["help", + "hello_technical_questions", + "help_0", + "help_1", + "help_2", + "help_3", + "help_overflow"] + +DISCORD_BG_COLOR = '#36393E' + +intents = discord.Intents.all() +bot = Bot(command_prefix="!", ignore_case=True, intents=intents) diff --git a/sentdebot.py b/sentdebot.py index 39f5fba..0de579c 100644 --- a/sentdebot.py +++ b/sentdebot.py @@ -1,404 +1,11 @@ -import matplotlib.pyplot as plt -import matplotlib.ticker as mticker -import matplotlib.dates as mdates -import asyncio -import discord -import pandas as pd -import random -import numpy as np -import time import os -import re -# py -3.6 -m pip install --upgrade requests-html -from requests_html import HTMLSession -from collections import Counter -from matplotlib import style -from discord.ext.commands import Bot +from modules.background_tasks import start_all_background_tasks +from modules.bot import bot -style.use("dark_background") +if __name__ == "__main__": + token_path = os.path.abspath(os.path.join('sentdebot', 'token.txt')) + token = open(f"{token_path}", "r").read().split('\n')[0] - -token_path = os.path.abspath(os.path.join('sentdebot', 'token.txt')) -local_files_path = os.path.abspath(os.path.join('sentdebot')) - -SENTEX_GUILD_ID = 405403391410438165 -SENTDEX_ID = 324953561416859658 -DHAN_ID = 150409781595602946 - -# days of history to work with -DAYS_BACK = 21 -RESAMPLE = "60min" - -# when doing counters of activity, top n users. -MOST_COMMON_INT = 10 -# names of channels where we want to count activity. -COMMUNITY_BASED_CHANNELS = ["__main__", - "main", - "help", - "help_0", - "help_1", - "help_2", - "help_3", - "voice-channel-text", - "__init__", - "hello_technical_questions", - "help_overflow"] - -HELP_CHANNELS = ["help", - "hello_technical_questions", - "help_0", - "help_1", - "help_2", - "help_3", - "help_overflow"] - -DISCORD_BG_COLOR = '#36393E' - -intents = discord.Intents.all() - -bot = Bot(command_prefix="!", ignore_case=True, intents=intents) - -token = open(f"{token_path}", "r").read().split('\n')[0] - -commands_available = """```py -def commands(): - return { - sentdebot.member_count(): 'get member count', - sentdebot.community_report(): 'get some stats on community', - sentdebot.search("QUERY"): 'search for a string', - sentdebot.commands(): 'get commands', - sentdebot.logout(): 'Sentdex-only command to log Sentdebot off' - sentdebot.user_activity(): 'See some stats on top users', - } -```""" - - -MAIN_CH_ID = 408713676095488000 -DOGS_CH_ID = 671016601440747520 -HELP_CH_ID_0 = 412620789133606914 -HELP_CH_ID_1 = 507281643606638622 -HELP_CH_ID_2 = 682674227664388146 -VOICE2TEXT_CH_ID = 484406428233367562 -HELLO_CHARLES_CH_ID = 407958260168130570 - -image_chan_ids = [MAIN_CH_ID, - HELP_CH_ID_0, - HELP_CH_ID_1, - HELP_CH_ID_2, - 476412789184004096, # ? - 499945870473691146, # ? - VOICE2TEXT_CH_ID, - ] - -chatbots = [405511726050574336, # Charles - 428904098985803776, # Irene - 414630095911780353, # Charlene (Charles' alter ego) - 500507500962119681] # ? - -SENTDEBOT_ID = 499921685051342848 - -admin_id = 405506750654054401 -mod_id = 405520180974714891 - -admins_mods_ids = [admin_id, mod_id] -# condition to restrict a command to admins/mods: len([r for r in author_roles if r.id in admins_mods_ids]) > 0 - -vanity_role_ids = [479433667576332289, - 501115401577431050, - 501115079572324352, - 501114732057460748, - 501115820273958912, - 501114928854204431, - 479433212561719296] - - -def search_term(message): - try: - match = re.fullmatch('sentdebot.search\((["|\'])([^\)]+)(["|\'])\)', message) - if match.group(1) != match.group(3): - return False - for check in re.finditer(match.group(1), match.group(2)): - if match.group(2)[check.start()-1] != '\\': - return False - return match.group(2) - except Exception: - return False - - -def community_report(guild): - online = 0 - idle = 0 - offline = 0 - - for m in guild.members: - if str(m.status) == "online": - online += 1 - elif str(m.status) == "offline": - offline += 1 - else: - idle += 1 - - return online, idle, offline - - -def df_match(c1, c2): - if c1 == c2: - return np.nan - else: - return c1 - - -async def user_metrics_background_task(): - await bot.wait_until_ready() - global sentdex_guild - sentdex_guild = bot.get_guild(SENTEX_GUILD_ID) - - while not bot.is_closed(): - try: - online, idle, offline = community_report(sentdex_guild) - metrics_path = os.path.join(local_files_path, "usermetrics.csv") - with open(metrics_path, "a") as f: - f.write(f"{int(time.time())},{online},{idle},{offline}\n") - - df_msgs = pd.read_csv(os.path.join(local_files_path, "msgs.csv"), - names=['time', 'uid', 'channel'] - ) - df_msgs = df_msgs[(df_msgs['time'] > time.time()-(86400*DAYS_BACK))] - df_msgs['count'] = 1 - df_msgs['date'] = pd.to_datetime(df_msgs['time'], unit='s') - df_msgs.drop("time", 1, inplace=True) - df_msgs.set_index("date", inplace=True) - - df_no_dup = df_msgs.copy() - df_no_dup['uid2'] = df_no_dup['uid'].shift(-1) - df_no_dup['uid_rm_dups'] = list(map(df_match, df_no_dup['uid'], df_no_dup['uid2'])) - - df_no_dup.dropna(inplace=True) - - message_volume = df_msgs["count"].resample(RESAMPLE).sum() - - user_id_counts_overall = Counter(df_no_dup[df_no_dup['channel'].isin(COMMUNITY_BASED_CHANNELS)]['uid'].values).most_common(MOST_COMMON_INT) - #print(user_id_counts_overall) - - uids_in_help = Counter(df_no_dup[df_no_dup['channel'].isin(HELP_CHANNELS)]['uid'].values).most_common(MOST_COMMON_INT) - #print(uids_in_help) - - df = pd.read_csv(metrics_path, names=['time', 'online', 'idle', 'offline']) - df = df[(df['time'] > time.time()-(86400*DAYS_BACK))] - df['date'] = pd.to_datetime(df['time'],unit='s') - df['total'] = df['online'] + df['offline'] + df['idle'] - df.drop("time", 1, inplace=True) - df.set_index("date", inplace=True) - - df = df.resample(RESAMPLE).mean() - df = df.join(message_volume) - - df.dropna(inplace=True) - #print(df.head()) - - fig = plt.figure(facecolor=DISCORD_BG_COLOR) - ax1 = plt.subplot2grid((2, 1), (0, 0)) - plt.ylabel("Active Users") - plt.title("Community Report") - ax1.set_facecolor(DISCORD_BG_COLOR) - ax1v = ax1.twinx() - plt.ylabel("Message Volume") - #ax1v.set_facecolor(DISCORD_BG_COLOR) - ax2 = plt.subplot2grid((2, 1), (1, 0)) - plt.ylabel("Total Users") - ax2.set_facecolor(DISCORD_BG_COLOR) - - ax1.plot(df.index, df.online, label="Active Users\n(Not Idle)") - #ax1v.bar(df.index, df["count"], width=0.01) - - ax1v.fill_between(df.index, 0, df["count"], facecolor="w", alpha=0.2, label="Message Volume") - ax1.legend(loc=2) - ax1v.legend(loc=9) - - ax2.plot(df.index, df.total, label="Total Users") - ax2.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d\n%H:%M')) - - #for label in ax2.xaxis.get_ticklabels(): - # label.set_rotation(45) - ax2.xaxis.set_major_locator(mticker.MaxNLocator(nbins=5, prune='lower')) - ax2.legend() - - plt.subplots_adjust(left=0.11, bottom=0.10, right=0.89, top=0.95, wspace=0.2, hspace=0) - ax1.get_xaxis().set_visible(False) - - ax1v.set_ylim(0, 3*df["count"].values.max()) - - #plt.show() - plt.savefig(os.path.join(local_files_path, "online.png"), facecolor = fig.get_facecolor()) - plt.clf() - - - fig = plt.figure(facecolor=DISCORD_BG_COLOR) - ax1 = plt.subplot2grid((2, 1), (0, 0)) - - plt.xlabel("Message Volume") - plt.title(f"General User Activity (past {DAYS_BACK} days)") - ax1.set_facecolor(DISCORD_BG_COLOR) - - users = [] - msgs = [] - for pair in user_id_counts_overall[::-1]: - try: - users.append(sentdex_guild.get_member(pair[0]).name) # get member name from here - if "Dhanos" in sentdex_guild.get_member(pair[0]).name: - msgs.append(pair[1]/1.0) - else: - msgs.append(pair[1]) - except Exception as e: - print(str(e)) - y_pos = np.arange(len(users)) - ax1.barh(y_pos, msgs, align='center', alpha=0.5) - plt.yticks(y_pos, users) - - ax2 = plt.subplot2grid((2, 1), (1, 0)) - plt.title(f"Help Channel Activity (past {DAYS_BACK} days)") - plt.xlabel("Help Channel\nMsg Volume") - ax2.set_facecolor(DISCORD_BG_COLOR) - - users = [] - msgs = [] - for pair in uids_in_help[::-1]: - try: - users.append(sentdex_guild.get_member(pair[0]).name) # get member name from here - - if "Dhanos" in sentdex_guild.get_member(pair[0]).name: - msgs.append(pair[1]/1.0) - else: - msgs.append(pair[1]) - #users.append(pair[0]) - except Exception as e: - print(str(e)) - - y_pos = np.arange(len(users)) - ax2.barh(y_pos, msgs, align='center', alpha=0.5) - plt.yticks(y_pos, users) - - plt.subplots_adjust(left=0.30, bottom=0.15, right=0.99, top=0.95, wspace=0.2, hspace=0.55) - plt.savefig(os.path.join(local_files_path, "activity.png"), facecolor=fig.get_facecolor()) - plt.clf() - - await asyncio.sleep(300) - - except Exception as e: - print(str(e)) - await asyncio.sleep(300) - - -@bot.event # event decorator/wrapper -async def on_ready(): - print(f"We have logged in as {bot.user}") - await bot.change_presence(status = discord.Status.online, activity = discord.Game('help(sentdebot)')) - - -@bot.event -async def on_message(message): - print(f"{message.channel}: {message.author}: {message.author.name}: {message.content}") - sentdex_guild = bot.get_guild(SENTEX_GUILD_ID) - author_roles = message.author.roles - #print(author_roles) - #author_role_ids = [r.id for r in author_roles] - - - if random.choice(range(500)) == 30: - matches = [r for r in author_roles if r.id in vanity_role_ids] - #print(matches) - - if len(matches) == 0: - try: - role_id_choice = random.choice(vanity_role_ids) - actual_role_choice = sentdex_guild.get_role(role_id_choice) - #print(type(message.author)) - author_roles.append(actual_role_choice) - await message.author.edit(roles=author_roles) - except Exception as e: - print('EDITING ROLES ISSUE:', str(e)) - - with open(os.path.join(local_files_path, 'msgs.csv'), "a") as f: - if message.author.id not in chatbots: - f.write(f"{int(time.time())}, {message.author.id}, {message.channel}\n") - - with open(os.path.join(local_files_path, 'log.csv'), "a") as f: - if message.author.id not in chatbots: - try: - f.write(f"{int(time.time())}, {message.author.id}, {message.channel}, {message.content}\n") - except Exception as e: - f.write(f"{str(e)}\n") - - if "sentdebot.member_count()" == message.content.lower(): - await message.channel.send(f"```py\n{sentdex_guild.member_count}```") - - - elif "sentdebot.community_report()" == message.content.lower() and message.channel.id in image_chan_ids: - online, idle, offline = community_report(sentdex_guild) - - file = discord.File(os.path.join(local_files_path, "online.png"), filename=f"online.png") - await message.channel.send("", file=file) - - await message.channel.send(f'```py\n{{\n\t"Online": {online},\n\t"Idle/busy/dnd": {idle},\n\t"Offline": {offline}\n}}```') - - elif "sentdebot.p6()" == message.content.lower(): - await message.channel.send(f"```\nThe Neural Networks from Scratch video series will resume when the NNFS book is completed. This means the videos will resume around Sept or Oct 2020.\n\nIf you are itching for the content, you can buy the book and get access to the draft now. The draft is over 500 pages, covering forward pass, activation functions, loss calcs, backward pass, optimization, train/test/validation for classification and regression. You can pre-order the book and get access to the draft via https://nnfs.io```") - - - elif "sentdebot.user_activity()" == message.content.lower() and message.channel.id in image_chan_ids: # and len([r for r in author_roles if r.id in admins_mods_ids]) > 0: - file = discord.File(os.path.join(local_files_path, "activity.png"), filename=f"activity.png") - await message.channel.send("", file=file) - - #await message.channel.send(f'```py\n{{\n\t"Online": {online},\n\t"Idle/busy/dnd": {idle},\n\t"Offline": {offline}\n}}```') - - - elif "help(sentdebot)" == message.content.lower() or "sentdebot.commands()" == message.content.lower(): - await message.channel.send(commands_available) - - # if it doesnt work later. - #elif "sentdebot.logout()" == message.content.lower() and message.author.id == 324953561416859658: - elif "sentdebot.logout()" == message.content.lower() and message.author.id == SENTDEX_ID: - await bot.close() - - elif "sentdebot.gtfo()" == message.content.lower() and message.author.id == SENTDEX_ID: - await bot.close() - - elif "sentdebot.get_history()" == message.content.lower() and message.author.id == SENTDEX_ID: - - channel = sentdex_guild.get_channel(MAIN_CH_ID) - - async for message in channel.history(limit=999999999999999): - if message.author.id == SENTDEX_ID: - with open(os.path.join(local_files_path, "history_out.csv"), "a") as f: - f.write(f"{message.created_at},1\n") - - elif message.content.lower().startswith("sentdebot.search"): - query = search_term(message.content) - print(f"Query: {query}") - if query: - #query = match.group(1) - print(query) - - qsearch = query.replace(" ", "%20") - full_link = f"https://pythonprogramming.net/search/?q={qsearch}" - session = HTMLSession() - r = session.get(full_link) - - specific_tutorials = [(tut.text, list(tut.links)[0]) for tut in r.html.find("a") if "collection-item" in tut.html] - - if len(specific_tutorials) > 0: - return_str = "\n---------------------------------------\n".join(f'{tut[0]}: ' for tut in specific_tutorials[:3]) - return_str = f"```Searching for '{query}'```\n" + return_str + f"\n----\n...More results: <{full_link}>" - - await message.channel.send(return_str) - else: - await message.channel.send(f"""```py -Traceback (most recent call last): - File "", line 1, in -NotFoundError: {query} not found```""") - else: - await bot.process_commands(message) - -bot.loop.create_task(user_metrics_background_task()) -bot.run(token) + start_all_background_tasks() + bot.run(token) From dae2015bd5267ee6d607f4e96ddd0c307ba47855 Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 21 Nov 2020 05:05:06 +0100 Subject: [PATCH 3/4] Added Poll, Mines, NNFS embed, Decorators, Permissions, Error handler, Emojis --- modules/bot.py | 293 +++++++++++++++++- modules/decorators.py | 237 +++++++++++++++ modules/definitions.py | 19 +- modules/emojis.py | 674 +++++++++++++++++++++++++++++++++++++++++ modules/minigames.py | 72 +++++ modules/permissions.py | 37 +++ 6 files changed, 1315 insertions(+), 17 deletions(-) create mode 100644 modules/decorators.py create mode 100644 modules/emojis.py create mode 100644 modules/minigames.py create mode 100644 modules/permissions.py diff --git a/modules/bot.py b/modules/bot.py index a5aaf34..c3fb3fc 100644 --- a/modules/bot.py +++ b/modules/bot.py @@ -1,3 +1,5 @@ +import traceback +import asyncio import discord import random import time @@ -7,11 +9,19 @@ # py -3.6 -m pip install --upgrade requests-html from requests_html import HTMLSession from matplotlib import style +from discord.ext.commands import CommandError +from discord import Embed, Colour -from .definitions import bot, local_files_path +from .definitions import bot, local_files_path, HELLO_TEXTS from .definitions import SENTDEX_ID, SENTEX_GUILD_ID from .definitions import MAIN_CH_ID, IMAGE_CHAN_IDS, VANITY_ROLE_IDS, CHATBOTS + +from .permissions import is_called_in_bot_channel, is_priv, is_not_priv, is_admin +from .decorators import advanced_perm_check_function, advanced_args_function + from .background_tasks import community_report +from .minigames import mines +from .emojis import EMOJIS, HAPPY_FACES style.use("dark_background") @@ -51,24 +61,27 @@ async def on_ready(): async def on_message(message): print(f"{message.channel}: {message.author}: {message.author.name}: {message.content}") sentdex_guild = bot.get_guild(SENTEX_GUILD_ID) - author_roles = message.author.roles + # print(author_roles) # author_role_ids = [r.id for r in author_roles] if random.choice(range(500)) == 30: - matches = [r for r in author_roles if r.id in VANITY_ROLE_IDS] - # print(matches) - - if len(matches) == 0: - try: - role_id_choice = random.choice(VANITY_ROLE_IDS) - actual_role_choice = sentdex_guild.get_role(role_id_choice) - # print(type(message.author)) - author_roles.append(actual_role_choice) - await message.author.edit(roles=author_roles) - except Exception as e: - print('EDITING ROLES ISSUE:', str(e)) - + try: + author_roles = message.author.roles + matches = [r for r in author_roles if r.id in VANITY_ROLE_IDS] + # print(matches) + + if len(matches) == 0: + try: + role_id_choice = random.choice(VANITY_ROLE_IDS) + actual_role_choice = sentdex_guild.get_role(role_id_choice) + # print(type(message.author)) + author_roles.append(actual_role_choice) + await message.author.edit(roles=author_roles) + except Exception as e: + print('EDITING ROLES ISSUE:', str(e)) + except Exception: + pass with open(os.path.join(local_files_path, 'msgs.csv'), "a") as f: if message.author.id not in CHATBOTS: f.write(f"{int(time.time())}, {message.author.id}, {message.channel}\n") @@ -83,7 +96,6 @@ async def on_message(message): if "sentdebot.member_count()" == message.content.lower(): await message.channel.send(f"```py\n{sentdex_guild.member_count}```") - elif "sentdebot.community_report()" == message.content.lower() and message.channel.id in IMAGE_CHAN_IDS: online, idle, offline = community_report(sentdex_guild) @@ -149,5 +161,254 @@ async def on_message(message): Traceback (most recent call last): File "", line 1, in NotFoundError: {query} not found```""") + + elif bot.user in message.mentions and not message.content.startswith("!"): + ch = message.channel + text = random.choice(HELLO_TEXTS) + emote = random.choice(HAPPY_FACES) + await ch.send(text.format(message.author.name, emote=emote)) else: await bot.process_commands(message) + + +async def get_last_message(channel_id, user_id, limit=10): + channel = bot.get_channel(channel_id) + messages = await channel.history(limit=limit).flatten() + message = None + for msg in messages: + if msg.author.id == user_id: + message = msg + break + return message + + +@bot.event +async def on_command_error(ctx, command_error): + text = ctx.message.content + invoked = ctx.invoked_with + text_error = str(command_error) + server = "private_message" if not ctx.guild else f"{ctx.guild} ({ctx.guild.id})" + + if text_error.startswith("The check functions for command") or text_error.startswith("No permission"): + # logger.warning(f"No permission: '{text}', server: '{server}'") + emoji = "⛔" + await ctx.channel.send(f"Some permissions do not allow it to run here, '{text_error}' '!{invoked}'", + delete_after=30) + + elif text_error.endswith("is not found"): + # logger.warning(f"Command not found: '{text}', server: '{server}'") + emoji = "❓" + + elif text_error.startswith("Command raised an exception: RestrictedError"): + # logger.warning(f"Restricted usage: '{text}', server: '{server}'") + emoji = "⛔" + await ctx.channel.send(f"{command_error.original} '!{invoked}'", delete_after=30) + + elif text_error.startswith("Command raised an exception: CommandWithoutPermissions"): + # logger.error(f"Command is free to all users: '{text}', server: '{server}'") + emoji = "⛔" + await ctx.channel.send(f"Command is not checking any permissions, sorry. '!{invoked}'", delete_after=30) + + elif text_error.startswith("You are missing"): + # logger.warning(f"Missing permission: '{text_error}', server: '{server}'") + emoji = "❌" + await ctx.channel.send(f"{text_error} '!{invoked}'", delete_after=30) + + elif text_error.startswith("Command raised an exception: Forbidden: 403 Forbidden"): + # logger.warning(f"Bot is missing permissions: '{text_error}', server: '{server}', '!{invoked}'") + emoji = "❌" + await ctx.channel.send(f"Bot is missing permissions: '!{invoked}'", delete_after=30) + + elif text_error.startswith("Command raised an exception: NotImplementedError"): + # logger.warning(f"Not implemented feature: '{text_error}', server: '{server}', '!{invoked}'") + emoji = "❌" + await ctx.channel.send(f"Not implemented feature: '{text}'", delete_after=30) + + elif type(command_error) is CommandError: + # logger.warning(f"Command error: '{text_error}', server: '{server}', '!{invoked}'") + emoji = "❌" + await ctx.channel.send(f"{text_error}: '!{invoked}'") + + elif "required positional argument" in text_error: + emoji = "❌" + await ctx.channel.send(f"Some arguments are missing: '{command_error.original}'", delete_after=30) + + else: + tb_text = traceback.format_exception(type(command_error), command_error, command_error.__traceback__) + tb_text = ''.join([line for line in tb_text if f'bot{os.path.sep}' in line or f'app{os.path.sep}' in line]) + emoji = "❌" + # logger.error( + # f"No reaction for this error type:\n" + # f"Command: '{ctx.message.content}', server: '{server}', \n" + # f"'{command_error}'\n" + # f"Partial traceback:\n{tb_text}") + print( + f"No reaction for this error type:\n" + f"Command: '{ctx.message.content}', server: '{server}', \n" + f"'{command_error}'\n" + f"Partial traceback:\n{tb_text}") + + try: + await ctx.message.add_reaction(emoji=emoji) + except Exception: + pass + + +@bot.command() +@advanced_args_function(bot) +# @log_call_function +@advanced_perm_check_function(is_not_priv) +# @my_help.help_decorator("Poll with maximum 10 answers. Minimum 2 answers, maximum 10. timeout is optional", +# "? , ..., (timeout=)") +async def poll(ctx, *args, text, force=False, dry=False, timeout=3 * 60, **kwargs): + """ + Multi choice poll with maximum 10 answers + Example `!poll Question, answer1, answer2, .... answer10` + Available delimiters: . ; , + You can add more time with `timeout=200`, default 180, maximum is 3600 (1hour) + Use `-d` for dryrun + Args: + ctx: + *args: + dry: + **kwargs: + + Returns: + + """ + + # text = ' '.join(args) + # logger.debug(text) + arr_text = re.split(r"['.?;,]", text) + arr_text = [el.lstrip().rstrip() for el in arr_text if len(el) > 0] + poll_color = Colour.from_rgb(250, 165, 0) + finished_poll_color = Colour.from_rgb(30, 255, 0) + + timeout = float(timeout) + if timeout > 60 * 60 and not force: + timeout = 60 * 60 + + update_interval = 30 if 30 < timeout else timeout + + if len(arr_text) < 3: + await ctx.send(f"Got less than 1 questions and 2 answers, use some delimiter ?;,") + return None + + question, *answers = arr_text + question = question.title() + "?" + + if dry: + text = f"Question: {question}\n" + f"Answers {len(answers)}: {answers}\n" + f"Timeout: {timeout}" + await ctx.send(text) + return None + + elif len(answers) > 10: + confirm_message = await ctx.send( + f"Got more than 10 answers, sorry. If you want poll with 10 answers, press reaction.", + delete_after=60) + await confirm_message.add_reaction(EMOJIS["green_ok"]) + + def wait_for_confirm(reaction, _cur_user): + return str(reaction.emoji) == EMOJIS["green_ok"] \ + and _cur_user.id == ctx.author.id \ + and reaction.message.id == confirm_message.id + + try: + reaction, user = await bot.wait_for("reaction_add", check=wait_for_confirm, timeout=60) + answers = answers[0:10] + await confirm_message.delete() + except asyncio.TimeoutError: + return None + try: + await ctx.message.delete() + except Exception: + pass + + answer_dict = {} + + embed = Embed(title=question, colour=poll_color) + embed.set_author(name=ctx.author.name) + embed.set_thumbnail(url=ctx.author.avatar_url) + embed.set_footer(text=f"Time left: {timeout / 60:4.1f} min") + + for num, ans in enumerate(answers, 1): + emoji = EMOJIS[num] + answer_dict[emoji] = {'ans': ans.title(), 'votes': 0} + embed.add_field(name=emoji, value=ans, inline=False) + + poll = await ctx.send(embed=embed) + + await poll.add_reaction("🛑") + for num in range(1, len(answers) + 1): + await poll.add_reaction(EMOJIS[num]) + await asyncio.sleep(0.01) + + def check_end_reaction(reaction, user): + b1 = reaction.emoji == "🛑" + b2 = reaction.message.id == poll.id + b3 = user == ctx.message.author + return b1 and b2 and b3 + + end_time = time.time() + timeout + while time.time() <= end_time: + end_loop = False + try: + reaction, message = await bot.wait_for('reaction_add', check=check_end_reaction, timeout=update_interval) + end_loop = True + except asyncio.TimeoutError as err: + pass + + poll = await ctx.channel.fetch_message(poll.id) + # all_votes = 0 + all_users = [] + vote_emojis = [EMOJIS[num] for num in range(1, 11)] + for rc in poll.reactions: + if rc.emoji not in vote_emojis: + continue + users = [user async for user in rc.users()] + all_users += users + me = rc.me + count = rc.count - 1 if me else 0 + # all_votes += count + answer_dict[rc.emoji]['votes'] = count + + all_users_count = len(set(all_users)) - 1 + embed = Embed(title=question, colour=poll_color) + embed.set_author(name=ctx.author.name) + embed.set_thumbnail(url=ctx.author.avatar_url) + embed.set_footer(text=f"Time left: {(end_time - time.time()) / 60:4.1f} min") + for number in range(1, 11): + try: + emoji = EMOJIS[number] + ans = answer_dict[emoji]['ans'] + votes = answer_dict[emoji]['votes'] + # fraction = votes / all_votes * 100 if all_votes > 0 else 0 + fraction = votes / all_users_count * 100 if all_users_count > 0 else 0 + embed.add_field(name=f"{emoji} -`{fraction:<3.1f} %`", value=ans, inline=False) + except KeyError: + break + + await poll.edit(embed=embed) + await asyncio.sleep(0.01) + if end_loop: + break + + embed.colour = finished_poll_color + embed.set_footer(text="") + await poll.edit(content='🔒 Poll has ended', embed=embed) + await poll.clear_reaction("🛑") + + +@bot.command(aliases=['nnfs.io', 'nfs', 'nn']) +async def nnfs(ctx, *args, **kwargs): + color = Colour.from_rgb(10, 180, 50) + url = r'https://nnfs.io/' + desc = '"Neural Networks From Scratch" is a book intended to teach you how to build neural networks on your own, without any libraries, so you can better understand deep learning and how all of the elements work. This is so you can go out and do new/novel things with deep learning as well as to become more successful with even more basic models.' + embed = Embed(title=f"Click link", colour=color, url=url, description=None) + + sentdex = bot.get_user(SENTDEX_ID) + embed.set_thumbnail(url=sentdex.avatar_url) + embed.add_field(name="NNFS is out!", value=desc) + # embed.set_author(name="Sentdex") + embed.set_footer(text=f"for {ctx.author.name}", icon_url=ctx.author.avatar_url) + await ctx.send(embed=embed) diff --git a/modules/decorators.py b/modules/decorators.py new file mode 100644 index 0000000..e597771 --- /dev/null +++ b/modules/decorators.py @@ -0,0 +1,237 @@ +import discord +import asyncio +import random +import time +import re + +from .permissions import CommandWithoutPermissions +from .definitions import send_disapprove, send_approve + +from discord.ext.commands import CommandError +from discord import HTTPException, NotFound + + +def approve_fun(coro): + """ + Decorator that adds reaction if success, else x. + Args: + coro: + + Returns: + message object returned by calling given function with given params + """ + + async def decorator(ctx, *args, **kwargs): + try: + result = await coro(ctx, *args, **kwargs) + await send_approve(ctx) + return result + except NotFound: + pass + except Exception as pe: + try: + await send_disapprove(ctx) + except NotFound: + pass + raise pe + + decorator.__name__ = coro.__name__ + decorator.__doc__ = coro.__doc__ + return decorator + + +def _check_advanced_perm(ctx, *args, rule_sets=None, restrictions=None, **kwargs): + if not rule_sets and not restrictions: + raise CommandWithoutPermissions("Not checking any permission") + + if restrictions: + if type(restrictions) is not list and type(restrictions) is not tuple: + restrictions = [restrictions] + + for rule in restrictions: + valid, error = rule(ctx, *args, **kwargs) + if not valid: + raise error + + rule_sets = [rules if (type(rules) is list or type(rules) is set) else [rules] + for rules in rule_sets] + all_errors = [] + + for rules in rule_sets: + valids, errors = zip(*[chk_f(ctx, *args, **kwargs) for chk_f in rules]) + if all(valids): + return True + else: + all_errors += errors + + if len(all_errors) > 0: + raise all_errors[0] + else: + return True + + +def advanced_perm_check_function(*rules_sets, restrictions=None): + """ + Check channels and permissions, use -s -sudo or -a -admin to run it. + Args: + *rules_sets: list of rules, 1d or 2d, + restrictions: Restrictions must be always met + Returns: + message object returned by calling given function with given params + """ + + def decorator(coro): + async def f(ctx, *args, **kwargs): + valid = _check_advanced_perm(ctx, + *args, + **kwargs, + rule_sets=[*rules_sets], restrictions=restrictions) + if valid: + output = await coro(ctx, *args, **kwargs) + return output + else: + # logger.error(f"Permission check failed! Exceptions should be raised earlier!") + raise CommandError("Permission check failed.") + + f.__name__ = coro.__name__ + f.__doc__ = coro.__doc__ + return f + + return decorator + + +def advanced_args_function(bot, bold_name=False): + """ + Decorator that translates args to create flags and converts string into kwargs. + Args: + bot: bot instance + bold_name: + + Returns: + message object returned by calling given function with given params + """ + + def wrapper(coro): + # logger.warning(f"Advanced args are not supporting non kwargs functions") + + async def f(ctx, *args, **kwargs): + # if text: + # logger.error(f"Text is already in advanced args: {text}") + + good_args, kwargs = _get_advanced_args(bot, ctx, *args, bold_name=bold_name, **kwargs) + output = await coro(ctx, *good_args, **kwargs) + return output + + f.__name__ = coro.__name__ + f.__doc__ = coro.__doc__ + return f + + return wrapper + + +def _get_advanced_args(bot, ctx, *args, bold_name=False, **kwargs): + if not kwargs: + kwargs = {} + + good_args = list() + text_args = [] + args, kwargs = _get_advanced_kwargs(bot, ctx, *args, **kwargs, bold_name=bold_name) + args = list(args) + + for arg in args: + if arg: + if ',' in arg: + arg, *rest = arg.split(',') + for _rest in rest: + args.append(_rest) + good_args.append(arg) + text_args.append(arg) + # else: + # logger.debug(f"Unwanted arg: '{arg}'") + + good_args = tuple(_ar for _ar in good_args if _ar) + print(good_args) + return good_args, kwargs + + +def _get_advanced_kwargs(bot, ctx, *args, bold_name=False, **kwargs): + args = list(args) + if not kwargs: + # kwargs = {"force": False, "dry": False, "sudo": False, 'update': False} + kwargs = {} + + good_args = list() + mention_pattern = re.compile(r"<@[!&]\d+>") + text_args = [] + + for arg in args: + # if arg.startswith("-f") or arg == 'force': + # "force, enforce parameters" + # kwargs['force'] = True + # elif arg.startswith("-d") or arg == 'dry': + # "dry run" + # kwargs['dry'] = True + # elif arg.startswith("-u") or arg == 'update' or arg == "upgrade": + # "update" + # kwargs['update'] = True + # elif arg.startswith("-s") or arg.startswith("-a") or arg == 'sudo': + # "sudo or admin" + # kwargs['sudo'] = True + # elif arg.startswith("-"): + # try: + # _ = float(arg) + # good_args.append(arg) + # except ValueError: + # "drop unknown parameters" + # # logger.warning(f"unknown argument: {arg}") + if "=" in arg: + key, val = arg.split("=") + if key == "force" or key == "dry": + continue + if key and val: + kwargs.update({key: val}) + elif mention_pattern.match(arg) or "@everyone" in arg or "@here" in arg: + name = string_mention_converter(bot, ctx.guild, arg, bold_name=bold_name) + text_args.append(name) + + else: + good_args.append(arg) + text_args.append(arg) + + good_args = tuple(good_args) + text = ' '.join(text_args) + kwargs['text'] = text + + return good_args, kwargs + + +def string_mention_converter(bot, guild, text: "input str", bold_name=True) -> "String": + user_pattern = re.compile(r"<@!(\d+)>") + role_pattern = re.compile(r"<@&(\d+)>") + new_text = text + + new_text = new_text.replace("@everyone", "") + new_text = new_text.replace("@here", "") + + user_list = user_pattern.findall(text) + role_list = role_pattern.findall(text) + + for user in user_list: + try: + user_name = bot.get_user(int(user)).name + except AttributeError: + user_name = f"{user}" + if bold_name: + new_text = new_text.replace(f"<@!{user}>", f"@**{user_name}**") + else: + new_text = new_text.replace(f"<@!{user}>", f"@{user_name}") + + for role in role_list: + try: + roleid = int(role) + role_name = guild.get_role(roleid).name + except AttributeError as err: + # logger.error(f"Error in string_mention_converter {err}") + role_name = f"{role}" + new_text = new_text.replace(f"<@&{role}>", f"@*{role_name}*") + return new_text diff --git a/modules/definitions.py b/modules/definitions.py index 7d58410..19c770a 100644 --- a/modules/definitions.py +++ b/modules/definitions.py @@ -46,7 +46,6 @@ 501114928854204431, 479433212561719296] - MOST_COMMON_INT = 10 # when doing counters of activity, top n users. DAYS_BACK = 21 # days of history to work with RESAMPLE = "60min" @@ -74,5 +73,23 @@ DISCORD_BG_COLOR = '#36393E' +HELLO_TEXTS = ["Hello there {emote} {0}.", + "How is it going today {0}? {emote}", + "What's up {0}?", "Hey {0}. {emote}", + "Hi {0}, do you feel well today? {emote}", + "Good day {0}. {emote}", + "Oh {0}! Hello. {emote}", + "Hey {emote}. {0}", + "Hey, do you want some coffe {0}? {emote}", + ] + intents = discord.Intents.all() bot = Bot(command_prefix="!", ignore_case=True, intents=intents) + + +async def send_approve(ctx): + await ctx.message.add_reaction('✅') + + +async def send_disapprove(ctx): + await ctx.message.add_reaction('❌') diff --git a/modules/emojis.py b/modules/emojis.py new file mode 100644 index 0000000..c57722a --- /dev/null +++ b/modules/emojis.py @@ -0,0 +1,674 @@ +EMOJIS_MY_SYMBOLS = { + 'green_x': "❎", + 'green_ok': "✅", + 'arrow_back_left': "↩️", + "thinking": "🤔", + "red_x": "❌", + "blocked": "⛔", +} + +EMOJIS_ALPHABET = { + 0: '0️⃣', + 1: '1️⃣', + 2: '2️⃣', + 3: '3️⃣', + 4: '4️⃣', + 5: '5️⃣', + 6: '6️⃣', + 7: '7️⃣', + 8: '8️⃣', + 9: '9️⃣', + 10: '🔟', + "z": "🇿", + "y": "🇾", + "x": "🇽", + "w": "🇼", + "v": "🇻", + "u": "🇺", + "t": "🇹", + "s": "🇸", + "r": "🇷", + "q": "🇶", + "p": "🇵", + "o": "🇴", + "n": "🇳", + "m": "🇲", + "l": "🇱", + "k": "🇰", + "j": "🇯", + "i": "🇮", + "h": "🇭", + "g": "🇬", + "f": "🇫", + "e": "🇪", + "d": "🇩", + "c": "🇨", + "b": "🇧", + "a": "🇦", +} + +EMOJIS_SYMBOLS = { + "heart": '❤️', + "orange_heart": "🧡", + "yellow_heart": "💛", + "green_heart": "💚", + "blue_heart": "💙", + "purple_heart": "💜", + "brown_heart": "🤎", + "white_heart": "🤍", + "black_heart": "🖤", + "broken_heart": "💔", + "heart_exclamation": "❣️", + "two_hearts": "💕", + "revolving_hearts": "💞", + "heartbeat": "💓", + "heartpulse": "💗", + "sparkling_heart": "💖", + "cupid": "💘", + "gift_heart": "💝", + "heart_decoration": "💟", + "peace": "☮️", + "cross": "✝️", + "star_and_crescent": "☪️", + "om_symbol": "🕉️", + "wheel_of_dharma": "☸️", + "star_of_david": "✡️", + "six_pointed_star": "🔯", + "menorah": "🕎", + "yin_yang": "☯️", + "leo": "♌", + "virgo": "♍", + "libra": "♎", + "scorpius": "♏", + "sagittarius": "♐", + "capricorn": "♑", + "aquarius": "♒", + "pisces": "♓", + "id": "🆔", + "atom": "⚛️", + "accept": "🉑", + "radioactive": "☢️", + "biohazard": "☣️", + "mobile_phone_off": "📴", + "vibration_mode": "📳", + "u6709": "🈶", + "u7121": "🈚", + "u7533": "🈸", + "u55b6": "🈺", + "u6708": "🈷️", + "vs": "🆚", + "white_flower": "💮", + "ideograph_advantage": "🉐", + "secret": "㊙️", + "congratulations": "㊗️", + "u5408": "🈴", + "u6e80": "🈵", + "u5272": "🈹", + "u7981": "🈲", + "a": "🅰️", + "b": "🅱️", + "ab": "🆎", + "cl": "🆑", + "o2": "🅾️", + "sos": "🆘", + "x": "❌", + "o": "⭕", + "octagonal_sign": "🛑", + "no_entry": "⛔", + "name_badge": "📛", + "no_entry_sign": "🚫", + "100": "💯", + "anger": "💢", + "hotsprings": "♨️", + "no_pedestrians": "🚷", + "do_not_litter": "🚯", + "no_bicycles": "🚳", + "non_potable_water": "🚱", + "underage": "🔞", + "no_mobile_phones": "📵", + "no_smoking": "🚭", + "exclamation": "❗", + "grey_exclamation": "❕", + "question": "❓", + "grey_question": "❔", + "bangbang": "‼️", + "interrobang": "⁉️", + "low_brightness": "🔅", + "high_brightness": "🔆", + "part_alternation_mark": "〽️", + "warning": "⚠️", + "children_crossing": "🚸", + "trident": "🔱", + "fleur_de_lis": "⚜️", + "beginner": "🔰", + "recycle": "♻️", + "white_check_mark": "✅", + "u6307": "🈯", + "chart": "💹", + "sparkle": "❇️", + "eight_spoked_asterisk": "✳️", + "negative_squared_cross_mark": "❎", + "globe_with_meridians": "🌐", + "diamond_shape_with_a_dot_inside": "💠", + "m": "Ⓜ️", + "cyclone": "🌀", + "zzz": "💤", + "atm": "🏧", + "wc": "🚾", + "wheelchair": "♿", + "parking": "🅿️", + "u7a7a": "🈳", + "sa": "🈂️", + "passport_control": "🛂", + "customs": "🛃", + "baggage_claim": "🛄", + "left_luggage": "🛅", + "mens": "🚹", + "womens": "🚺", + "baby_symbol": "🚼", + "restroom": "🚻", + "put_litter_in_its_place": "🚮", + "cinema": "🎦", + "signal_strength": "📶", + "koko": "🈁", + "symbols": "🔣", + "information_source": "ℹ️", + "abc": "🔤", + "abcd": "🔡", + "capital_abcd": "🔠", + "ng": "🆖", + "ok": "🆗", + "up": "🆙", + "cool": "🆒", + "new": "🆕", + "free": "🆓", + "zero": "0️⃣", + "one": "1️⃣️⃣", + "two": "2️⃣", + "three": "3️⃣'", + "four": "4️⃣", + "five": "5️⃣", + "six": "6️⃣", + "seven": "7", + "eight": "8️⃣", + "nine": "9️⃣", + "keycap_ten": "🔟", + "1234": "🔢", + "hash": "#️⃣", + "asterisk": "*️⃣", + "eject": "⏏️", + "arrow_forward": "▶️", + "pause_button": "⏸️", + "play_pause": "⏯️", + "stop_button": "⏹️", + "record_button": "⏺️", + "track_next": "⏭️", + "track_previous": "⏮️", + "fast_forward": "⏩", + "rewind": "⏪", + "arrow_double_up": "⏫", + "arrow_double_down": "⏬", + "arrow_backward": "◀️", + "arrow_up_small": "🔼", + "arrow_down_small": "🔽", + "arrow_right": "➡️", + "arrow_left": "⬅️", + "arrow_up": "⬆️", + "arrow_down": "⬇️", + "arrow_upper_right": "↗️", + "arrow_lower_right": "↘️", + "arrow_lower_left": "↙️", + "arrow_upper_left": "↖️", + "arrow_up_down": "↕️", + "left_right_arrow": "↔️", + "arrow_right_hook": "↪️", + "leftwards_arrow_with_hook": "↩️", + "arrow_heading_up": "⤴️", + "arrow_heading_down": "⤵️", + "twisted_rightwards_arrows": "🔀", + "repeat": "🔁", + "repeat_one": "🔂", + "arrows_counterclockwise": "🔄", + "arrows_clockwise": "🔃", + "musical_note": "🎵", + "notes": "🎶", + "heavy_plus_sign": "➕", + "heavy_minus_sign": "➖", + "heavy_division_sign": "➗", + "heavy_multiplication_x": "✖️", + "infinity": "♾️", + "heavy_dollar_sign": "💲", + "currency_exchange": "💱", + "tm": "™️", + "copyright": "©", + "registered": "®️", + "wavy_dash": "〰️", + "curly_loop": "➰", + "loop": "➿", + "end": "🔚", + "back": "🔙", + "on": "🔛", + "top": "🔝", + "soon": "🔜", + "heavy_check_mark": "✔️", + "ballot_box_with_check": "☑️", + "radio_button": "🔘", + "white_circle": "⚪", + "black_circle": "⚫", + "red_circle": "🔴", + "blue_circle": "🔵", + "brown_circle": "🟤", + "purple_circle": "🟣", + "green_circle": "🟢", + "yellow_circle": "🟡", + "orange_circle": "🟠", + "small_red_triangle": "🔺", + "small_red_triangle_down": "🔻", + "small_orange_diamond": "🔸", + "small_blue_diamond": "🔹", + "large_orange_diamond": "🔶", + "large_blue_diamond": "🔷", + "white_square_button": "🔳", + "black_square_button": "🔲", + "black_small_square": "▪️", + "white_small_square": "▫️", + "black_medium_small_square": "◾", + "white_medium_small_square": "◽", + "black_medium_square": "◼️", + "white_medium_square": "◻️", + "black_large_square": "⬛", + "white_large_square": "⬜", + "orange_square": "🟧", + "blue_square": "🟦", + "red_square": "🟥", + "brown_square": "🟫", + "purple_square": "🟪", + "green_square": "🟩", + "yellow_square": "🟨", + "speaker": "🔈", + "mute": "🔇", + "sound": "🔉", + "loud_sound": "🔊", + "bell": "🔔", + "no_bell": "🔕", + "mega": "📣", + "loudspeaker": "📢", + "speech_left": "🗨️", + "eye_in_speech_bubble": "👁‍🗨", + "speech_balloon": "💬", + "thought_balloon": "💭", + "anger_right": "🗯️", + "spades": "♠️", + "clubs": "♣️", + "hearts": "♥️", + "diamonds": "♦️", + "black_joker": "🃏", + "flower_playing_cards": "🎴", + "mahjong": "🀄", + "clock1": "🕐", + "clock2": "🕑", + "clock3": "🕒", + "clock4": "🕓", + "clock5": "🕔", + "clock6": "🕕", + "clock7": "🕖", + "clock8": "🕗", + "clock9": "🕘", + "clock10": "🕙", + "clock11": "🕚", + "clock12": "🕛", + "clock130": "🕜", + "clock230": "🕝", + "clock330": "🕞", + "clock430": "🕟", + "clock530": "🕠", + "clock630": "🕡", + "clock730": "🕢", + "clock830": "🕣", + "clock930": "🕤", + "clock1030": "🕥", + "clock1130": "🕦", + "clock1230": "🕧", + "female_sign": "♀️", + "male_sign": "♂️", + "medical_symbol": "⚕️", + "regional_indicator_z": "🇿", + "regional_indicator_y": "🇾", + "regional_indicator_x": "🇽", + "regional_indicator_w": "🇼", + "regional_indicator_v": "🇻", + "regional_indicator_u": "🇺", + "regional_indicator_t": "🇹", + "regional_indicator_s": "🇸", + "regional_indicator_r": "🇷", + "regional_indicator_q": "🇶", + "regional_indicator_p": "🇵", + "regional_indicator_o": "🇴", + "regional_indicator_n": "🇳", + "regional_indicator_m": "🇲", + "regional_indicator_l": "🇱", + "regional_indicator_k": "🇰", + "regional_indicator_j": "🇯", + "regional_indicator_i": "🇮", + "regional_indicator_h": "🇭", + "regional_indicator_g": "🇬", + "regional_indicator_f": "🇫", + "regional_indicator_e": "🇪", + "regional_indicator_d": "🇩", + "regional_indicator_c": "🇨", + "regional_indicator_b": "🇧", + "regional_indicator_a": "🇦", +} + +EMOJI_FOOD = { + "green_apple": "🍏", + "apple": "🍎", + "pear": "🍐", + "tangerine": "🍊", + "lemon": "🍋", + "banana": "🍌", + "watermelon": "🍉", + "grapes": "🍇", + "blueberries": '🫐', + "strawberry": "🍓", + "melon": "🍈", + "cherries": "🍒", + "peach": "🍑", + "mango": "🥭", + "pineapple": "🍍", + "coconut": "🥥", + "kiwi": "🥝", + "tomato": "🍅", + "eggplant": "🍆", + "avocado": "🥑", + "broccoli": "🥦", + "leafy_green": "🥬", + "cucumber": "🥒", + "hot_pepper": "🌶️", + "corn": "🌽", + "carrot": "🥕", + "onion": "🧅", + "garlic": "🧄", + "potato": "🥔", + "sweet_potato": "🍠", + "croissant": "🥐", + "bagel": "🥯", + "bread": "🍞", + "french_bread": "🥖", + "pretzel": "🥨", + "cheese": "🧀", + "egg": "🥚", + "cooking": "🍳", + "pancakes": "🥞", + "waffle": "🧇", + "bacon": "🥓", + "cut_of_meat": "🥩", + "poultry_leg": "🍗", + "meat_on_bone": "🍖", + "hotdog": "🌭", + "hamburger": "🍔", + "fries": "🍟", + "pizza": "🍕", + "sandwich": "🥪", + "falafel": "🧆", + "stuffed_flatbread": "🥙", + "taco": "🌮", + "burrito": "🌯", + "salad": "🥗", + "shallow_pan_of_food": "🥘", + "canned_food": "🥫", + "spaghetti": "🍝", + "ramen": "🍜", + "stew": "🍲", + "curry": "🍛", + "sushi": "🍣", + "bento": "🍱", + "dumpling": "🥟", + "fried_shrimp": "🍤", + "rice_ball": "🍙", + "rice": "🍚", + "rice_cracker": "🍘", + "fish_cake": "🍥", + "fortune_cookie": "🥠", + "moon_cake": "🥮", + "oden": "🍢", + "dango": "🍡", + "shaved_ice": "🍧", + "ice_cream": "🍨", + "icecream": "🍦", + "pie": "🥧", + "cupcake": "🧁", + "cake": "🍰", + "birthday": "🎂", + "custard": "🍮", + "lollipop": "🍭", + "candy": "🍬", + "chocolate_bar": "🍫", + "popcorn": "🍿", + "doughnut": "🍩", + "cookie": "🍪", + "chestnut": "🌰", + "peanuts": "🥜", + "honey_pot": "🍯", + "butter": "🧈", + "milk": "🥛", + "baby_bottle": "🍼", + "coffee": "☕", + "tea": "🍵", + "mate": "🧉", + "cup_with_straw": "🥤", + "beverage_box": "🧃", + "ice_cube": "🧊", + "sake": "🍶", + "beer": "🍺", + "beers": "🍻", + "champagne_glass": "🥂", + "wine_glass": "🍷", + "tumbler_glass": "🥃", + "cocktail": "🍸", + "tropical_drink": "🍹", + "champagne": "🍾", + "spoon": "🥄", + "fork_and_knife": "🍴", + "fork_knife_plate": "🍽️", + "bowl_with_spoon": "🥣", + "takeout_box": "🥡", + "chopsticks": "🥢", + "salt": "🧂", +} + +EMOJI_NATURE = { + "dog": "🐶", + "cat": "🐱", + "mouse": "🐭", + "hamster": "🐹", + "rabbit": "🐰", + "fox": "🦊", + "bear": "🐻", + "panda_face": "🐼", + "koala": "🐨", + "tiger": "🐯", + "lion_face": "🦁", + "cow": "🐮", + "pig": "🐷", + "pig_nose": "🐽", + "frog": "🐸", + "monkey_face": "🐵", + "see_no_evil": "🙈", + "hear_no_evil": "🙉", + "speak_no_evil": "🙊", + "monkey": "🐒", + "chicken": "🐔", + "penguin": "🐧", + "bird": "🐦", + "baby_chick": "🐤", + "hatching_chick": "🐣", + "hatched_chick": "🐥", + "duck": "🦆", + "eagle": "🦅", + "owl": "🦉", + "bat": "🦇", + "wolf": "🐺", + "boar": "🐗", + "horse": "🐴", + "unicorn": "🦄", + "bee": "🐝", + "bug": "🐛", + "butterfly": "🦋", + "snail": "🐌", + "shell": "🐚", + "lady_beetle": "🐞", + "ant": "🐜", + "mosquito": "🦟", + "cricket": "🦗", + "spider": "🕷️", + "spider_web": "🕸️", + "scorpion": "🦂", + "turtle": "🐢", + "snake": "🐍", + "lizard": "🦎", + "t_rex": "🦖", + "sauropod": "🦕", + "octopus": "🐙", + "squid": "🦑", + "shrimp": "🦐", + "lobster": "🦞", + "oyster": "🦪", + "crab": "🦀", + "blowfish": "🐡", + "tropical_fish": "🐠", + "fish": "🐟", + "dolphin": "🐬", + "whale": "🐳", + "whale2": "🐋", + "shark": "🦈", + "crocodile": "🐊", + "tiger2": "🐅", + "leopard": "🐆", + "zebra": "🦓", + "gorilla": "🦍", + "orangutan": "🦧", + "elephant": "🐘", + "hippopotamus": "🦛", + "rhino": "🦏", + "dromedary_camel": "🐪", + "camel": "🐫", + "giraffe": "🦒", + "kangaroo": "🦘", + "water_buffalo": "🐃", + "ox": "🐂", + "cow2": "🐄", + "racehorse": "🐎", + "pig2": "🐖", + "ram": "🐏", + "llama": "🦙", + "sheep": "🐑", + "goat": "🐐", + "deer": "🦌", + "dog2": "🐕", + "guide_dog": "🦮", + "service_dog": "🐕‍🦺", + "poodle": "🐩", + "cat2": "🐈", + "rooster": "🐓", + "turkey": "🦃", + "peacock": "🦚", + "parrot": "🦜", + "swan": "🦢", + "flamingo": "🦩", + "dove": "🕊️", + "rabbit2": "🐇", + "sloth": "🦥", + "otter": "🦦", + "skunk": "🦨", + "raccoon": "🦝", + "badger": "🦡", + "mouse2": "🐁", + "rat": "🐀", + "chipmunk": "🐿️", + "hedgehog": "🦔", + "dragon": "🐉", + "dragon_face": "🐲", + "feet": "🐾", + "cactus": "🌵", + "christmas_tree": "🎄", + "evergreen_tree": "🌲", + "deciduous_tree": "🌳", + "palm_tree": "🌴", + "seedling": "🌱", + "herb": "🌿", + "shamrock": "☘️", + "four_leaf_clover": "🍀", + "bamboo": "🎍", + "tanabata_tree": "🎋", + "leaves": "🍃", + "fallen_leaf": "🍂", + "maple_leaf": "🍁", + "mushroom": "🍄", + "ear_of_rice": "🌾", + "bouquet": "💐", + "tulip": "🌷", + "rose": "🌹", + "wilted_rose": "🥀", + "hibiscus": "🌺", + "cherry_blossom": "🌸", + "blossom": "🌼", + "sunflower": "🌻", + "sun_with_face": "🌞", + "full_moon_with_face": "🌝", + "first_quarter_moon_with_face": "🌛", + "last_quarter_moon_with_face": "🌜", + "new_moon_with_face": "🌚", + "full_moon": "🌕", + "waning_gibbous_moon": "🌖", + "last_quarter_moon": "🌗", + "waning_crescent_moon": "🌘", + "new_moon": "🌑", + "waxing_crescent_moon": "🌒", + "first_quarter_moon": "🌓", + "waxing_gibbous_moon": "🌔", + "crescent_moon": "🌙", + "earth_americas": "🌎", + "earth_africa": "🌍", + "earth_asia": "🌏", + "ringed_planet": "🪐", + "dizzy": "💫", + "star": "⭐", + "star2": "🌟", + "sparkles": "✨", + "zap": "⚡", + "comet": "☄️", + "boom": "💥", + "fire": "🔥", + "cloud_tornado": "🌪️", + "rainbow": "🌈", + "sunny": "☀️", + "white_sun_small_cloud": "🌤️", + "partly_sunny": "⛅", + "white_sun_cloud": "🌥️", + "cloud": "☁️", + "white_sun_rain_cloud": "🌦️", + "cloud_rain": "🌧️", + "thunder_cloud_rain": "⛈️", + "cloud_lightning": "🌩️", + "cloud_snow": "🌨️", + "snowflake": "❄️", + "snowman2": "☃️", + "snowman": "⛄", + "wind_blowing_face": "🌬️", + "dash": "💨", + "droplet": "💧", + "sweat_drops": "💦", + "umbrella": "☔", + "umbrella2": "☂️", + "ocean": "🌊", + "fog": "🌫️", +} + +EMOJIS = {**EMOJIS_SYMBOLS, + **EMOJI_FOOD, **EMOJI_NATURE, + **EMOJIS_ALPHABET, + **EMOJIS_MY_SYMBOLS, } + +HAPPY_FACES = ["😀", "🙂", "😌", "😍", "🥰", "😜", "😊", "😎", "🤠", "🤗", "🤩", "🥳", "😉", "🤪", "😋", "😛"] +DISRTUBED_FACES = ["😒", "😞", "😔", "🧐", "😕", "😫", "😩", "🥺", "😤", "😠", "😳", "🤔", "🤫", "😟"] diff --git a/modules/minigames.py b/modules/minigames.py new file mode 100644 index 0000000..3aab147 --- /dev/null +++ b/modules/minigames.py @@ -0,0 +1,72 @@ +import numpy as np +import asyncio +import random + +from .permissions import is_called_in_bot_channel, is_priv +from .definitions import bot +from .decorators import advanced_perm_check_function, advanced_args_function + + +@bot.command(aliases=['sweeper', 'minesweeper']) +# @advanced_args_function(bot) +@advanced_perm_check_function(is_called_in_bot_channel, is_priv) +async def mines(ctx, *args, **kwargs): + """ + Generates sweeper game, !sweeper (size) (bombs) + Args: + ctx: + *args: + + Returns: + + """ + if len(args) == 0: + size = 7 + bombs = None + elif len(args) == 1: + size = args[0] + size = int(size) + bombs = None + elif len(args) == 2: + size, bombs = args + size = int(size) + bombs = int(bombs) + else: + size = 7 + bombs = None + + "Check table size, 2k characters limit" + if size < 1: + size = 2 + elif size > 14: + size = 14 + + "Check bomb amount" + fields = size * size + if bombs is None or bombs < 0: + bombs = size * size // 3.5 + elif bombs >= fields: + bombs = fields - 1 + + bomb_list = [1 if fi < bombs else 0 for fi in range(fields)] + random.shuffle(bomb_list) + temp_arr = np.array(bomb_list).reshape(size, size) + bomb_arr = np.zeros((size + 2, size + 2), dtype=int) + for rind, row in enumerate(temp_arr): + rind += 1 + for cin, num in enumerate(row): + cin += 1 + if num: + bomb_arr[rind - 1:rind + 2, cin - 1:cin + 2] += 1 + await asyncio.sleep(0.01) + + bomb_arr = bomb_arr[1:-1, 1:-1] + mask = temp_arr == 1 + bomb_arr[mask] = -1 + hidden_text = '\n'.join( + "".join(f"||`{num:^2}`||" if num >= 0 else f"||`{'X':^2}`||" for num in row) + for row in bomb_arr) + text = f"Sweeper {size}x{size}, bombs: {bombs:.0f} ({bombs / fields * 100:.0f}%)" + sweeper_text = f"{text}\n{hidden_text}" + message = await ctx.send(sweeper_text) + return message \ No newline at end of file diff --git a/modules/permissions.py b/modules/permissions.py new file mode 100644 index 0000000..69f6273 --- /dev/null +++ b/modules/permissions.py @@ -0,0 +1,37 @@ +from .definitions import SENTDEX_ID, DHAN_ID, HELLO_CHARLES_CH_ID + + +class RestrictedError(PermissionError): + pass + + +class CommandWithoutPermissions(PermissionError): + pass + + +def is_admin(ctx, *args, **kwargs): + if ctx.message.author.id in [SENTDEX_ID, DHAN_ID]: + return True, None + else: + return False, RestrictedError("This command is restricted to Youshisu.") + + +def is_priv(ctx, *args, **kwargs): + if ctx.guild: + return False, RestrictedError("This command is restricted to private channels.") + else: + return True, None + + +def is_not_priv(ctx, *args, **kwargs): + if ctx.guild: + return True, None + else: + return False, RestrictedError("This command is restricted to server channels.") + + +def is_called_in_bot_channel(ctx, *args, **kwargs): + if ctx.channel.id == HELLO_CHARLES_CH_ID: + return True, None + else: + return False, RestrictedError("This command is restricted to channel 'hello-charles'.") From 63f5cb6b9bc5156618c5286178e8a379a0467b0b Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 21 Nov 2020 05:39:25 +0100 Subject: [PATCH 4/4] Resolve convlict, missing changes --- modules/bot.py | 3 +-- modules/definitions.py | 7 +++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/bot.py b/modules/bot.py index c3fb3fc..f4e61fe 100644 --- a/modules/bot.py +++ b/modules/bot.py @@ -106,8 +106,7 @@ async def on_message(message): f'```py\n{{\n\t"Online": {online},\n\t"Idle/busy/dnd": {idle},\n\t"Offline": {offline}\n}}```') elif "sentdebot.p6()" == message.content.lower(): - await message.channel.send( - f"```\nThe Neural Networks from Scratch video series will resume when the NNFS book is completed. This means the videos will resume around Sept or Oct 2020.\n\nIf you are itching for the content, you can buy the book and get access to the draft now. The draft is over 500 pages, covering forward pass, activation functions, loss calcs, backward pass, optimization, train/test/validation for classification and regression. You can pre-order the book and get access to the draft via https://nnfs.io```") + await message.channel.send(f"```\nThe Neural Networks from Scratch videos will resume one day. https://nnfs.io```") elif "sentdebot.user_activity()" == message.content.lower() and message.channel.id in IMAGE_CHAN_IDS: # and len([r for r in author_roles if r.id in admins_mods_ids]) > 0: file = discord.File(os.path.join(local_files_path, "activity.png"), filename=f"activity.png") diff --git a/modules/definitions.py b/modules/definitions.py index 19c770a..150b35d 100644 --- a/modules/definitions.py +++ b/modules/definitions.py @@ -61,7 +61,10 @@ "voice-channel-text", "__init__", "hello_technical_questions", - "help_overflow"] + "help_overflow", + "dogs", + "show_and_tell", + "politics_enter_at_own_risk"] HELP_CHANNELS = ["help", "hello_technical_questions", @@ -80,7 +83,7 @@ "Good day {0}. {emote}", "Oh {0}! Hello. {emote}", "Hey {emote}. {0}", - "Hey, do you want some coffe {0}? {emote}", + "Hey, do you want some coffee {0}? {emote}", ] intents = discord.Intents.all()