async def kiss(self, ctx:Context, user:Member): ''' Kisses a mentioned user ''' # Check if they're themself if user == ctx.author: await ctx.send(f"How would you even manage to do that?") return # Check if they're related x = FamilyTreeMember.get(ctx.author.id) y = FamilyTreeMember.get(user.id) async with ctx.channel.typing(): relationship = x.get_relation(y) # Generate responses if relationship == None or relationship.casefold() == 'partner': responses = [ f"*Kisses {user.mention}*" ] else: responses = [ f"Woah woah, you two are family!", f"Incest is wincest, I guess.", f"You two are related but go off I guess.", ] # Boop an output await ctx.send(choice(responses))
async def relationship(self, ctx: Context, user: User, other: User = None): ''' Gets the relationship between the two specified users ''' if user == ctx.author: await ctx.send(f"You are you...") return if other == None: user, other = ctx.author, user user, other = FamilyTreeMember.get(user.id), FamilyTreeMember.get( other.id) relation = user.get_relation(other) if relation == None: await ctx.send( f"`{user.get_name(self.bot)}` is not related to `{other.get_name(self.bot)}`." ) return for i in range(10): for o in self.operations: relation = o(relation) for o in self.post_operations: relation = o(relation) await ctx.send( f"`{other.get_name(self.bot)}` is `{user.get_name(self.bot)}`'s {relation}." )
async def recache(self, ctx:Context, user:UserID, guild_id:int=0): '''Recaches a user's family tree member object''' # Read data from DB async with self.bot.database() as db: parent = await db('SELECT parent_id FROM parents WHERE child_id=$1 AND guild_id=$2', user, guild_id) children = await db('SELECT child_id FROM parents WHERE parent_id=$1 AND guild_id=$2', user, guild_id) partner = await db('SELECT partner_id FROM marriages WHERE user_id=$1 AND guild_id=$2', user, guild_id) # Load data into cache children = [i['child_id'] for i in children] parent_id = parent[0]['parent_id'] if len(parent) > 0 else None partner_id = partner[0]['partner_id'] if len(partner) > 0 else None f = FamilyTreeMember( user, children=children, parent_id=parent_id, partner_id=partner_id, guild_id=guild_id, ) # Push update via redis async with self.bot.redis() as re: await re.publish_json('TreeMemberUpdate', f.to_json()) # Output to user await ctx.send("Published update.")
async def divorce(self, ctx:Context): ''' Divorces you from your current spouse ''' instigator = ctx.author # Get marriage data for the user instigator_data = FamilyTreeMember.get(instigator.id, ctx.guild.id if ctx.guild.id in self.bot.server_specific_families else 0) # See why it could fail if instigator_data.partner == None: await ctx.send(self.bot.get_cog('DivorceRandomText').invalid_instigator(None, None)) return target = ctx.guild.get_member(instigator_data.partner.id) if target == None: target_id = instigator_data.partner.id else: target_id = target.id if instigator_data.partner.id != target_id: await ctx.send(self.bot.get_cog('DivorceRandomText').invalid_target(None, None)) return # At this point they can only be married async with self.bot.database() as db: await db('DELETE FROM marriages WHERE (user_id=$1 OR user_id=$2) AND guild_id=$3', instigator.id, target_id, ctx.guild.id if ctx.guild.id in self.bot.server_specific_families else 0) await ctx.send(self.bot.get_cog('DivorceRandomText').valid_target(instigator, target)) me = instigator_data me._partner = None them = FamilyTreeMember.get(target_id, ctx.guild.id if ctx.guild.id in self.bot.server_specific_families else 0) them._partner = None
async def recachefamily(self, ctx:Context, user:UserID, guild_id:int=0): '''Recaches a user's family tree member object, but through their whole family''' # Get connections db = await self.bot.database.get_connection() re = await self.bot.redis.get_connection() # Loop through their tree family = FamilyTreeMember.get(user, guild_id).span(expand_upwards=True, add_parent=True)[:] for i in family: parent = await db('SELECT parent_id FROM parents WHERE child_id=$1 AND guild_id=$2', i.id, guild_id) children = await db('SELECT child_id FROM parents WHERE parent_id=$1 AND guild_id=$2', i.id, guild_id) partner = await db('SELECT partner_id FROM marriages WHERE user_id=$1 AND guild_id=$2', i.id, guild_id) # Load data into cache children = [i['child_id'] for i in children] parent_id = parent[0]['parent_id'] if len(parent) > 0 else None partner_id = partner[0]['partner_id'] if len(partner) > 0 else None f = FamilyTreeMember( i.id, children=children, parent_id=parent_id, partner_id=partner_id, guild_id=guild_id, ) # Push update via redis await re.publish_json('TreeMemberUpdate', f.to_json()) # Disconnect from database await db.disconnect() await re.disconnect() # Output to user await ctx.send(f"Published `{len(family)}` updates.")
async def emancipate(self, ctx: Context): ''' Making it so you no longer have a parent ''' instigator = ctx.author user_tree = FamilyTreeMember.get(instigator.id) try: parent_id = user_tree.parent.id except AttributeError: await ctx.send( self.emancipate_random_text.invalid_target(instigator, None)) return async with self.bot.database() as db: await db('DELETE FROM parents WHERE parent_id=$1 AND child_id=$2', parent_id, instigator.id) await ctx.send( self.emancipate_random_text.valid_target( instigator, ctx.guild.get_member(parent_id))) me = FamilyTreeMember.get(instigator.id) me._parent = None them = FamilyTreeMember.get(parent_id) them._children.remove(instigator.id)
async def forcemarry(self, ctx:Context, user_a:UserID, user_b:UserID=None): ''' Marries the two specified users ''' if user_b is None: user_b = ctx.author.id if user_a == user_b: await ctx.send("You can't marry yourself (but you can be your own parent ;3).") return # Get users me = FamilyTreeMember.get(user_a, ctx.family_guild_id) them = FamilyTreeMember.get(user_b, ctx.family_guild_id) # See if they have partners if me.partner != None or them.partner != None: await ctx.send("One of those users already has a partner.") return # Update database async with self.bot.database() as db: try: await db.marry(user_a, user_b, ctx.family_guild_id) except Exception as e: await ctx.send(f"Error encountered: `{e}`") return # Only thrown if two people try to marry at once, so just return me._partner = user_b them._partner = user_a await ctx.send("Consider it done.")
async def disown(self, ctx: Context, child: User): ''' Lets you remove a user from being your child ''' instigator = ctx.author target = child user_tree = FamilyTreeMember.get(instigator.id) children_ids = user_tree._children if target.id not in children_ids: await ctx.send( self.disown_random_text.invalid_target(instigator, target)) return async with self.bot.database() as db: await db('DELETE FROM parents WHERE child_id=$1 AND parent_id=$2', target.id, instigator.id) await ctx.send( self.disown_random_text.valid_target( instigator, ctx.guild.get_member(child.id))) me = FamilyTreeMember.get(instigator.id) me._children.remove(target.id) them = FamilyTreeMember.get(target.id) them._parent = None
async def emancipate(self, ctx: Context): ''' Making it so you no longer have a parent ''' instigator = ctx.author user_tree = FamilyTreeMember.get( instigator.id, ctx.guild.id if ctx.guild.id in self.bot.server_specific_families else 0) try: parent_id = user_tree.parent.id except AttributeError: await ctx.send( self.bot.get_cog('EmancipateRandomText').invalid_target( instigator, None)) return async with self.bot.database() as db: await db( 'DELETE FROM parents WHERE parent_id=$1 AND child_id=$2 AND guild_id=$3', parent_id, instigator.id, ctx.guild.id if ctx.guild.id in self.bot.server_specific_families else 0) await ctx.send( self.bot.get_cog('EmancipateRandomText').valid_target( instigator, ctx.guild.get_member(parent_id))) user_tree._parent = None them = FamilyTreeMember.get( parent_id, ctx.guild.id if ctx.guild.id in self.bot.server_specific_families else 0) them._children.remove(instigator.id)
async def disown(self, ctx: Context, *, child: User): ''' Lets you remove a user from being your child ''' instigator = ctx.author target = child user_tree = FamilyTreeMember.get( instigator.id, ctx.guild.id if ctx.guild.id in self.bot.server_specific_families else 0) children_ids = user_tree._children if target.id not in children_ids: await ctx.send( self.bot.get_cog('DisownRandomText').invalid_target( instigator, target)) return async with self.bot.database() as db: await db( 'DELETE FROM parents WHERE child_id=$1 AND parent_id=$2 AND guild_id=$3', target.id, instigator.id, ctx.guild.id if ctx.guild.id in self.bot.server_specific_families else 0) await ctx.send( self.bot.get_cog('DisownRandomText').valid_target( instigator, ctx.guild.get_member(child.id))) user_tree._children.remove(target.id) them = FamilyTreeMember.get( target.id, ctx.guild.id if ctx.guild.id in self.bot.server_specific_families else 0) them._parent = None
async def forceadopt(self, ctx:Context, parent:UserID, child:UserID=None): ''' Adds the child to the specified parent ''' if child is None: child = parent parent = ctx.author.id # Run check them = FamilyTreeMember.get(child, ctx.family_guild_id) child_name = await self.bot.get_name(child) if them.parent: await ctx.send(f"`{child_name!s}` already has a parent.") return # Update database async with self.bot.database() as db: try: await db('INSERT INTO parents (parent_id, child_id, guild_id) VALUES ($1, $2, $3)', parent, child, ctx.family_guild_id) except Exception as e: raise e return # Only thrown when multiple people do at once, just return me = FamilyTreeMember.get(parent, ctx.family_guild_id) me._children.append(child) them._parent = parent async with self.bot.redis() as re: await re.publish_json('TreeMemberUpdate', me.to_json()) await re.publish_json('TreeMemberUpdate', them.to_json()) await ctx.send("Consider it done.")
async def relationship(self, ctx: Context, user: User, other: User = None): ''' Gets the relationship between the two specified users ''' if user == ctx.author and other is None: await ctx.send( f"Unsurprisingly, you're pretty closely related to yourself.") return await ctx.channel.trigger_typing() if other == None: user, other = ctx.author, user user, other = FamilyTreeMember.get( user.id, ctx.family_guild_id), FamilyTreeMember.get(other.id, ctx.family_guild_id) async with ctx.channel.typing(): relation = user.get_relation(other) username = await self.bot.get_name(user.id) othername = await self.bot.get_name(other.id) if relation == None: await ctx.send(f"`{username}` is not related to `{othername}`.") return await ctx.send(f"`{othername}` is `{username}`'s {relation}.")
async def destroy(self, user_id: int): ''' Removes a user ID from the database and cache ''' async with self.database() as db: await db.destroy(user_id) FamilyTreeMember.get(user_id).destroy()
async def startup(self): ''' Resets and fills the FamilyTreeMember cache with objects ''' # Cache all users for easier tree generation FamilyTreeMember.all_users = {None: None} # Get all from database async with self.database() as db: partnerships = await db('SELECT * FROM marriages WHERE valid=TRUE') parents = await db('SELECT * FROM parents') customisations = await db('SELECT * FROM customisation') # Cache all into objects for i in partnerships: FamilyTreeMember(discord_id=i['user_id'], children=[], parent_id=None, partner_id=i['partner_id']) for i in parents: parent = FamilyTreeMember.get(i['parent_id']) parent._children.append(i['child_id']) child = FamilyTreeMember.get(i['child_id']) child._parent = i['parent_id'] for i in customisations: CustomisedTreeUser(**i) # Pick up the blacklisted guilds from the db async with self.database() as db: blacklisted = await db('SELECT * FROM blacklisted_guilds') self.blacklisted_guilds = [i['guild_id'] for i in blacklisted] # Now wait for the stuff you need to actually be online for await self.wait_until_ready() await self.set_default_presence() # Grab the command prefixes per guild async with self.database() as db: settings = await db('SELECT * FROM guild_settings') for guild_setting in settings: self.guild_prefixes[ guild_setting['guild_id']] = guild_setting['prefix'] # Remove anyone who's empty or who the bot can't reach async with self.database() as db: for user_id, ftm in FamilyTreeMember.all_users.copy().items(): if user_id == None or ftm == None: continue if self.get_user(user_id) == None: await db.destroy(user_id) ftm.destroy() # And update DBL await self.post_guild_count()
async def forcemarry(self, ctx: Context, user_a: User, user_b: User): ''' Marries the two specified users ''' async with self.bot.database() as db: try: await db.marry(user_a, user_b) except Exception as e: return # Only thrown if two people try to marry at once, so just return me = FamilyTreeMember.get(user_a.id) me._partner = user_b.id them = FamilyTreeMember.get(user_b.id) them._partner = user_a.id await ctx.send("Consider it done.")
async def children(self, ctx: Context, user: User = None): ''' Gives you a list of all of your children ''' if user == None: user = ctx.author # Setup output variable output = '' # Get the user's info user_info = FamilyTreeMember.get(user.id) if len(user_info.children) == 0: output += f"`{user!s}` has no children right now." else: output += f"`{user!s}` has `{len(user_info.children)}` child" + \ {False:"ren", True:""}.get(len(user_info.children)==1) + ": " + \ ", ".join([f"`{self.bot.get_user(i.id)!s}` (`{i.id}`)" for i in user_info.children]) + '. ' # Get their partner's info, if any if user_info.partner == None: await ctx.send(output) return user_info = user_info.partner user = self.bot.get_user(user_info.id) if len(user_info.children) == 0: output += f"\nTheir partner, `{user!s}`, has no children right now." else: output += f"\nTheir partner, `{user!s}`, has `{len(user_info.children)}` child" + \ {False:"ren", True:""}.get(len(user_info.children)==1) + ": " + \ ", ".join([f"`{self.bot.get_user(i.id)!s}` (`{i.id}`)" for i in user_info.children]) + '. ' # Return all output await ctx.send(output)
async def disownall(self, ctx: Context): '''Disowns all of your children''' # Get their children user_tree = FamilyTreeMember.get(ctx.author.id, ctx.family_guild_id) children = user_tree.children[:] if not children: await ctx.send("You don't have any children to disown .-." ) # TODO make this text into a template return # Disown em for child in children: child._parent = None user_tree._children = [] # Save em async with self.bot.database() as db: for child in children: await db( 'DELETE FROM parents WHERE parent_id=$1 AND child_id=$2 AND guild_id=$3', user_tree.id, child.id, user_tree._guild_id) # Redis em async with self.bot.redis() as re: for person in children + [user_tree]: await re.publish_json('TreeMemberUpdate', person.to_json()) # Output to user await ctx.send("You've sucessfully disowned all of your children." ) # TODO
async def copyfamilytoguild(self, ctx: Context, user: UserID, guild_id: int): '''Copies a family's span to a given guild ID for server specific families''' # Get their current family tree = FamilyTreeMember.get(user) users = tree.span(expand_upwards=True, add_parent=True) await ctx.channel.trigger_typing() # Database it to the new guild db = await self.bot.database.get_connection() # Delete current guild data await db('DELETE FROM marriages WHERE guild_id=$1', guild_id) await db('DELETE FROM parents WHERE guild_id=$1', guild_id) # Generate new data to copy parents = ((i.id, i._parent, guild_id) for i in users if i._parent) partners = ((i.id, i._partner, guild_id) for i in users if i._partner) # Push to db await db.conn.copy_records_to_table( 'parents', columns=['child_id', 'parent_id', 'guild_id'], records=parents) await db.conn.copy_records_to_table( 'marriages', columns=['user_id', 'partner_id', 'guild_id'], records=partners) # Send to user await ctx.send(f"Copied over `{len(users)}` users.") await db.disconnect()
async def convert(self, ctx: commands.Context, value: str) -> int: """Converts the given value to a valid user ID""" # Maybe they gave a straight? try: return int(value) except ValueError: pass # They pinged the user match = self.USER_ID_REGEX.search(value) if match is not None: return int(match.group(1)) # Try and find their name from the user's relations ftm = FamilyTreeMember.get(ctx.author.id, ctx.family_guild_id) relations = ftm.get_direct_relations() if relations: redis_keys = [f"UserID-{i}" for i in relations] async with ctx.bot.redis() as re: usernames = await re.mget(*redis_keys) for uid, name in zip(relations, usernames): if name == value: return uid # Ah well raise commands.BadArgument(f"User \"{value}\" not found")
async def copulate(self, ctx:Context, user:Member): ''' Lets you heck someone ''' # Check for NSFW channel if not ctx.channel.is_nsfw(): await ctx.send("This command can't be run in a non-NSFW channel.") return # Check for the most common catches text_processor = CopulateRandomText(self.bot) text = text_processor.process(ctx.author, user) if text: await ctx.send(text) return # Check if they are related x = FamilyTreeMember.get(ctx.author.id) y = FamilyTreeMember.get(user.id) async with ctx.channel.typing(): relationship = x.get_relation(y) if relationship == None or relationship.casefold() == 'partner': pass elif not self.bot.allows_incest(ctx.guild.id): pass else: await ctx.send(text_processor.target_is_relation(ctx.author, user, relationship)) return # Ping out a message for them await ctx.send(text_processor.valid_target(ctx.author, user)) # Wait for a response try: check = AcceptanceCheck(user.id, ctx.channel.id).check m = await self.bot.wait_for('message', check=check, timeout=60.0) response = check(m) except AsyncTimeoutError as e: await ctx.send(text_processor.proposal_timed_out(ctx.author, user), ignore_error=True) return # Process response if response == "NO": await ctx.send(text_processor.request_denied(ctx.author, user)) return await ctx.send(text_processor.request_accepted(ctx.author, user))
async def forceadopt(self, ctx: Context, parent: User, child: User): ''' Adds the child to the specified parent ''' async with self.bot.database() as db: try: await db( 'INSERT INTO parents (parent_id, child_id) VALUES ($1, $2)', parent.id, child.id) except Exception as e: return # Only thrown when multiple people do at once, just return me = FamilyTreeMember.get(parent.id) me._children.append(child.id) them = FamilyTreeMember.get(child.id) them._parent = parent.id await ctx.send("Consider it done.")
async def familysize(self, ctx: Context, user: User = None): ''' Gives you the size of your family tree ''' if user == None: user = ctx.author user = FamilyTreeMember.get(user.id) await ctx.send( f"There are `{len(user.span(expand_upwards=True, add_parent=True))}` people in `{user.get_name(self.bot)}`'s family tree." )
async def treefile(self, ctx: Context, root: Member = None): ''' Gives you the full family tree of a user ''' if root == None: root = ctx.author text = FamilyTreeMember.get(root.id).generate_gedcom_script(self.bot) file = BytesIO(text.encode()) await ctx.send(file=File(file, filename=f'Tree of {root.id}.ged'))
async def on_member_remove(self, member: Member): ''' Checks if you have the member stored, and if not, then removes them from cache and database ''' if self.bot.get_user(member.id) == None: ftm = FamilyTreeMember.get(member.id) if not ftm.is_empty(): async with self.bot.database() as db: await db.destroy(member.id) ftm.destroy()
async def divorce(self, ctx: Context): ''' Divorces you from your current spouse ''' instigator = ctx.author # Get marriage data for the user instigator_data = FamilyTreeMember.get(instigator.id) # See why it could fail if instigator_data.partner == None: await ctx.send( self.divorce_random_text.invalid_instigator(None, None)) return target = ctx.guild.get_member(instigator_data.partner.id) if target == None: target_id = instigator_data.partner.id else: target_id = target.id if instigator_data.partner.id != target_id: await ctx.send(self.divorce_random_text.invalid_target(None, None)) return # At this point they can only be married async with self.bot.database() as db: await db( 'UPDATE marriages SET valid=FALSE where user_id=$1 OR user_id=$2', instigator.id, target_id) await ctx.send( self.divorce_random_text.valid_target(instigator, target)) me = instigator_data me._partner = None them = FamilyTreeMember.get(target_id) them._partner = None
async def parent(self, ctx: Context, user: User = None): ''' Tells you who your parent is ''' if user == None: user = ctx.author user_info = FamilyTreeMember.get(user.id) if user_info.parent == None: await ctx.send(f"`{user!s}` has no parent.") return await ctx.send( f"`{user!s}`'s parent is `{self.bot.get_user(user_info.parent.id)!s}` (`{user_info.parent.id}`)." )
async def kiss(self, ctx: Context, user: Member): ''' Kisses a mentioned user ''' if user == ctx.author: await ctx.send(f"How does one even manage to do that?") return #Check if they are related x = FamilyTreeMember.get(ctx.author.id) y = FamilyTreeMember.get(user.id) relationship = x.get_relation(y) if relationship == None or relationship.casefold() == 'partner': await ctx.send(f"*Kisses {user.mention}*") return else: responses = [ f"Well you two lovebirds may be related but... I'll allow it :smirk:", f"Woah woah, you two are family!", f"Incest is wincest, I guess.", f"You two are related but go off I guess.", ] await ctx.send(choice(responses))
async def familysize(self, ctx: Context, user: User = None): ''' Gives you the size of your family tree ''' if user == None: user = ctx.author await ctx.channel.trigger_typing() user = FamilyTreeMember.get(user.id, ctx.family_guild_id) async with ctx.channel.typing(): span = user.span(expand_upwards=True, add_parent=True) username = await self.bot.get_name(user.id) await ctx.send( f"There are `{len(span)}` people in `{username}`'s family tree.")
async def children(self, ctx: Context, user: UserID = None): ''' Gives you a list of all of your children ''' if user == None: user = ctx.author.id # Setup output variable output = '' # Get the user's info user_name = await self.bot.get_name(user) user_info = FamilyTreeMember.get(user, ctx.family_guild_id) if len(user_info._children) == 0: output += f"`{user_name}` has no children right now." else: output += f"`{user_name}` has `{len(user_info._children)}` child" + { False: "ren", True: "" }.get(len(user_info._children) == 1) + ": " out_names = [] for i in user_info._children: name = await self.bot.get_name(i) out_names.append(f"`{name}` (`{i}`)") output += ', '.join(out_names) + '. ' # Get their partner's info, if any if user_info._partner == None: await ctx.send(output) return user_info = user_info.partner user = await self.bot.get_name(user_info.id) if len(user_info._children) == 0: output += f"\n\nTheir partner, `{user}`, has no children right now." else: output += f"\n\nTheir partner, `{user}`, has `{len(user_info._children)}` child" + { False: "ren", True: "" }.get(len(user_info._children) == 1) + ": " out_names = [] for i in user_info._children: name = await self.bot.get_name(i) out_names.append(f"`{name}` (`{i}`)") output += ', '.join(out_names) + '. ' # Return all output await ctx.send(output)
async def parent(self, ctx: Context, user: UserID = None): ''' Tells you who someone's parent is ''' if user == None: user = ctx.author.id user_info = FamilyTreeMember.get(user, ctx.family_guild_id) user_name = await self.bot.get_name(user) if user_info._parent == None: await ctx.send(f"`{user_name}` has no parent.") return name = await self.bot.get_name(user_info._parent) await ctx.send( f"`{user_name}`'s parent is `{name}` (`{user_info._parent}`).")