async def projects(self, ctx, *, project_name: str = None): user_lookup = utils.UserLookup(ctx.bot) if not project_name: session = database.new_session() projects = sorted(session.query(database.Project).all(), key=lambda proj: proj.name) users = session.query(database.User).all() lookup = {} for user in users: for p in user.projects: if p.id not in lookup: lookup[p.id] = [] if user.mal_name: lookup[p.id].append( user_lookup.display_name_from_mal(user.mal_name)) message = '\n'.join( map(lambda x: self.project_string(x, lookup), projects)) await utils.safe_say( ctx, f'Use `!project [project_name]` to get details about a project.\n\n{message}' ) else: project_name = project_name.strip() session = database.new_session() await ctx.bot.say( self.project_build_message(ctx, session, user_lookup, project_name))
async def preload_all(self): print('Preloading lists...') session = database.new_session() users = session.query(database.User).filter( database.User.mal_name.isnot(None)).all() for entity in ['anime', 'manga']: print(f'Downloading {entity} lists...') downloaded = self.animelists if entity == 'anime' else self.mangalists dl_stats = self.anime_stats if entity == 'anime' else self.manga_stats async with aiohttp.ClientSession() as session: for user in users: try: list = await self.load_entity_list(user.mal_name, session, entity=entity) downloaded[user.mal_name] = list[entity] dl_stats[user.mal_name] = list['statistics'] except: pass if downloaded: print('Download complete') if entity == 'anime': self.animelists = downloaded self.anime_stats = dl_stats elif entity == 'manga': self.mangalists = downloaded self.manga_stats = dl_stats print('Preloading airing...') await self.require_airing() print('Done.') self.last_update = datetime.datetime.now()
async def db_update(self, ctx): await shared.state.lock_user(ctx, '!user update') try: user_id = ctx.message.author.id session = database.new_session() user = session.query(database.User).filter( database.User.discord_id == user_id).first() if user is None: await ctx.bot.whisper( f'Hello {ctx.message.author.name}, your profile doesn\'t exist yet. Please run `!user setup` to get started!' ) return fields = ', '.join(map(lambda x: f'`{x}`', self.questions.keys())) fields += 'and `projects`' dm = await ctx.bot.whisper( f'Hello {ctx.message.author.name}, You can edit any of the following fields (just type its name, or `done` to stop updating your profile):\n{fields}' ) data = { 'ctx': ctx, 'user': ctx.message.author, 'channel': dm.channel, } while True: response = await self.db_wait_message(data) response = response.lower() if response == 'done': break elif response in ['mal_name', 'gender', 'bio']: setattr(user, response, await self.db_ask(data, response)) session.commit() elif response in ['birthdate']: setattr(user, response, await self.db_ask_date(data, response)) session.commit() elif response == 'country': await self.db_fill_country(ctx, data, session, user) elif response == 'timezone': await self.db_fill_tz(ctx, data, session, user) elif response == 'languages': await self.db_fill_languages(ctx, data, session, user) elif response == 'prog_languages': await self.db_fill_prog_languages(ctx, data, session, user) elif response == 'projects': await self.update_projects(ctx, data, session, user) await ctx.bot.whisper( f'You can edit any of the following fields (just type its name, or `done` to stop updating your profile):\n{fields}' ) await ctx.bot.whisper(f'Thank you!') finally: shared.state.unlock(ctx)
async def list(self, ctx): session = database.new_session() extras = session.query(database.Extras).all() message = '' for extra in extras: options = f' => {extra.options}' if extra.options else '' message += f'**{extra.id}** - {extra.question}{options}\n' await utils.safe_say(ctx, message)
async def require_mal_username(ctx, member): if member is None: member = ctx.message.author session = database.new_session() user = session.query( database.User).filter(database.User.discord_id == member.id).first() if user is None or user.mal_name is None: await ctx.bot.say(f'{member.name} has not set their MAL username!') return None return user.mal_name
def __init__(self, bot): self.bot = bot session = database.new_session() users = session.query(database.User).all() table = {} for user in users: if user.mal_name: table[user.mal_name] = user.discord_id self.mal_table = table self.user_id_cache = {}
async def check_birthdays(bot): print(f'checking birthdays {datetime.datetime.utcnow()}') utils.write_property('last_birthday_check', datetime.datetime.utcnow().strftime('%Y-%m-%d')) session = database.new_session() users = session.query(database.User).filter(database.User.birthdate.isnot(None)).all() today = datetime.datetime.utcnow().strftime('%Y-%m-%d') for user in users: if user.birthdate[4:] == today[4:]: if user.discord_id not in shared.birthday_blacklist: if utils.is_birthdate_valid(user.birthdate): await wish_birthday(bot, user)
async def db_update(self, ctx): await shared.state.lock_user(ctx, '!user extras update') try: session = database.new_session() user = await self.get_user(ctx, session, ctx.message.author.id) extras = session.query(database.Extras).all() user_extras = session.query(database.UserExtras).filter( database.UserExtras.user_id == user.id).all() if len(extras) == 0: await ctx.bot.whisper('No extras available') return done_extras, new_extras = self.sort_extras(extras, user_extras) done_extras_display = '\n'.join( map(lambda x: f'**{x["extra"].id}** - {x["extra"].question}', done_extras)) new_extras_display = '\n'.join( map(lambda x: f'**{x.id}** - {x.question}', new_extras)) if not done_extras_display: done_extras_display = 'None' if not new_extras_display: new_extras_display = 'None' message = f"""To answer a question, simply type its identifier. If you do not want to answer a question, simply type `-` and I will skip it (and remove your previous answer if any). If at any point you want to stop editing questions, type `!quit` (or `done` when selecting questions). Questions you've already answered: {done_extras_display} New questions: {new_extras_display}""" dm = await utils.force_say(message, ctx.bot.whisper) data = { 'ctx': ctx, 'user': ctx.message.author, 'channel': dm.channel, } while True: done_extras, new_extras = await self.db_update_loop( ctx, data, session, user, done_extras, new_extras) finally: shared.state.unlock(ctx)
async def rm(self, ctx, id): session = database.new_session() extra = session.query(database.Extras).filter(database.Extras.id == id).first() if extra: user_extras = session.query(database.UserExtras).filter(database.UserExtras.extras_id == extra.id).all() for item in user_extras: session.delete(item) session.delete(extra) session.commit() await ctx.bot.add_reaction(ctx.message, shared.reaction_ok) else: await ctx.bot.add_reaction(ctx.message, shared.reaction_ko)
async def add(self, ctx, *, raw): parts = raw.split('=>') question = parts[0].strip() session = database.new_session() new_extra = database.Extras(question=question) if len(parts) > 1: options = parts[1].strip() new_extra.options = options session.add(new_extra) session.commit() await ctx.bot.add_reaction(ctx.message, shared.reaction_ok)
async def require_entity_lists(self, ctx, entity): session = database.new_session() users = session.query(database.User).filter( database.User.mal_name.isnot(None)).all() self.cleanup_cache(users) self.check_cache_expiry() if len(self.entitylists(entity)) >= len(users): return self.entitylists(entity) else: errors = [] loaded = 0 loading_message = await ctx.bot.say( f'Refreshing cached {entity}lists...') async with aiohttp.ClientSession() as session: for user in users: if user.mal_name not in self.entitylists(entity): try: if user.mal_name: list = await self.load_entity_list( user.mal_name, session, entity=entity) self.set_entitylists(entity, user.mal_name, list[entity]) self.set_stats(entity, user.mal_name, list['statistics']) loaded += 1 if user.mal_name: await ctx.bot.edit_message( loading_message, f'Refreshing cached {entity}lists... ({loaded}/{len(users)})' ) except Exception as e: errors.append( f'Error getting {user.mal_name}\'s {entity}list: {e}' ) await ctx.bot.delete_message(loading_message) #if errors: # await ctx.bot.edit_message(loading_message, f'Finished loading with {len(errors)} errors.\n') #else: # await ctx.bot.edit_message(loading_message, f'Loaded {len(self.entitylists(entity))} lists.') self.last_update = datetime.datetime.now() return self.entitylists(entity)
async def db_setup(self, ctx): await shared.state.lock_user(ctx, '!user extras setup') try: session = database.new_session() user = await self.get_user(ctx, session, ctx.message.author.id) extras = session.query(database.Extras).all() user_extras = session.query(database.UserExtras).filter( database.UserExtras.user_id == user.id).all() if len(extras) == 0: await ctx.bot.whisper('No extras available') return done_extras, new_extras = self.sort_extras(extras, user_extras) if len(new_extras) == 0: await ctx.bot.whisper( 'You\'ve already answered all extra questions. To edit them, use `!extras update`.' ) return dm = await ctx.bot.whisper( """I will now ask you some random questions. If you do not want to answer a question, simply type `-` and I will skip it. If possible answers are specified, you can only use one of them. Otherwise, you're free to write what you want. If at any point you want to stop answering these questions, type `!quit`. """) data = { 'ctx': ctx, 'user': ctx.message.author, 'channel': dm.channel, } for item in new_extras: await self.ask_extra(data, session, user, item) await ctx.bot.whisper( 'Annnnnnd done! Thank you! You can use `!profile extras [user]` to view other people\'s answers.' ) finally: shared.state.unlock(ctx)
def util_list_all(self, wants_stat_grouping, wants_grouping_detail, valid_user_check, get_grouping_properties, get_display_string, sorting, formatted_group_name): session = database.new_session() users = session.query(database.User).all() items_lookup = {} for user in users: if valid_user_check(user) and user.mal_name: properties = get_grouping_properties(user) for meta, property in properties: if property in items_lookup: items_lookup[property].append((user, meta)) else: items_lookup[property] = [(user, meta)] sorted_items = sorting(items_lookup) if sorting else sorted( items_lookup.keys()) message = '' for item in sorted_items: if wants_grouping_detail: message += f'`{formatted_group_name(item, items_lookup[item])}: `' else: message += f'**{formatted_group_name(item, items_lookup[item])}**: ' n = len(items_lookup[item]) if wants_stat_grouping: if wants_grouping_detail: message += f' **{n}**' else: message += f' {n}' names = ', '.join( map(lambda x: get_display_string(x[0], x[1]), items_lookup[item])) if wants_stat_grouping: if wants_grouping_detail: message += f' *({names})*' else: message += f'{names}' message += '\n' return message
def convert(self): message = self.ctx.message bot = self.ctx.bot match = self._get_id_match() or re.match(r'<@!?([0-9]+)>$', self.argument) server = message.server result = None # Exact discord match or ID if match is None: result = self.convert_get_from_member(server, bot, 'get_member_named') else: user_id = match.group(1) if server: result = server.get_member(user_id) else: result = converter._get_from_servers(bot, 'get_member', user_id) # Exact MAL match session = database.new_session() if result is None: result = self.convert_get_from_mal(session, server, bot, self.argument) # Fuzzy discord match if result is None: result = self.convert_get_from_member(server, bot, 'get_member_named_fuzzy') # Fuzzy MAL match if result is None: result = self.convert_get_from_mal(session, server, bot, f'%{self.argument}%') if result is None: raise errors.BadArgument('Member "{}" not found'.format( self.argument)) return result
async def edit(self, ctx, id, *, raw): session = database.new_session() extra = session.query(database.Extras).filter(database.Extras.id == id).first() if extra: parts = raw.split('=>') question = parts[0].strip() extra.question = question if len(parts) > 1: options = parts[1].strip() extra.options = options else: extra.options = None session.commit() await ctx.bot.add_reaction(ctx.message, shared.reaction_ok) else: await ctx.bot.add_reaction(ctx.message, shared.reaction_ko)
async def profile(self, ctx, first_param: str = None, second_param: str = None): show_extra = None if first_param and (first_param in ['extra', 'extras', '+extra', '+extras']): member_raw = second_param show_extra = first_param elif second_param and (second_param in ['extra', 'extras', '+extra', '+extras']): member_raw = first_param show_extra = second_param else: member_raw = first_param member = await utils.convert_member(ctx, member_raw, optional=True) if member is None: member = ctx.message.author session = database.new_session() user = session.query(database.User).filter( database.User.discord_id == member.id).first() if user is None: await ctx.bot.say(f'No data recorded for {member.name}') return if not show_extra or show_extra.startswith('+'): await ctx.bot.say(embed=self.profile_main_message( ctx, user, member, session, show_extra)) if show_extra: message = self.profile_extra_message(ctx, session, user, member) if message: await utils.force_say(message, ctx.bot.say)
async def users(self, ctx, extras: str = None): session = database.new_session() users = session.query(database.User).all() lookup = {} for user in users: lookup[user.discord_id] = user registered_users_raw = [] registered_users = [] new_users = [] mal_users = lookup server_id = ctx.message.server.id if ctx.message.server and ctx.message.server.id else shared.main_server_id server = ctx.bot.get_server(server_id) for member in server.members: if not member.bot: true_name = utils.UserLookup.display_name_from_user(member) if member.id in lookup: if true_name == lookup[member.id].mal_name: registered_users.append(true_name) else: registered_users.append( f'{true_name} *({lookup[member.id].mal_name})*') registered_users_raw.append(member) mal_users.pop(member.id) else: new_users.append(true_name) message = '' if registered_users: message += '**Registered users**:\n' message += ', '.join(registered_users) message += '\n\n' if new_users: message += '**Users who have not filled their profile yet** (type `!user setup`):\n' message += ', '.join(new_users) message += '\n\n' if mal_users: message += '**Registered users who are not in this server**:\n' message += ', '.join(map(lambda x: x.mal_name, mal_users.values())) message += '\n\n' if extras and extras in ['extra', 'extras']: message += 'Registered users with an extras profile:\n' user_extras = session.query(database.UserExtras).all() extras_lookup = {} for item in user_extras: extras_lookup[item.user_id] = item user_extra_profile = [] for user in registered_users_raw: if lookup[user.id].id in extras_lookup: user_extra_profile.append( utils.UserLookup.display_name_from_user(user)) message += ', '.join(user_extra_profile) message += '\n\n' await utils.safe_say(ctx, message)
async def tz(self, ctx, member_raw: str = None): member = await utils.convert_member(ctx, member_raw, optional=True) if member is None: user_lookup = utils.UserLookup(ctx.bot) session = database.new_session() users = session.query(database.User).filter( database.User.timezone.isnot(None)).all() timezones = {} now = datetime.datetime.utcnow() for user in users: local_time = pytz.utc.localize(now, is_dst=None).astimezone( pytz.timezone(user.timezone)) key = local_time.strftime('%Y%m%d %H:%M:%S') if key in timezones: timezones[key]['data'].append(user) else: timezones[key] = {'tz': local_time, 'data': [user]} days_tz = {} for key in timezones.keys(): day_string = key.split(' ')[0] if day_string not in days_tz: days_tz[day_string] = [] days_tz[day_string].append({ 'sorting': key, 'time': timezones[key]['tz'], 'users': timezones[key]['data'] }) sorted_tz = { k: sorted(v, key=lambda x: x['sorting']) for k, v in days_tz.items() } flat_tz = [] for item in sorted_tz.keys(): flat_tz.append({'day': item, 'data': sorted_tz[item]}) final_tz = sorted(flat_tz, key=lambda x: x['day']) message = '' for day in final_tz: day_string = day['data'][0]['time'].strftime('%A') message += f'**{day_string}:**\n\n' for item in day['data']: display_time = item['time'].strftime('%H:%M:%S') tz_users = ', '.join( map( lambda x: f'{user_lookup.display_name_from_mal(x.mal_name)} *({x.timezone})*', item['users'])) message += f'{display_time}:\n{tz_users}\n\n' await utils.safe_say(ctx, message) else: session = database.new_session() user = session.query(database.User).filter( database.User.discord_id == member.id).first() if user is None or user.timezone is None: await ctx.bot.say( f'{utils.UserLookup.display_name_from_user(member)} has not set their timezone!' ) return tz = pytz.timezone(user.timezone) time = pytz.utc.localize(datetime.datetime.utcnow(), is_dst=None).astimezone(tz) timezone_string = time.strftime('%A %H:%M:%S') await ctx.bot.say(f'{timezone_string} ({user.timezone})\n')
async def db_setup(self, ctx, arg=None): session = database.new_session() user_id = ctx.message.author.id user = session.query( database.User).filter(database.User.discord_id == user_id).first() if user is not None and arg != 'force': await ctx.bot.whisper( f"""You already have a profile, did you mean to edit it using `!user update`? If you want to go through the setup again, use `!user setup force`""") return await shared.state.lock_user(ctx, '!user setup') try: dm = await ctx.bot.whisper( f"""Hello {ctx.message.author.name}, let's get started! I'm going to ask a few questions so we can get to know you. This is just to get a general idea of who you are, it's not for the NSA, I swear. If you don't want to answer one of these question, just type `-` and I'll ignore it or put some default value if needed. You can edit this later using `!user update`.""") data = { 'ctx': ctx, 'user': ctx.message.author, 'channel': dm.channel, } if user is None: user = database.User(discord_id=user_id) session.add(user) user.mal_name = await self.db_ask(data, 'mal_name') session.commit() user.birthdate = await self.db_ask_date(data, 'birthdate') session.commit() user.gender = await self.db_ask(data, 'gender') session.commit() await self.db_fill_country(ctx, data, session, user) await self.db_fill_tz(ctx, data, session, user) await self.db_fill_languages(ctx, data, session, user) await self.db_fill_prog_languages(ctx, data, session, user) user.bio = await self.db_ask(data, 'bio') session.commit() await self.update_projects(ctx, data, session, user) session.commit() await ctx.bot.whisper(f"""That's all for now, thank you! You can see your profile or other people's profile by using `!profile [user]` If you still want to tell us about you, here are some additional commands you can run: `!user extras setup`: Lets you answer some additional random questions `b/waifuset`: A command by BobDono to let you set your waifu You can also run `!bots` for more info about bots, don't hesitate to play with us. Lastly, keep an eye on the channels under ELECTIONS, we run very important waifu wars in them. Yes, this server is for Intellectuals™. Also, please avoid using `@everyone` or `@here` unless it's a real emergency. Instead, you should use `@AMA` if you have a question or need some help.""") finally: shared.state.unlock(ctx)