def __init__(self, **kwargs): if kwargs['is_pm'] and len(kwargs['full_command']) > 1: kwargs['room'] = find_true_name(kwargs['full_command'][1]) super().__init__(**kwargs) if self.is_pm: if self.command in ['mal', 'anime', 'manga']: self.room = const.ANIME_ROOM else: self.min_args += 1 self.usage_msg += '[ROOM] ' if self.command in ['anime', 'manga']: self.usage_msg += 'SERIES NAME' if self.command in ['randanime', 'randmanga']: self.usage_msg += '[GENRES]' if self.command == 'mal': self.req_rank_pm = ' ' self.usage_msg += '[USERNAME] [-r CATEGORIES]' self.mal_args = mal_arg_parser(' '.join(self.args), self.true_caller) if self.mal_args.roll is not None: self.pm_response = self.is_pm
def update_info(self, info_line): parts = info_line.split('|') if len(parts) <= 2: return if parts[1] == 'player': self.players.append(find_true_name(parts[3]))
def check_answer(guess, answers, exact=False): ''' Checks if a guess is correct for a trivia question. Args: guess (str): The raw guess answers (str list): Base list of acceptable answers Returns: An empty string if the guess is incorrect, else the matching answer from answers. ''' t_guess = find_true_name(guess) for answer in answers: t_answer = find_true_name(answer) if (t_guess == t_answer): return answer elif exact: continue elif t_answer in t_guess: return answer # The heuristic for generating aliases for the answers is as follows - # given an answer, valid prefixes consist of whole alphanumeric chunks # (separated by non-alphanumeric chars), starting from the beginning of # the answer. If the guess matches any of these prefixes, and is at least # 8 characters long, it is counted as correct. answer_parts = re.findall('([a-zA-Z0-9]+)', answer) acceptable = [] total = '' for part in answer_parts: total += part.lower() if len(total) >= 8: acceptable.append(total) if ":" in answer: prefix = answer.split(':')[0] acceptable.append(find_true_name(prefix)) if t_guess in acceptable: return answer return ''
async def steam_user_rand_series(putter, id64, username, caller, ctx): true_caller = find_true_name(caller) prefix = f'{ctx}|' if ctx == 'pm': prefix = f'|/w {true_caller},' games = [] async with aiohttp.ClientSession(trust_env=True) as session: steam_key = os.getenv('STEAM_KEY') async with session.get( f'{STEAM_API}IPlayerService/GetOwnedGames/v0001/?key={steam_key}&steamid={id64}' ) as r: resp = await r.text() try: games = json.loads(resp)['response']['games'] except: await putter( f'{prefix} No games found for {username} with the given specifications.' ) return game_info = None while True: if len(games) == 0: await putter( f'{prefix} No games found for {username} with the given specifications.' ) return rand_game = random.choice(games) game_id = rand_game['appid'] game_info = await steam_game_info(game_id) if not game_info: games.remove(rand_game) await asyncio.sleep(1) continue elif game_info['type'] != 'game': games.remove(rand_game) await asyncio.sleep(1) continue else: break rand_title = game_info['name'] msg = f'{prefix}{caller} rolled {rand_title}' await putter(msg)
async def gen_mangadex_question(self, session): series_id = '' cover_id = '' answers = [] while not series_id: await asyncio.sleep(0.2) async with session.get(f'{const.DEX_API}manga/random') as r: rand_series = await r.json() if r.status != 200: await asyncio.sleep(3) continue series_info = rand_series['data'] if series_info['attributes']['contentRating'] != 'safe': continue skip = False for tag in series_info['attributes']['tags']: if tag['attributes']['name']['en'] in [ 'Doujinshi', 'Oneshot' ]: skip = True break if skip: continue if not self.duplicate_check(series_info['id']): series_id = series_info['id'] answers = [series_info['attributes']['title']['en']] for title in series_info['attributes']['altTitles']: if len(find_true_name(title['en'])) > 0: answers.append(title['en']) async with session.get(f'{const.DEX_API}cover', params={'manga[]': [series_id]}) as r: covers = await r.json() cover_info = random.choice(covers['data']) cover_id = cover_info['attributes']['fileName'] img_url = f'https://uploads.mangadex.org/covers/{series_id}/{cover_id}.256.jpg' img_uhtml = gen_uhtml_img_code(img_url, height_resize=PIC_SIZE) await self.questions.put( ['/adduhtml {}, {}'.format(UHTML_NAME, img_uhtml), answers])
async def evaluate(self): if self.check_eligible(): await self.pm_msg(self.msg) return '' arg_offset = 1 if self.is_pm else 0 if self.command == 'song_add': title = ' '.join(self.args[arg_offset:-1]) url = self.args[-1] song_exists = await self.db_man.execute( "SELECT * FROM songs WHERE " f"room='{self.room}' AND url='{url}'") if song_exists: self.msg = f'This url already exists in the song pool for {self.room}.' else: # Sqlite escape title = title.replace("'", "''") await self.db_man.execute( "INSERT INTO songs (room, title, url) " f"VALUES ('{self.room}', '{title}', '{url}')") self.msg = f'Added {title} to {self.room} song pool.' elif self.command == 'song_rm': title = ' '.join(self.args[arg_offset:]) self.msg = f'{title} not found in song pool.' room_songs = await self.db_man.execute("SELECT title FROM songs " f"WHERE room='{self.room}'") for s in list(sum(room_songs, ())): if find_true_name(s) == find_true_name(title): escaped_s = s.replace("'", "''") await self.db_man.execute( "DELETE FROM songs WHERE " f"room='{self.room}' AND title='{escaped_s}'") self.msg = f'Deleted all songs called {title} from song pool.' elif self.command == 'song_list': self.msg = f'No songs found for {self.room}.' song_exists = await self.db_man.execute( "SELECT title, url FROM songs " f"WHERE room='{self.room}'") if song_exists: room_songs = {} for song in song_exists: room_songs[song[0]] = song[1] header_text = monospace_table_row([('Song Title', 100), ('Link', 25)]) header_text += '\n' + '-' * 146 box_text = '' for s in sorted(room_songs.keys()): box_text += monospace_table_row([(s, 100), (room_songs[s], 25)]) box_text += '\n' r = requests.post( const.PASTIE_API, data=f'{header_text}\n{box_text}'.encode('utf-8')) if r.status_code == 200: self.msg = f"""https://pastie.io/raw/{r.json()['key']}""" else: self.msg = 'Unable to generate song list at this time.' elif self.command == 'randsong': song_exists = await self.db_man.execute( "SELECT title, url FROM songs " f"WHERE room='{self.room}'") if not song_exists: self.msg = f'There are no songs for {self.room}!' else: rand_song = random.choice(song_exists) # Decode the URL because PS re-encodes it self.msg = f'[[{rand_song[0]}<{urllib.parse.unquote(rand_song[1])}>]]' return self.msg
async def evaluate(self): if self.check_eligible(): await self.pm_msg(self.msg) return '' arg_offset = 1 if self.is_pm else 0 if self.command == 'emote_add': emote = self.args[arg_offset].lower() if emote.endswith(','): emote = emote[:-1] if find_true_name(emote) != emote: await self.pm_msg('Emotes must be only letters and/or numbers.' ) return emote_url = self.args[arg_offset + 1] if 'discordapp' in emote_url: await self.pm_msg('Discord URLs do not work as emotes.') return emote_exists = await self.db_man.execute( "SELECT * FROM emotes WHERE " f"room='{self.room} AND name={emote}'") if emote_exists: await self.db_man.execute( f"UPDATE emotes SET url='{emote_url}' " f"WHERE room='{self.room}' AND name='{emote}'") else: await self.db_man.execute( "INSERT INTO emotes (room, name, url) " f"VALUES ('{self.room}', '{emote}', '{emote_url}')") self.msg = f'Set :{emote}: to show {emote_url}.' elif self.command == 'emote_rm': emote = find_true_name(self.args[arg_offset]) emote_exists = await self.db_man.execute( "SELECT * FROM emotes WHERE " f"room='{self.room}' AND name='{emote}'") if not emote_exists: await self.pm_msg(f'{self.room} does not have emote {emote}.') return await self.db_man.execute("DELETE FROM emotes WHERE " f"room='{self.room}' AND name='{emote}'") self.msg = f'Removed {emote} from {self.room}.' elif self.command == 'emote_list': self.msg = 'No emotes found.' emote_list = await self.db_man.execute("SELECT name FROM emotes " f"WHERE room='{self.room}'") if emote_list: # Flatten emote_list = list(sum(emote_list, ())) self.msg = f'!code {self.room} emotes: ' + ', '.join( emote_list) elif self.command == 'emote_stats': self.msg = f'No emotes found for {self.room}.' emote_list = await self.db_man.execute( "SELECT name, times_used FROM emotes " f"WHERE room='{self.room}'") if emote_list: header_text = monospace_table_row([('Emote', 30), ('Times Used', 12)]) header_text += '\n' + '-' * 44 box_text = '' for e in sorted(emote_list, key=lambda x: x[1], reverse=True): box_text += monospace_table_row([(e[0], 30), (e[1], 12)]) box_text += '\n' r = requests.post(const.PASTIE_API, data=f'{header_text}\n{box_text}') if r.status_code == 200: self.msg = f"""https://pastie.io/raw/{r.json()['key']}""" else: self.msg = 'Unable to generate emote stats at this time.' return self.msg
async def evaluate(self): self.msg = '/adduhtml ' eligibility = self.check_eligible() if eligibility and eligibility != 2: await self.pm_msg(self.msg) return '' elif eligibility: if self.is_pm and not self.room: await self.pm_msg(self.usage_with_error('')) return '' elif self.is_pm and self.bot.roomlist[self.room].get_user( self.caller): self.msg = f'/sendprivateuhtml {self.true_caller}, ' else: await self.pm_msg(f'You can only use {self.command} in PMs. ' 'Make sure you\'re in the specified room.') return '' elif self.is_pm: self.msg = f'/sendprivateuhtml {self.true_caller}, ' if self.command == 'plebs': uhtml = gen_uhtml_img_code(const.PLEB_URL, height_resize=250) self.msg += f'hippo-pleb, {uhtml}' elif self.command == 'calendar': curr_day_str = curr_cal_date() calendar = json.load(open(const.CALENDARFILE)) if self.room not in calendar: calendar[self.room] = {curr_day_str: []} json.dump(calendar, open(const.CALENDARFILE, 'w', indent=4)) if not calendar[self.room][curr_day_str]: return 'No images found for this date.' date_imgs = calendar[self.room][curr_day_str] uhtml = gen_uhtml_img_code(random.choice(date_imgs), height_resize=200) self.msg += f'hippo-calendar, {uhtml}' elif self.command == 'birthday': await self.bot.send_birthday_text(automatic=False) elif self.command == 'anime': query = ' '.join(self.args) self.msg += await anilist_search('anime', query, self.bot.anilist_man) elif self.command == 'manga': query = ' '.join(self.args) self.msg += await anilist_search('manga', query, self.bot.anilist_man) elif self.command == 'randanime': genres = [] tags = [] true_args = list(map(find_true_name, self.args)) for g in const.ANILIST_GENRES: if find_true_name(g) in true_args: genres.append(g) for t in list(const.ANILIST_TAGS): if find_true_name(t) in true_args: tags.append(t) self.msg += await anilist_rand_series('anime', self.bot.anilist_man, genres=genres, tags=tags) elif self.command == 'randmanga': genres = [] tags = [] true_args = list(map(find_true_name, self.args)) for g in const.ANILIST_GENRES: if find_true_name(g) in true_args: genres.append(g) for t in list(const.ANILIST_TAGS): if find_true_name(t) in true_args: tags.append(t) self.msg += await anilist_rand_series('manga', self.bot.anilist_man, genres=genres, tags=tags) elif self.command == 'mal': true_mal_user = find_true_name(''.join(self.mal_args.username)) if self.mal_args.roll is not None: media = ['anime', 'manga'] if 'anime' not in self.mal_args.roll: media.remove('anime') if 'manga' not in self.mal_args.roll: media.remove('manga') return_msg = await self.bot.mal_man.user_rand_series( true_mal_user, media, anotd=self.is_anotd) if return_msg.startswith('rolled'): self.msg = f'{self.caller} {return_msg}' else: self.msg = return_msg else: return_msg = await self.bot.mal_man.show_user( true_mal_user, self.bot.jikan_man) if is_uhtml(return_msg): self.msg += f'hippo-{true_mal_user}mal, {return_msg}' elif self.is_pm: self.pm_response = self.is_pm self.msg = return_msg else: self.msg = return_msg return self.msg
async def gen_am_base(self, session, anilist_man): query = ''' query ($page: Int, $perpage: Int) { Page (page: $page, perPage: $perpage) { pageInfo { total } media (CATEGORIES_PLACEHOLDER minimumTagRank: 50, isAdult: false, sort: SORT_PLACEHOLDER) { id idMal type description title { english userPreferred romaji } coverImage { large } bannerImage } } } ''' if not self.category_params: if 'all' not in self.categories: media = [] genres = [] tags = [] for c in self.categories: true_c = find_true_name(c) for m in const.ANILIST_MEDIA: if true_c == find_true_name(m): media.append(m) break for g in const.ANILIST_GENRES: if true_c == find_true_name(g): genres.append(g) break for t in const.ANILIST_TAGS: if true_c == find_true_name(t): tags.append(t) break if media: self.category_params.append( f'format_in: {", ".join(media)}') if genres: self.category_params.append( f'genre_in: {json.dumps(genres)}') if tags: self.category_params.append(f'tag_in: {json.dumps(tags)}') if self.excludecats: media = [] genres = [] tags = [] for c in self.excludecats: true_c = find_true_name(c) for m in const.ANILIST_MEDIA: if true_c == find_true_name(m): media.append(m) break for g in const.ANILIST_GENRES: if true_c == find_true_name(g): genres.append(g) break for t in const.ANILIST_TAGS: if true_c == find_true_name(t): tags.append(t) break if media: self.category_params.append( f'format_not_in: {json.dumps(media)}') if genres: self.category_params.append( f'genre_not_in: {json.dumps(genres)}') if tags: self.category_params.append( f'tag_not_in: {json.dumps(tags)}') category_params_str = ','.join(self.category_params) if category_params_str: category_params_str += ',' query = query.replace('CATEGORIES_PLACEHOLDER', category_params_str) sort = 'SCORE_DESC' if self.by_rating else 'POPULARITY_DESC' query = query.replace('SORT_PLACEHOLDER', sort) # Get max_rank if not self.max_rank: query_vars = {'page': 1, 'perpage': 1} async with anilist_man.lock(): self.max_rank = await anilist_num_entries( query, query_vars, session) if not self.max_rank: for task in asyncio.all_tasks(): if task.get_name() == 'trivia-{}'.format(self.room): task.cancel() return rank = 0 if self.max_rank < self.num_qs: self.series_exist = False for task in asyncio.all_tasks(): if task.get_name() == 'trivia-{}'.format(self.room): task.cancel() return diff_scale = max(1.1, math.log(self.max_rank, 10) / 1.5) std_dev_scale = max(10, diff_scale**2) while rank < 1 or rank > self.max_rank: rank = int( random.gauss(self.max_rank // ((diff_scale)**(10 - self.diff)), (std_dev_scale * self.diff / 2))) all_series = [] roll_query_vars = {'page': rank, 'perpage': 1} # Anilist pageInfo is flaky, resulting in the possibility of overshooting the upper bound at higher diffs while not all_series: async with anilist_man.lock(): async with session.post(const.ANILIST_API, json={ 'query': query, 'variables': roll_query_vars }) as r: resp = await r.json() if r.status != 200: for task in asyncio.all_tasks(): if task.get_name() == 'trivia-{}'.format( self.room): task.cancel() return all_series = resp['data']['Page']['media'] roll_query_vars['page'] = math.floor(roll_query_vars['page'] * 0.8) series_data = all_series[0] aliases = [] for title in series_data['title'].values(): if title: aliases.append(title) slug = { 'img_url': series_data['coverImage']['large'], 'description': series_data['description'], 'answers': aliases, 'rank': rank, 'id': series_data['id'] } is_nsfw = await self.bot.mal_man.is_nsfw(series_data['type'].lower(), series_data['idMal']) if is_nsfw: slug = slug.fromkeys(slug, None) return slug
def get_user(self, username): for u in self.users: if u.true_name == find_true_name(username): return u return None
def remove_user(self, username): for u in self.users: if u.true_name == find_true_name(username): self.users.remove(u)
def __init__(self, username, rank=' '): self.name = username self.true_name = find_true_name(self.name) self.rank = rank