async def command(command: str, message: discord.Message): if not __common__.check_permission(message.author): await message.add_reaction("❌") if message.author.id == key.shutdown_easter_egg_user: await message.channel.send("*hehehe*\n\nCan't fool me! >:3") return else: parts = command.split(" ") try: target_pid = int(parts[2]) except IndexError: target_pid = os.getpid() except ValueError: await message.channel.send("Invalid integer of PID to kill") return if target_pid == os.getpid(): # double check we want to uptime = time.perf_counter() - client.first_execution m = await message.channel.send( "Are you sure you want to shut down the bot?\n" f"Hostname: {gethostname()}\n" f"Uptime: {uptime/86400:.4f} days\n" f"To confirm shutdown, react to this message with ☑ in 30 seconds" ) await m.add_reaction("☑") def reaction_check(reaction, user): return (reaction.message.id == m.id and __common__.check_permission(user) and reaction.emoji == "☑") try: await client.wait_for("reaction_add", check=reaction_check, timeout=30) except asyncio.TimeoutError: await m.add_reaction("❌") await m.remove_reaction("☑", m.guild.me) await message.add_reaction("❌") await m.edit(content=m.content.replace( "To confirm shutdown, react to this message with ☑ in 30 seconds", "Shutdown timed out. Deleting this message in 30 seconds." ), delete_after=30) return else: client.active = False await m.delete() await message.add_reaction("☑") await message.channel.send("Shutting down bot...") await message.channel.send( f"Uptime: { time.perf_counter() - client.first_execution:.3f} seconds ({(time.perf_counter() - client.first_execution) / 86400:.3f} days)" ) await asyncio.sleep( 0.1) # give the above a chance to do its thing log.info( f"Bot shutdown initiated at {datetime.utcnow().__str__()} by {message.author.name}#{message.author.discriminator}" ) await client.on_shutdown() return
async def command(command: str, message: discord.Message): if not __common__.check_permission(message.author): await message.add_reaction("❌") if message.author.id == key.shutdown_easter_egg_user: await message.channel.send("*hehehe*\n\nCan't fool me! >:3") return else: parts = command.split(" ") try: target_pid = int(parts[2]) except IndexError: target_pid = os.getpid() except ValueError: await message.channel.send("Invalid integer of PID to kill") return if target_pid == os.getpid(): await message.channel.send("Shutting down bot...") await message.channel.send(f"Uptime: {time.perf_counter() - client.first_execution:.3f} seconds ({(time.perf_counter() - client.first_execution)/86400:.3f} days)") log.info(f"Bot shutdown initiated at {datetime.utcnow().__str__()} by {message.author.name}#{message.author.discriminator}") await client.on_shutdown() return
async def logstat(command: str, message: discord.Message): await message.channel.send( "Command disabled: System will run out of memory if ran") return global use_mpl if not __common__.check_permission(message.author): await __common__.failure(message) return profiling = "--profile" in message.content async with message.channel.typing(): # now we can actually see what our user wants of us # args: logstat 30(days) users 25(top X) # first we need to check if there's enough arguments parts = command.split(" ") # check if they want textual format try: parts.pop(parts.index("--text")) use_mpl = False except ValueError: use_mpl = True if use_mpl else False if len(parts) < 3: await message.channel.send( "Not enough arguments provided to command. See help for help.") return try: limit = datetime.datetime.utcnow() - datetime.timedelta( days=int(parts[1])) except ValueError: await message.channel.send( "First argument to command `logstat` must be number of days behind log to check, as an int" ) return start = time.perf_counter() if "--test" not in message.content: target_guild = message.guild.id logfiles = [x for x in os.listdir("logs/")] # list of all log files all_log_lines = [] # get every single line from those files for f in logfiles: with open("logs/" + f, "r", encoding="utf-8") as file: all_log_lines.extend(file.readlines()) await asyncio.sleep(0.05) else: target_guild = 364480908528451584 logfiles = [x for x in os.listdir("logstat_control_logs/") ] # list of all log files all_log_lines = [] # get every single line from those files for f in logfiles: with open("logs/" + f, "r", encoding="utf-8") as file: all_log_lines.extend(file.readlines()) end = time.perf_counter() if profiling: await message.channel.send( f"profiling: Processing time to load all log lines was {(end-start)*1000:.4f} ms" ) parsed_logs: List[Dict] = [] # we'll now loop through all the lines and parse them into dicts i = 0 if profiling: await message.channel.send(f"{len(all_log_lines)} lines to process" ) start = time.perf_counter() for line in all_log_lines: new_entry = {} try: new_entry["ts"] = datetime.datetime.strptime( line[:29], "[%Y-%m-%d %H:%M:%S.%f] ") except: continue # if there's no timestamp, just move on line_lvl = match_loglevel(line) if line_lvl: new_entry["lvl"] = line_lvl else: continue # all lines we're interested in looking at should have a loglevel on them anyways line_server_id = match_server_id(line) if line_server_id: new_entry["server_id"] = line_server_id else: continue line_channel_id = match_channel_id(line) if line_channel_id: new_entry["channel_id"] = line_channel_id else: continue line_message_id = match_message_id(line) if line_message_id: new_entry["message_id"] = line_message_id else: continue line_user_id = match_user_id(line) if line_user_id: new_entry["user_id"] = line_user_id else: continue # finally, we can add our parsed line into the list parsed_logs.append(new_entry) i += 1 if i % 10000 is 0: await asyncio.sleep(0.01) # i = 0 # split_workload = [] # while True: # if i > 1000: # raise RecursionError("infinite loop ran away, abandoning operation (more than 75 000 000 log lines?!)") # split_workload.append(all_log_lines[(i*75000):((i+1)*75000)]) # if not all_log_lines[(i * 75000):((i + 1) * 75000)]: # break # i += 1 # # pool_event_loop = asyncio.new_event_loop() # with ProcessPoolExecutor() as pool: # results = [await pool_event_loop.run_in_executor(pool, parse_lines, workload) for workload in split_workload] # pool_event_loop.stop() # pool_event_loop.close() # # for x in results: # parsed_logs.extend(x) end = time.perf_counter() if profiling: await message.channel.send( f"profiling: Processing time to parse all log lines: {(end-start)*1000:.4f} ms ({((end-start)*1_000_000)/len(all_log_lines):.3f} us/line)" ) await asyncio.sleep(0.1) # now we'll filter by time and server # list comprehensions are used here because in testing they showed to be about 8x faster than filter() snipped_logs = [x for x in parsed_logs if (x["ts"] > limit)] filtered_logs = [ x for x in snipped_logs if x["server_id"] == target_guild ] if parts[2] in ["users", "user"]: # check if need to filter to a channel try: filter_channel = __common__.strip_to_id(parts[4]) except TypeError: # ignore, we're not filtering to a channel pass except IndexError: # ignore, we're not filtering to a channel pass else: filtered_logs = [ x for x in filtered_logs if x["channel_id"] == filter_channel ] if parts[2] in ["channel", "channels"]: # check if we need to filter to a user try: filter_user = __common__.strip_to_id(parts[3]) except TypeError: # ignore, we're not filtering to a user pass except IndexError: # ignore, we're not filtering to a user pass else: filtered_logs = [ x for x in filtered_logs if x["user_id"] == filter_user ] record = Counter() if parts[2] in ["users", "user"]: item = "users" try: scoreboard = int(parts[3]) except: # it's fine scoreboard = 25 n = 0 for entry in filtered_logs: record[entry["user_id"]] += 1 n += 1 if n % 5000 is 0: await asyncio.sleep(0.1) top = record.most_common(scoreboard) top_mentions = {await get_human_id(x, message): y for x, y in top} elif parts[2] in ["channel", "channels"]: item = "channels" scoreboard = len(message.guild.text_channels) start = time.perf_counter() for entry in filtered_logs: record[entry["channel_id"]] += 1 end = time.perf_counter() log.debug( f"logstat: Processing time to count all users: {(end-start)*1000:.4f} ms ({((end-start)*1_000_000)/len(filtered_logs):.3f} us/entry)" ) if profiling else None top = record.most_common(scoreboard) top_mentions = { get_channel_name(x, message): y for x, y in top if y is not 0 } elif parts[2] in ["active"]: for entry in filtered_logs: record[entry["user_id"]] += 1 await message.channel.send( f"In the past {int(parts[1])} days, there have been {len(record.most_common())} active unique users." ) return else: await message.channel.send( "Unknown item to get records for. See help for help.") return if not use_mpl: data = "" i = 0 for x, y in top_mentions.items(): i += 1 data += f"`{'0' if i < 100 else ''}{'0' if i < 10 else ''}{str(i)}: {'0' if y < 10000 else ''}{'0' if y < 1000 else ''}{'0' if y < 100 else ''}{'0' if y < 10 else ''}{str(y)} messages:` {x}\n" embed = discord.Embed( title=f"Logfile statistics for past {int(parts[1])} days", description= f"Here are the top {scoreboard} {item} in this server, sorted by number of messages.\nTotal messages: {len(record)}\n" + data) try: await message.channel.send(embed=embed) except: await message.channel.send( "Looks like that information was too long to post, sorry. It's been dumped to the log instead." ) log.info( f"logstat command could not be output back (too many items). here's the data:\n{data}" ) if use_mpl: plot.rcdefaults() plot.rcParams.update({'figure.autolayout': True}) figure, ax = plot.subplots() ax.barh(range(len(top_mentions)), list(top_mentions.values()), align='center') ax.set_xticks(range(len(top_mentions)), list(top_mentions.keys())) ax.set_yticks(range(len(top_mentions))) ax.set_yticklabels(list(top_mentions.keys())) ax.invert_yaxis() plot.show() return
async def logstat(command: str, message: discord.Message): global use_mpl if not __common__.check_permission(message.author): await message.add_reaction("❌") return async with message.channel.typing(): logfiles = [x for x in os.listdir("logs/")] # list of all log files all_log_lines = [] # get every single line from those files for f in logfiles: with open("logs/" + f, "r", encoding="utf-8") as file: all_log_lines.extend(file.readlines()) await asyncio.sleep(0.1) parsed_logs: List[Dict] = [] # we'll now loop through all the lines and parse them into dicts n = 0 for line in all_log_lines: new_entry = {} line_ts = match_date(line) if line_ts: new_entry["ts"] = line_ts else: continue # if there's no timestamp, just move on line_lvl = match_loglevel(line) if line_lvl: new_entry["lvl"] = line_lvl else: continue # all lines we're interested in looking at should have a loglevel on them anyways line_server_id = match_server_id(line) if line_server_id: new_entry["server_id"] = line_server_id else: continue line_channel_id = match_channel_id(line) if line_channel_id: new_entry["channel_id"] = line_channel_id else: continue line_message_id = match_message_id(line) if line_message_id: new_entry["message_id"] = line_message_id else: continue line_user_id = match_user_id(line) if line_user_id: new_entry["user_id"] = line_user_id else: continue n += 1 if n % 5000 is 0: await asyncio.sleep(0.1) # finally, we can add our parsed line into the list parsed_logs.append(new_entry) # if you've got the above loop collapsed: all lines have been parsed into Dicts with the following properties: # ts (datetime), lvl (1char str), server_id, channel_id, message_id, and user_id # now we can actually see what our user wants of us # args: logstat 30(days) users 25(top X) # first we need to check if there's enough arguments parts = command.split(" ") # check if they want textual format try: parts.pop(parts.index("--text")) except ValueError: use_mpl = True if use_mpl else False if len(parts) < 3: await message.channel.send( "Not enough arguments provided to command. See help for help.") return try: limit = datetime.datetime.utcnow() - datetime.timedelta( days=int(parts[1])) except ValueError: await message.channel.send( "First argument to command `logstat` must be number of days behind log to check, as an int" ) return await asyncio.sleep(0.1) # now we'll trim to only the most recent days snipped_logs = [x for x in parsed_logs if (x["ts"] > limit)] # and then to what's in this server await asyncio.sleep(0.1) filtered_logs = [ x for x in snipped_logs if x["server_id"] == message.guild.id ] record = Counter() if parts[2] in ["users", "user"]: item = "users" try: scoreboard = int(parts[3]) except: # it's fine scoreboard = 25 n = 0 for entry in filtered_logs: record[entry["user_id"]] += 1 n += 1 if n % 5000 is 0: await asyncio.sleep(0.1) top = record.most_common(scoreboard) top_mentions = {await get_human_id(x, message): y for x, y in top} elif parts[2] in ["channel", "channels"]: item = "channels" scoreboard = len(message.guild.text_channels) for entry in filtered_logs: record[entry["channel_id"]] += 1 top = record.most_common(scoreboard) top_mentions = { get_channel_name(x, message): y for x, y in top if y is not 0 } elif parts[2] in ["active"]: for entry in filtered_logs: record[entry["user_id"]] += 1 await message.channel.send( f"In the past {int(parts[1])} days, there have been {len(record.most_common())} active unique users." ) return else: await message.channel.send( "Unknown item to get records for. See help for help.") return if use_mpl: data = "" i = 0 for x, y in top_mentions: i += 1 data += f"`{'0' if i < 100 else ''}{'0' if i < 10 else ''}{str(i)}: {'0' if y < 10000 else ''}{'0' if y < 1000 else ''}{'0' if y < 100 else ''}{'0' if y < 10 else ''}{str(y)} messages:` {x}\n" embed = discord.Embed( title=f"Logfile statistics for past {int(parts[1])} days", description= f"Here are the top {scoreboard} {item} in this server, sorted by number of messages.\n" + data) try: await message.channel.send(embed=embed) except: await message.channel.send( "Looks like that information was too long to post, sorry. It's been dumped to the log instead." ) log.info( f"logstat command could not be output back (too many items). here's the data:\n{data}" ) if not use_mpl: plot.rcdefaults() plot.rcParams.update({'figure.autolayout': True}) figure, ax = plot.subplots() ax.barh(range(len(top_mentions)), list(top_mentions.values()), align='center') ax.set_xticks(range(len(top_mentions)), list(top_mentions.keys())) ax.set_yticks(range(len(top_mentions))) ax.set_yticklabels(list(top_mentions.keys())) ax.invert_yaxis() plot.show() return
async def emergency_suicide(command: str, message: discord.Message): if not __common__.check_permission(message.author): await message.add_reaction("❌") return else: os._exit(2)
def reaction_check(reaction, user): return (reaction.message.id == m.id and __common__.check_permission(user) and reaction.emoji == "☑")