def main() -> None: if configuration.get('github_user') is None or configuration.get( 'github_password') is None: print('Invalid Config') sys.exit(1) verification_numbers() issues = repo.get_repo().get_issues() for issue in issues: print(issue.title) if issue.state == 'open': process_issue(issue) txt = open('bannable.txt', mode='w') pd = open('pd_bannable.txt', mode='w') for bug in ALL_BUGS: if bug['bannable']: txt.write(bug['card'] + '\n') if bug['pd_legal']: pd.write(bug['card'] + '\n') txt.close() pd.close() bugsjson = open('bugs.json', mode='w') json.dump(ALL_BUGS, bugsjson, indent=2) bugsjson.close()
def cmc(deck_id): name = str(deck_id) + '-cmc.png' if not os.path.exists(configuration.get('charts_dir')): raise DoesNotExistException( 'Cannot store graph images because {dir} does not exist.'.format( dir=configuration.get('charts_dir'))) path = os.path.join(configuration.get('charts_dir'), name) if os.path.exists(path): return path d = deck.load_deck(deck_id) costs = {} for ci in d.maindeck: c = ci.get('card') if c.is_land(): continue if c.mana_cost is None: cost = '0' elif next((s for s in c.mana_cost if '{X}' in s), None) is not None: cost = 'X' else: cost = int(float(c.cmc)) if cost >= 7: cost = '7+' cost = str(cost) costs[cost] = ci.get('n') + costs.get(cost, 0) return image(path, costs)
def login(user=None, password=None): if user is None: user = configuration.get('to_username') if password is None: password = configuration.get('to_password') if user == '' or password == '': print('No TappedOut credentials provided') return url = "https://tappedout.net/accounts/login/" session = fetcher_internal.SESSION response = session.get(url) match = re.search( r"<input type='hidden' name='csrfmiddlewaretoken' value='(\w+)' />", response.text) if match is None: # Already logged in? return csrf = match.group(1) data = { 'csrfmiddlewaretoken': csrf, 'next': '/', 'username': user, 'password': password, } headers = { 'referer': url, } print("Logging in to TappedOut as {0}".format(user)) response = session.post(url, data=data, headers=headers) if response.status_code == 403: print("Failed to log in")
def decksite_url(path='/'): hostname = configuration.get('decksite_hostname') port = configuration.get('decksite_port') if port != 80: hostname = '{hostname}:{port}'.format(hostname=hostname, port=port) return parse.urlunparse((configuration.get('decksite_protocol'), hostname, path, None, None, None))
def __init__(self, db): warnings.filterwarnings('error', category=MySQLdb.Warning) try: self.name = db host = configuration.get('mysql_host') port = configuration.get('mysql_port') if str(port).startswith('0.0.0.0:'): # Thanks Docker :/ port = int(port[8:]) user = configuration.get('mysql_user') passwd = configuration.get('mysql_passwd') self.connection = MySQLdb.connect(host=host, port=port, user=user, passwd=passwd, use_unicode=True, charset='utf8', autocommit=True) self.cursor = self.connection.cursor(MySQLdb.cursors.DictCursor) self.execute('SET NAMES utf8mb4') try: self.execute("USE {db}".format(db=db)) except DatabaseException: print('Creating database {db}'.format(db=db)) self.execute( 'CREATE DATABASE {db} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' .format(db=db)) self.execute('USE {db}'.format(db=db)) except MySQLdb.Error as e: raise DatabaseException( 'Failed to initialize database in `{location}`'.format( location=db)) from e
async def google(self, client: Client, channel: Channel, args: str, author: Member, **_: Dict[str, Any]) -> None: """`!google {args}` Search google for `args`.""" await client.send_typing(channel) api_key = configuration.get('cse_api_key') cse_id = configuration.get('cse_engine_id') if api_key is None or cse_id is None: return await client.send_message( channel, 'The google command has not been configured.') if len(args.strip()) == 0: return await client.send_message( channel, '{author}: No search term provided. Please type !google followed by what you would like to search' .format(author=author.mention)) try: service = build('customsearch', 'v1', developerKey=api_key) res = service.cse().list(q=args, cx=cse_id, num=1).execute() # pylint: disable=no-member if 'items' in res: r = res['items'][0] s = '{title} <{url}> {abstract}'.format(title=r['title'], url=r['link'], abstract=r['snippet']) else: s = '{author}: Nothing found on Google.'.format( author=author.mention) except HttpError as e: if e.resp['status'] == '403': s = 'We have reached the allowed limits of Google API' else: raise e await client.send_message(channel, s)
def run() -> None: wd = configuration.get_str('modo_bugs_dir') if not os.path.exists(wd): subprocess.run(['git', 'clone', 'https://github.com/PennyDreadfulMTG/modo-bugs.git', wd]) os.chdir(wd) subprocess.run(['git', 'pull']) args = sys.argv[2:] if not args: args.extend(['scrape', 'update', 'verify', 'commit']) print('modo_bugs invoked with modes: ' + repr(args)) changes: List[str] = [] if 'scrape' in args: args.extend(['scrape_bb', 'scrape_an']) if 'scrape_bb' in args: scrape_bugblog.main(changes) if 'scrape_an' in args: scrape_announcements.main(changes) if 'update' in args: update.main() if 'verify' in args: verification.main() if 'commit' in args: subprocess.run(['git', 'add', '.']) subprocess.run(['git', 'commit', '-m', 'Updated']) user = configuration.get('github_user') pword = configuration.get('github_password') subprocess.run(['git', 'push', f'https://{user}:{pword}@github.com/PennyDreadfulMTG/modo-bugs.git'])
async def google(ctx: MtgContext, *, args: str) -> None: """Google search""" api_key = configuration.get('cse_api_key') cse_id = configuration.get('cse_engine_id') if api_key is None or cse_id is None: return await ctx.send('The google command has not been configured.') if len(args) == 0: return await ctx.send( '{author}: No search term provided. Please type !google followed by what you would like to search.' .format(author=ctx.author.mention)) try: service = build('customsearch', 'v1', developerKey=api_key) res = service.cse().list(q=args, cx=cse_id, num=1).execute() # pylint: disable=no-member if 'items' in res: r = res['items'][0] s = '{title} <{url}> {abstract}'.format(title=r['title'], url=r['link'], abstract=r['snippet']) else: s = '{author}: Nothing found on Google.'.format( author=ctx.author.mention) except HttpError as e: if e.resp['status'] == '403': s = 'We have reached the allowed limits of Google API' else: raise e await ctx.send(s)
def create_issue(content: str, author: str, location: str = 'Discord', repo_name: str = 'PennyDreadfulMTG/Penny-Dreadful-Tools', exception: Optional[Exception] = None) -> Issue: labels: List[str] = [] if content is None or content == '': return None body = '' if '\n' in content: title, body = content.split('\n', 1) body += '\n\n' else: title = content body += 'Reported on {location} by {author}'.format(location=location, author=author) if request: body += textwrap.dedent(""" -------------------------------------------------------------------------------- Request Method: {method} Path: {full_path} Cookies: {cookies} Endpoint: {endpoint} View Args: {view_args} Person: {id} Referrer: {referrer} Request Data: {safe_data} """.format(method=request.method, full_path=request.full_path, cookies=request.cookies, endpoint=request.endpoint, view_args=request.view_args, id=session.get('id', 'logged_out'), referrer=request.referrer, safe_data=str(safe_data(request.form)))) body += '\n'.join( ['{k}: {v}'.format(k=k, v=v) for k, v in request.headers]) if exception: body += '--------------------------------------------------------------------------------\n' body += exception.__class__.__name__ + '\n' stack = traceback.extract_stack()[:-3] + traceback.extract_tb( exception.__traceback__) pretty = traceback.format_list(stack) body += 'Stack Trace:\n' + ''.join(pretty) + '\n' print(title + '\n' + body, file=sys.stderr) # Only check for github details at the last second to get log output even if github not configured. if not configuration.get('github_user') or not configuration.get( 'github_password'): return None g = Github(configuration.get('github_user'), configuration.get('github_password')) git_repo = g.get_repo(repo_name) if repo_name == 'PennyDreadfulMTG/perf-reports': labels.append(location) if exception: labels.append(exception.__class__.__name__) issue = git_repo.create_issue(title=title, body=body, labels=labels) return issue
def create_github_issue(title, author, repo='PennyDreadfulMTG/Penny-Dreadful-Tools'): if configuration.get('github_user') is None or configuration.get('github_password') is None: return None if title is None or title == '': return None g = Github(configuration.get('github_user'), configuration.get('github_password')) repo = g.get_repo(repo) issue = repo.create_issue(title=title, body='Reported on Discord by {author}'.format(author=author)) return issue
async def get_server(self) -> discord.Guild: for guild in self.bot.guilds: if guild.name == self.bot.user.name: configuration.write('selfguild', guild.id) return guild if configuration.get('selfguild') is None: guild = await self.bot.create_guild(self.bot.user.name) configuration.write('selfguild', guild.id) return guild return self.bot.get_guild(configuration.get('selfguild'))
def __init__(self): lines = [] files = glob.glob( os.path.join(configuration.get('legality_dir'), "Run_*.txt")) if len(files) == 0: files = glob.glob( os.path.join(configuration.get('legality_dir'), "*.jar")) if len(files) == 0: raise DoesNotExistException( 'Invalid configuration. Could not find Legality Checker') self.runs = 0 self.runs_percent = 0 self.cards = [] return for line in fileinput.input(files): lines.append(line.strip()) scores = Counter(lines).most_common() self.runs = scores[0][1] self.runs_percent = round(round(self.runs / 168, 2) * 100) self.cards = [] cs = oracle.cards_by_name() remaining_runs = (168 - self.runs) for name, hits in scores: name = html.unescape(name.encode('latin-1').decode('utf-8')) hits_needed = max(84 - hits, 0) card = cs.get(name) percent = round(round(hits / self.runs, 2) * 100) if remaining_runs == 0: percent_needed = 0 else: percent_needed = round( round(hits_needed / remaining_runs, 2) * 100) if card is None: raise DoesNotExistException( "Legality list contains unknown card '{card}'".format( card=name)) if remaining_runs + hits < 84: status = 'Not Legal' elif hits >= 84: status = 'Legal' else: status = 'Undecided' hits = redact(hits) hits_needed = redact(hits_needed) percent = redact(percent) percent_needed = redact(percent_needed) card.update({ 'hits': hits, 'hits_needed': hits_needed, 'percent': percent, 'percent_hits_needed': percent_needed, 'status': status }) self.cards.append(card)
def __init__(self, db): warnings.filterwarnings('error', category=MySQLdb.Warning) self.name = db self.host = configuration.get('mysql_host') self.port = configuration.get('mysql_port') if str(self.port).startswith('0.0.0.0:'): # Thanks Docker :/ self.port = int(self.port[8:]) self.user = configuration.get('mysql_user') self.passwd = configuration.get('mysql_passwd') self.connect()
def fetch(): all_prices, timestamps = {}, [] for i, url in enumerate(configuration.get('cardhoarder_urls')): s = fetcher_internal.fetch(url) s = ftfy.fix_encoding(s) timestamps.append(dtutil.parse_to_ts(s.split('\n', 1)[0].replace('UPDATED ', ''), '%Y-%m-%dT%H:%M:%S+00:00', dtutil.CARDHOARDER_TZ)) all_prices[i] = parse_cardhoarder_prices(s) url = configuration.get('mtgotraders_url') if url: s = fetcher_internal.fetch(configuration.get('mtgotraders_url')) timestamps.append(dtutil.dt2ts(dtutil.now())) all_prices['mtgotraders'] = parse_mtgotraders_prices(s) if not timestamps: raise TooFewItemsException('Did not get any prices when fetching {urls} ({all_prices})'.format(urls=configuration.get('cardhoarder_urls') + [configuration.get('mtgotraders_url')], all_prices=all_prices)) store(min(timestamps), all_prices)
async def spoiler(self, client: Client, channel: Channel, args: str, author: Member, **_: Dict[str, Any]) -> None: """`!spoiler {cardname}`: Request a card from an upcoming set.""" if len(args) == 0: return await client.send_message( channel, '{author}: Please specify a card name.'.format( author=author.mention)) sfcard = fetcher.internal.fetch_json( 'https://api.scryfall.com/cards/named?fuzzy={name}'.format( name=args)) if sfcard['object'] == 'error': return await client.send_message( channel, '{author}: {details}'.format(author=author.mention, details=sfcard['details'])) imagename = '{set}_{number}'.format(set=sfcard['set'], number=sfcard['collector_number']) imagepath = '{image_dir}/{imagename}.jpg'.format( image_dir=configuration.get('image_dir'), imagename=imagename) if sfcard.get('card_faces'): c = sfcard['card_faces'][0] else: c = sfcard fetcher.internal.store(c['image_uris']['normal'], imagepath) text = emoji.replace_emoji( '{name} {mana}'.format(name=sfcard['name'], mana=c['mana_cost']), client) await client.send_file(channel, imagepath, content=text) oracle.scryfall_import(sfcard['name'])
def if_todays_prices(out: bool = True) -> List[Card]: current_format = multiverse.get_format_id('Penny Dreadful') if out: not_clause = '' compare = '<' else: not_clause = 'NOT' compare = '>=' where = """ c.id {not_clause} IN (SELECT card_id FROM card_legality WHERE format_id = {format}) AND c.name in (SELECT name FROM `{prices_database}`.cache WHERE week {compare} 0.5) AND c.layout IN ({layouts}) """.format(not_clause=not_clause, format=current_format, prices_database=configuration.get('prices_database'), compare=compare, layouts=', '.join([ sqlescape(layout) for layout in multiverse.playable_layouts() ])) rs = db().select(multiverse.cached_base_query(where=where)) cards = [Card(r) for r in rs] return sorted(cards, key=lambda card: card['name'])
def determine_filepath(cards: List[Card]) -> str: imagename = basename(cards) # Hash the filename if it's otherwise going to be too large to use. if len(imagename) > 240: imagename = hashlib.md5(imagename.encode('utf-8')).hexdigest() filename = imagename + '.jpg' return '{dir}/{filename}'.format(dir=configuration.get('image_dir'), filename=filename)
def league_api() -> Response: lg = league.active_league(should_load_decks=True) pdbot = request.form.get('api_token', None) == configuration.get('pdbot_api_token') if not pdbot: lg.decks = [d for d in lg.decks if not d.is_in_current_run()] return return_json(lg)
def times_from_location(q: str, twentyfour: bool) -> Dict[str, List[str]]: api_key = configuration.get('google_maps_api_key') if not api_key: raise NotConfiguredException('No value found for google_maps_api_key') url = 'https://maps.googleapis.com/maps/api/geocode/json?address={q}&key={api_key}&sensor=false'.format( q=fetch_tools.escape(q), api_key=api_key) info = fetch_tools.fetch_json(url) if 'error_message' in info: return info['error_message'] try: location = info['results'][0]['geometry']['location'] except IndexError as e: raise TooFewItemsException(e) from e url = 'https://maps.googleapis.com/maps/api/timezone/json?location={lat},{lng}×tamp={timestamp}&key={api_key}&sensor=false'.format( lat=fetch_tools.escape(str(location['lat'])), lng=fetch_tools.escape(str(location['lng'])), timestamp=fetch_tools.escape(str(dtutil.dt2ts(dtutil.now()))), api_key=api_key) timezone_info = fetch_tools.fetch_json(url) if 'error_message' in timezone_info: return timezone_info['error_message'] if timezone_info['status'] == 'ZERO_RESULTS': raise TooFewItemsException(timezone_info['status']) try: timezone = dtutil.timezone(timezone_info['timeZoneId']) except KeyError as e: raise TooFewItemsException( f'Unable to find a timezone in {timezone_info}') from e return { current_time(timezone, twentyfour): [info['results'][0]['formatted_address']] }
def ad_hoc() -> int: dist = Distribution(dict( name='Penny-Dreadful-Tools' )) dist.message_extractors = { # type: ignore 'decksite': [ ('**.py', 'python', {}), ('**.mustache', extract_mustache, {}) ], 'logsite': [ ('**.py', 'python', {}), ('**.mustache', extract_mustache, {}) ] } compiler = frontend.extract_messages(dist) compiler.initialize_options() compiler.output_file = './shared_web/translations/messages.pot' compiler.input_paths = ['decksite', 'logsite'] compiler.finalize_options() compiler.run() api_key = configuration.get('poeditor_api_key') if api_key is None: return exitcode() client = POEditorAPI(api_token=api_key) client.update_terms('162959', './shared_web/translations/messages.pot') return exitcode()
def test_fallbackimagedownload() -> None: filepath = '{dir}/{filename}'.format(dir=configuration.get('image_dir'), filename='nalathni-dragon.jpg') if fetch_tools.acceptable_file(filepath): os.remove(filepath) c = [oracle.load_card('Nalathni Dragon')] assert image_fetcher.download_image(c) is not None
def run(): api_key = configuration.get("poeditor_api_key") if api_key is None: print("Missing poeditor.com API key") return client = POEditorAPI(api_token=api_key) languages = client.list_project_languages("162959") # pull down translations for locale in languages: print("Found translation for {code}: {percent}%".format(code=locale['code'], percent=locale['percentage'])) if locale['percentage'] > 0: path = os.path.join('decksite', 'translations', locale['code'].replace('-', '_'), 'LC_MESSAGES') if not os.path.exists(path): os.makedirs(path) pofile = os.path.join(path, 'messages.po') print('Saving to {0}'.format(pofile)) if os.path.exists(pofile): os.remove(pofile) client.export("162959", locale['code'], local_file=pofile) # Compile .po files into .mo files compiler = compile_catalog() compiler.directory = os.path.join('decksite', 'translations') compiler.domain = ['messages'] compiler.run() # hack for English - We need an empty folder so that Enlish shows up in the 'Known Languages' list. path = os.path.join('decksite', 'translations', 'en', 'LC_MESSAGES') if not os.path.exists(path): os.makedirs(path)
def cache(): db = database.get_database(configuration.get('prices_database')) now = int(time.time()) week = now - 60 * 60 * 24 * 7 month = now - 60 * 60 * 24 * 7 * 30 last_rotation = int(rotation.last_rotation().timestamp()) sql = 'SELECT MAX(`time`) FROM low_price' latest = db.value(sql) db.begin() db.execute('DELETE FROM cache') sql = """ INSERT INTO cache (`time`, name, price, low, high, week, month, season) SELECT MAX(`time`) AS `time`, name, MIN(CASE WHEN `time` = %s THEN price END) AS price, MIN(CASE WHEN `time` > %s THEN price END) AS low, MAX(CASE WHEN `time` > %s THEN price END) AS high, AVG(CASE WHEN `time` > %s AND price = 1 THEN 1 WHEN `time` > %s THEN 0 END) AS week, AVG(CASE WHEN `time` > %s AND price = 1 THEN 1 WHEN `time` > %s THEN 0 END) AS month, AVG(CASE WHEN `time` > %s AND price = 1 THEN 1 WHEN `time` > %s THEN 0 END) AS season FROM low_price GROUP BY name; """ db.execute(sql, [ latest, last_rotation, last_rotation, week, week, month, month, last_rotation, last_rotation ]) db.commit()
async def handle_command(message, bot): parts = message.content.split(' ', 1) method = find_method(parts[0]) if parts[0].lower() in configuration.get('otherbot_commands').split(','): return args = "" if len(parts) > 1: args = parts[1] if method is not None: try: if method.__code__.co_argcount == 5: await method(Commands, bot, message.channel, args, message.author) elif method.__code__.co_argcount == 4: await method(Commands, bot, message.channel, args) elif method.__code__.co_argcount == 3: await method(Commands, bot, message.channel) elif method.__code__.co_argcount == 2: await method(Commands, bot) elif method.__code__.co_argcount == 1: await method(Commands) except Exception as e: # pylint: disable=broad-except print('Caught exception processing command `{cmd}`'.format(cmd=message.content)) print(traceback.format_exc()) await bot.client.send_message(message.channel, 'I know the command `{cmd}` but I could not do that.'.format(cmd=parts[0])) await getattr(Commands, 'bug')(Commands, bot, message.channel, 'Command failed with {c}: {cmd}'.format(c=e.__class__.__name__, cmd=message.content), message.author) else: await bot.client.send_message(message.channel, 'Unknown command `{cmd}`. Try `!help`?'.format(cmd=parts[0]))
def setup_session(url: str) -> None: discord = make_session(state=session.get('oauth2_state')) token = discord.fetch_token(TOKEN_URL, client_secret=OAUTH2_CLIENT_SECRET, authorization_response=url) session.permanent = True session['oauth2_token'] = token discord = make_session(token=session.get('oauth2_token')) user = discord.get(API_BASE_URL + '/users/@me').json() session['id'] = user['id'] session['discord_id'] = user['id'] session['discord_locale'] = user['locale'] guilds = discord.get(API_BASE_URL + '/users/@me/guilds').json() wrong_guilds = False # protect against an unexpected response from discord session['in_guild'] = False session['admin'] = False session['demimod'] = False for guild in guilds: if isinstance(guild, dict) and 'id' in guild: if guild['id'] == configuration.get('guild_id'): session['admin'] = ( guild['permissions'] & 0x10000000 ) != 0 # Check for the MANAGE_ROLES permissions on Discord as a proxy for "is admin". session['demimod'] = ( guild['permissions'] & 0x20000 ) != 0 # Check for the "Mention @everyone" permissions on Discord as a proxy for "is demimod". session['in_guild'] = True else: wrong_guilds = True if wrong_guilds: logger.warning( 'auth.py: unexpected discord response. Guilds: {g}'.format( g=guilds))
def get(self) -> comp.Competition: lg = league.active_league(should_load_decks=True) pdbot = request.form.get('api_token', None) == configuration.get('pdbot_api_token') if not pdbot: lg.decks = [d for d in lg.decks if not d.is_in_current_run()] return lg
def run() -> None: api_key = configuration.get('poeditor_api_key') if api_key is None: logger.warning('Missing poeditor.com API key') return client = POEditorAPI(api_token=api_key) languages = client.list_project_languages('162959') # pull down translations for locale in languages: logger.warning('Found translation for {code}: {percent}%'.format(code=locale['code'], percent=locale['percentage'])) if locale['percentage'] > 0: path = os.path.join('shared_web', 'translations', locale['code'].replace('-', '_'), 'LC_MESSAGES') if not os.path.exists(path): os.makedirs(path) pofile = os.path.join(path, 'messages.po') logger.warning('Saving to {0}'.format(pofile)) if os.path.exists(pofile): os.remove(pofile) client.export(project_id='162959', language_code=locale['code'], local_file=pofile, filters=['translated', 'not_fuzzy']) # Compile .po files into .mo files validate_translations.ad_hoc() compiler = compile_catalog() compiler.directory = os.path.join('shared_web', 'translations') compiler.domain = ['messages'] compiler.run() # hack for English - We need an empty folder so that Enlish shows up in the 'Known Languages' list. path = os.path.join('shared_web', 'translations', 'en', 'LC_MESSAGES') if not os.path.exists(path): os.makedirs(path)
async def background_task_tournaments(): await BOT.client.wait_until_ready() tournament_channel_id = configuration.get('tournament_channel_id') if not tournament_channel_id: return channel = discord.Object(id=tournament_channel_id) while not BOT.client.is_closed: info = tournaments.next_tournament_info() diff = info['next_tournament_time_precise'] if diff <= 14400: embed = discord.Embed(title=info['next_tournament_name'], description='Starting in {0}.'.format(info['next_tournament_time']), colour=0xDEADBF) embed.set_image(url=fetcher.decksite_url('/favicon-152.png')) await BOT.client.send_message(channel, embed=embed) if diff <= 300: # Five minutes, final warning. Sleep until the tournament has started. timer = 301 elif diff <= 1800: # Half an hour. Sleep until 5 minute warning. timer = diff - 300 elif diff <= 3600: # One hour. Sleep until half-hour warning. timer = diff - 1800 else: # Wait until four hours before tournament. timer = min(3600, diff - 14400) await asyncio.sleep(timer)
async def honk(self, channel): emojis = configuration.get('honk_emoji') if not emojis: return chosen = random.choice(emojis) emoji = await self.bot.get_cog('SelfGuild').get_emoji(chosen) await channel.send(str(emoji))
def test_imagedownload() -> None: filepath = '{dir}/{filename}'.format(dir=configuration.get('image_dir'), filename='island.jpg') if fetcher_internal.acceptable_file(filepath): os.remove(filepath) c = [oracle.load_card('Island')] assert image_fetcher.download_image(c) is not None