def check_ghosts(): yield from bot.wait_until_ready() while not bot.is_closed: for server in bot.servers: for member in server.members: c = userDatabase.cursor() now = datetime.utcnow() try: c.execute("SELECT * FROM user_servers WHERE id = ? AND server = ?", (member.id, member.server.id,)) result = c.fetchone() if result is not None: last_message = datetime.strptime(result["last_message"], "%Y-%m-%d %H:%M:%S.%f") name = result["name"] diff = (now - last_message).total_seconds() / 60.0 if (diff/(24*60) > 7.0): #yield from bot.send_message(member.server, "Simulando remoção de usuários: Usuário {0} será removido pois ficou {1} dia(s) sem escrever nada.".format(name, int(diff/(24*60)))) #yield from bot.send_message(member, "Você foi kickado do servidor {0} por ficar {1} dia(s) sem escrever nada. Saí daqui seu ghost!".format(member.server.name, int(diff/(24*60)))) for owners in owner_ids: yield from bot.send_message(member.server.get_member(owners), "O usuário {0} foi kickado (simulação apenas) do servidor {1} por ficar {2} dia(s) sem escrever nada.".format(member.name, member.server.name, int(diff/(24*60)))) #yield from bot.kick(member) else: # Não há registro deste usuário escrever algo mas ele está no servidor c.execute("""INSERT INTO user_servers (id, server, last_message, name) values (?, ?, ?, ?);""", (member.id, member.server.id, now, member.name)) finally: c.close() userDatabase.commit() yield from asyncio.sleep(60*60) # Verifica a cada uma hora (60 minutos)
def update_ghost(member): c = userDatabase.cursor() now = datetime.utcnow() try: c.execute("SELECT * FROM user_servers WHERE id = ? AND server = ?", (member.id, member.server.id,)) result = c.fetchone() if result is not None: c.execute("""UPDATE user_servers SET last_message = ?, name = ? WHERE id = ? AND server = ?;""", (now, member.id, member.server.id, member.name)) else: c.execute("""INSERT INTO user_servers (id, server, last_message, name) values (?, ?, ?, ?);""", (member.id, member.server.id, now, member.name)) finally: c.close() userDatabase.commit()
def remove_char(self, ctx, *, name): """Removes a registered character. The syntax is: /stalk removechar name""" if not ctx.message.channel.is_private: return True # This could be used to remove deleted chars so we don't need to check anything # Except if the char exists in the database... yield from self.bot.send_typing(ctx.message.channel) c = userDatabase.cursor() try: c.execute( "SELECT name, user_id, world, ABS(last_level) as level, vocation " "FROM chars WHERE name LIKE ?", (name, )) result = c.fetchone() if result is None: yield from self.bot.say( "There's no character with that name registered.") return user = get_member(self.bot, result["user_id"]) username = "******" if user is None else user.display_name c.execute("DELETE FROM chars WHERE name LIKE ?", (name, )) yield from self.bot.say( "**{0}** was removed successfully from **@{1}**.".format( result["name"], username)) if user is not None: for server in get_user_servers(self.bot, user.id): world = tracked_worlds.get(server.id, None) if world != result["world"]: continue log_msg = "{0.mention} removed **{1}** ({2} {3}) from {4.mention}.".\ format(ctx.message.author, result["name"], result["level"], result["vocation"], user) yield from send_log_message(self.bot, server, log_msg) return finally: c.close() userDatabase.commit()
def refresh_names(self, ctx): """Checks and updates user names on the database.""" if not ctx.message.channel.is_private: return True c = userDatabase.cursor() try: c.execute("SELECT id FROM users") result = c.fetchall() if len(result) <= 0: yield from self.bot.say("There are no registered users.") return update_users = list() for user in result: update_users.append( ("unknown" if get_member(self.bot, user[0]) is None else get_member(self.bot, user[0]).display_name, user["id"])) c.executemany("UPDATE users SET name = ? WHERE id LIKE ?", update_users) yield from self.bot.say("Usernames updated successfully.") finally: c.close() userDatabase.commit()
def purge(self, ctx): """Performs a database cleanup Removes characters that have been deleted and users with no characters or no longer in server.""" if not ctx.message.channel.is_private: return True c = userDatabase.cursor() try: c.execute("SELECT id FROM users") result = c.fetchall() if result is None: yield from self.bot.say("There are no users registered.") return delete_users = list() yield from self.bot.say("Initiating purge...") # Deleting users no longer in server for row in result: user = get_member(self.bot, row["id"]) if user is None: delete_users.append((row["id"], )) if len(delete_users) > 0: c.executemany("DELETE FROM users WHERE id = ?", delete_users) yield from self.bot.say( "{0} user(s) no longer in the server were removed.".format( c.rowcount)) # Deleting chars with non-existent user c.execute( "SELECT name FROM chars WHERE user_id NOT IN (SELECT id FROM users)" ) result = c.fetchall() if len(result) >= 1: chars = ["**" + i["name"] + "**" for i in result] reply = "{0} char(s) were assigned to a non-existent user and were deleted:\n\t".format( len(result)) reply += "\n\t".join(chars) yield from self.bot.say(reply) c.execute( "DELETE FROM chars WHERE user_id NOT IN (SELECT id FROM users)" ) # Removing deleted chars c.execute("SELECT name,last_level,vocation FROM chars") result = c.fetchall() if result is None: return delete_chars = list() rename_chars = list() # revoc_chars = list() for row in result: char = yield from get_character(row["name"]) if char == ERROR_NETWORK: yield from self.bot.say( "Couldn't fetch **{0}**, skipping...".format( row["name"])) continue # Char was deleted if char == ERROR_DOESNTEXIST: delete_chars.append((row["name"], )) yield from self.bot.say( "**{0}** doesn't exists, deleting...".format( row["name"])) continue # Char was renamed if char['name'] != row["name"]: rename_chars.append(( char['name'], row["name"], )) yield from self.bot.say( "**{0}** was renamed to **{1}**, updating...".format( row["name"], char['name'])) # No need to check if user exists cause those were removed already if len(delete_chars) > 0: c.executemany("DELETE FROM chars WHERE name LIKE ?", delete_chars) yield from self.bot.say("{0} char(s) were removed.".format( c.rowcount)) if len(rename_chars) > 0: c.executemany("UPDATE chars SET name = ? WHERE name LIKE ?", rename_chars) yield from self.bot.say("{0} char(s) were renamed.".format( c.rowcount)) # Remove users with no chars c.execute( "SELECT id FROM users WHERE id NOT IN (SELECT user_id FROM chars)" ) result = c.fetchall() if len(result) >= 1: c.execute( "DELETE FROM users WHERE id NOT IN (SELECT user_id FROM chars)" ) yield from self.bot.say( "{0} user(s) with no characters were removed.".format( c.rowcount)) # Remove level ups of removed characters c.execute( "DELETE FROM char_levelups WHERE char_id NOT IN (SELECT id FROM chars)" ) if c.rowcount > 0: yield from self.bot.say( "{0} level up registries from removed characters were deleted." .format(c.rowcount)) c.execute( "DELETE FROM char_deaths WHERE char_id NOT IN (SELECT id FROM chars)" ) # Remove deaths of removed characters if c.rowcount > 0: yield from self.bot.say( "{0} death registries from removed characters were deleted." .format(c.rowcount)) yield from self.bot.say("Purge done.") return finally: userDatabase.commit() c.close()
def stalk_namelock(self, ctx, *, params): """Register the name of a new character that was namelocked. Characters that get namelocked can't be searched by their old name, so they must be reassigned manually. If the character got a name change (from the store), searching the old name redirects to the new name, so this are usually reassigned automatically. The syntax is: /stalk namelock oldname,newname""" if not ctx.message.channel.is_private: return True params = params.split(",") if len(params) != 2: yield from self.bot.say( "The correct syntax is: `/stalk namelock oldname,newname") return old_name = params[0] new_name = params[1] yield from self.bot.send_typing(ctx.message.channel) c = userDatabase.cursor() try: c.execute("SELECT * FROM chars WHERE name LIKE ? LIMIT 1", (old_name, )) old_char_db = c.fetchone() # If character wasn't registered, there's nothing to do. if old_char_db is None: yield from self.bot.say( "I don't have a character registered with the name: **{0}**" .format(old_name)) return # Search old name to see if there's a result old_char = yield from get_character(old_name) if old_char == ERROR_NETWORK: yield from self.bot.say( "I'm having problem with 'the internet' as you humans say, try again." ) return # Check if returns a result if type(old_char) is dict: if old_name.lower() == old_char["name"].lower(): yield from self.bot.say( "The character **{0}** wasn't namelocked.".format( old_char["name"])) else: yield from self.bot.say( "The character **{0}** was renamed to **{1}**.".format( old_name, old_char["name"])) # Renaming is actually done in get_character(), no need to do anything. return # Check if new name exists new_char = yield from get_character(new_name) if new_char == ERROR_NETWORK: yield from self.bot.say( "I'm having problem with 'the internet' as you humans say, try again." ) return if new_char == ERROR_DOESNTEXIST: yield from self.bot.say( "The character **{0}** doesn't exists.".format(new_name)) return # Check if vocations are similar if not (old_char_db["vocation"].lower() in new_char["vocation"].lower() or new_char["vocation"].lower() in old_char_db["vocation"].lower()): yield from self.bot.say( "**{0}** was a *{1}* and **{2}** is a *{3}*. I think you're making a mistake." .format(old_char_db["name"], old_char_db["vocation"], new_char["name"], new_char["vocation"])) return confirm_message = "Are you sure **{0}** ({1} {2}) is **{3}** ({4} {5}) now? `yes/no`" yield from self.bot.say( confirm_message.format(old_char_db["name"], abs(old_char_db["last_level"]), old_char_db["vocation"], new_char["name"], new_char["level"], new_char["vocation"])) reply = yield from self.bot.wait_for_message( author=ctx.message.author, channel=ctx.message.channel, timeout=50.0) if reply is None: yield from self.bot.say( "No answer? I guess you changed your mind.") return elif reply.content.lower() not in ["yes", "y"]: yield from self.bot.say("No then? Alright.") return # Check if new name was already registered c.execute("SELECT * FROM chars WHERE name LIKE ?", (new_char["name"], )) new_char_db = c.fetchone() if new_char_db is None: c.execute( "UPDATE chars SET name = ?, vocation = ?, last_level = ? WHERE id = ?", ( new_char["name"], new_char["vocation"], new_char["level"], old_char_db["id"], )) else: # Replace new char with old char id and delete old char, reassign deaths and levelups c.execute( "DELETE FROM chars WHERE id = ?", (old_char_db["id"]), ) c.execute("UPDATE chars SET id = ? WHERE id = ?", ( old_char_db["id"], new_char_db["id"], )) c.execute("UPDATE char_deaths SET id = ? WHERE id = ?", ( old_char_db["id"], new_char_db["id"], )) c.execute("UPDATE char_levelups SET id = ? WHERE id = ?", ( old_char_db["id"], new_char_db["id"], )) yield from self.bot.say("Character renamed successfully.") finally: c.close() userDatabase.commit()
def remove_user(self, ctx, *, name): """Removes a discord user from the database The syntax is: /stalk remove name""" if not ctx.message.channel.is_private: return True c = userDatabase.cursor() yield from self.bot.send_typing(ctx.message.channel) # Searching users in server user = get_member_by_name(self.bot, name) # Searching users in database try: c.execute("SELECT id, name from users WHERE name LIKE ?", (name, )) result = c.fetchone() # Users in database and not in servers if result is not None and get_member(self.bot, result['id']) is None: yield from self.bot.say( "**@{0}** was no longer in server and was removed successfully." .format(result["name"])) delete_id = result["id"] # User in servers and in database elif user is not None and result is not None: yield from self.bot.say( "**{0}** was removed successfully.".format( user.display_name)) delete_id = user.id # User in server but not in database elif user is not None and result is None: yield from self.bot.say("**{0}** is not registered.".format( user.display_name)) return # User not in server or database else: yield from self.bot.say( "I don't see any user named **{0}**.".format(name)) return c.execute("DELETE FROM users WHERE id = ?", (delete_id, )) c.execute("SELECT name FROM chars WHERE user_id = ?", (delete_id, )) result = c.fetchall() if len(result) >= 1: chars = ["**" + i["name"] + "**" for i in result] reply = "The following characters were registered to the user:\n\t" reply += "\n\t".join(chars) reply += "\nDo you want to delete them? ``(yes/no)``" yield from self.bot.say(reply) answer = yield from self.bot.wait_for_message( author=ctx.message.author, channel=ctx.message.channel, timeout=30.0) if answer is None: yield from self.bot.say( "I will take your silence as a no...") elif answer.content.lower() in ["yes", "y"]: c.execute("DELETE FROM chars WHERE user_id = ?", (delete_id, )) yield from self.bot.say("Characters deleted successfully.") else: yield from self.bot.say("Ok, we are done then.") return finally: c.close() userDatabase.commit()
def add_account(self, ctx, *, params): """Register a character and all other visible characters to a discord user. If a character is hidden, only that character will be added. Characters in other worlds are skipped. The syntax is the following: /stalk addacc user,char""" if not ctx.message.channel.is_private: return True params = params.split(",") if len(params) != 2: yield from self.bot.say( "The correct syntax is: ``/stalk addacc username,character``") return author = ctx.message.author if author.id in mod_ids + owner_ids: author_servers = get_user_servers(self.bot, author.id) else: author_servers = get_user_admin_servers(self.bot, author.id) author_worlds = get_user_worlds(self.bot, author.id) user = get_member_by_name(self.bot, params[0], server_list=author_servers) user_servers = get_user_servers(self.bot, user.id) user_worlds = get_user_worlds(self.bot, user.id) common_worlds = list(set(author_worlds) & set(user_worlds)) yield from self.bot.send_typing(ctx.message.channel) character = yield from get_character(params[1]) if user is None: yield from self.bot.say( "I don't see any user named **{0}** in the servers you manage." .format(params[0])) return if type(character) is not dict: if character == ERROR_NETWORK: yield from self.bot.say( "I couldn't fetch the character, please try again.") elif character == ERROR_DOESNTEXIST: yield from self.bot.say("That character doesn't exists.") return c = userDatabase.cursor() try: chars = character['chars'] # If the char is hidden,we still add the searched character if len(chars) == 0: yield from self.bot.say("Character is hidden.") chars = [character] skipped = list() added = list() added_tuples = list() reassigned_tuples = list() existent = list() error = list() for char in chars: # Character not in followed server(s), skip. if char['world'] not in common_worlds: skipped.append([char["name"], char["world"]]) continue name = char["name"] # If char is the same we already looked up, no need to look him up again if character["name"] == char["name"]: char = character else: char = yield from get_character(char["name"]) if type(char) is not dict: error.append(name) continue # Skip characters scheduled for deletion if char.get("deleted", False): skipped.append([char["name"], char["world"]]) continue c.execute( "SELECT id, name,user_id FROM chars WHERE name LIKE ?", (char['name'], )) result = c.fetchone() # Char is already in database if result is not None: # Registered to different user if str(result["user_id"]) != user.id: current_user = get_member(self.bot, result["user_id"]) # Char is registered to user no longer in server if current_user is None: added.append(char) reassigned_tuples.append(( user.id, result["id"], )) continue else: yield from self.bot.say( "{0} is already assigned to {1}. We can't add any other of these " "characters.".format( char["name"], current_user.display_name)) return # Registered to current user existent.append(char) continue added.append(char) added_tuples.append(( char["name"], char["level"] * -1, char["vocation"], user.id, char["world"], )) c.execute("SELECT id from users WHERE id = ?", (user.id, )) result = c.fetchone() if result is None: c.execute("INSERT INTO users(id,name) VALUES (?,?)", ( user.id, user.display_name, )) c.executemany( "INSERT INTO chars(name,last_level,vocation,user_id, world) VALUES (?,?,?,?,?)", added_tuples) c.executemany("UPDATE chars SET user_id = ? WHERE id = ?", reassigned_tuples) reply = "" log_reply = dict().fromkeys([server.id for server in user_servers], "") if added: reply += "\nThe following characters were registered or reassigned successfully:" for char in added: char["guild"] = char.get("guild", "No guild") reply += "\n\t**{name}** ({level} {vocation}) - **{guild}**".format( **char) # Announce on server log of each server for server in user_servers: # Only announce on worlds where the character's world is tracked if tracked_worlds.get(server.id, None) == char["world"]: log_reply[ server. id] += "\n\t{name} - {level} {vocation} - **{guild}**".format( **char) if existent: reply += "\nThe following characters were already registered to this user:"******"guild"] = char.get("guild", "No guild") reply += "\n\t**{name}** ({level} {vocation}) - **{guild}**".format( **char) if skipped: reply += "\nThe following characters were skipped (not in tracked worlds or scheduled deletion):" for char, world in skipped: reply += "\n\t{0} ({1})".format(char, world) if error: reply += "\nThe following characters couldn't be fetched: " reply += ", ".join(error) yield from self.bot.say(reply) for server_id, message in log_reply.items(): if message: message = "{0.mention} registered the following characters to {1.mention}: {2}".format( author, user, message) yield from send_log_message(self.bot, self.bot.get_server(server_id), message) return finally: c.close() userDatabase.commit()
def add_char(self, ctx, *, params): """Registers a tibia character to a discord user. The syntax is: /stalk addchar user,character""" if not ctx.message.channel.is_private: return True params = params.split(",") if len(params) != 2: yield from self.bot.say( "The correct syntax is: ``/stalk addchar username,character``") return author = ctx.message.author if author.id in mod_ids + owner_ids: author_servers = get_user_servers(self.bot, author.id) else: author_servers = get_user_admin_servers(self.bot, author.id) author_worlds = get_user_worlds(self.bot, author.id) # Only search in the servers the command author is user = get_member_by_name(self.bot, params[0], server_list=author_servers) user_servers = get_user_servers(self.bot, user.id) user_worlds = get_user_worlds(self.bot, author.id) common_worlds = list(set(author_worlds) & set(user_worlds)) yield from self.bot.send_typing(ctx.message.channel) char = yield from get_character(params[1]) if user is None: yield from self.bot.say( "I don't see any user named **{0}** in the servers you manage." .format(params[0])) return if type(char) is not dict: if char == ERROR_NETWORK: yield from self.bot.say( "I couldn't fetch the character, please try again.") elif char == ERROR_DOESNTEXIST: yield from self.bot.say("That character doesn't exists.") return if char["world"] not in common_worlds: yield from self.bot.say( "**{name}** ({world}) is not in a world you can manage.". format(**char)) return if char.get("deleted", False): yield from self.bot.say( "**{name}** ({world}) is scheduled for deletion and can't be added." .format(**char)) return c = userDatabase.cursor() try: c.execute("SELECT id, name, user_id FROM chars WHERE name LIKE ?", (char['name'], )) result = c.fetchone() # Char is already in database if result is not None: # Update name if it was changed if char['name'] != params[1]: c.execute("UPDATE chars SET name = ? WHERE id = ?", ( char['name'], result["id"], )) yield from self.bot.say( "This character's name was changed from **{0}** to **{1}**" .format(params[1], char['name'])) # Registered to a different user if result["user_id"] != user.id: current_user = get_member(self.bot, result["user_id"]) # User no longer in server if current_user is None: c.execute("UPDATE chars SET user_id = ? WHERE id = ?", ( user.id, result["id"], )) yield from self.bot.say( "This character was registered to a user no longer in server. " "It was assigned to this user successfully.") # Log on relevant servers for server in user_servers: world = tracked_worlds.get(server.id, None) if world == char["world"]: log_msg = "{0.mention} registered **{1}** ({2} {3}) to {4.mention}." yield from send_log_message( self.bot, server, log_msg.format(author, char["name"], char["level"], char["vocation"], user)) else: yield from self.bot.say( "This character is already registered to **@{0}**". format(current_user.display_name)) return # Registered to current user yield from self.bot.say( "This character is already registered to this user.") return c.execute( "INSERT INTO chars (name,last_level,vocation,user_id, world) VALUES (?,?,?,?,?)", (char["name"], char["level"] * -1, char["vocation"], user.id, char["world"])) # Check if user is already registered c.execute("SELECT id from users WHERE id = ?", (user.id, )) result = c.fetchone() if result is None: c.execute("INSERT INTO users(id,name) VALUES (?,?)", ( user.id, user.display_name, )) yield from self.bot.say( "**{0}** was registered successfully to this user.".format( char['name'])) # Log on relevant servers for server in user_servers: world = tracked_worlds.get(server.id, None) if world == char["world"]: char["guild"] = char.get("guild", "No guild") log_msg = "{0.mention} registered **{1}** ({2} {3}, {4}) to {5.mention}." yield from send_log_message( self.bot, server, log_msg.format(author, char["name"], char["level"], char["vocation"], char["guild"], user)) return finally: c.close() userDatabase.commit()