def merge_card_images(cards: List[Dict[str, Any]]) -> str: """ Taken from SO, but this method will merge all images in the images folder into one image. All prior images will be side-by-side :param cards: Cards to merge into one image :return: Resting URL of merged image """ cards_to_merge: List[str] = glob.glob(os.path.join(TEMP_CARD_DIR, '*.png')) images: Iterator[Any] = map(PIL.Image.open, cards_to_merge) widths, heights = zip(*(i.size for i in images)) total_width = sum(widths) max_height = max(heights) new_im = PIL.Image.new('RGB', (total_width, max_height)) images = map(PIL.Image.open, cards_to_merge) x_offset = 0 for im in images: new_im.paste(im, (x_offset, 0)) x_offset += im.size[0] combined_name: str = '{}-{}.png'.format(cards[0]['name'].replace('/', '_'), cards[1]['name'].replace('/', '_')) save_url: str = os.path.join(TEMP_CARD_DIR, combined_name) new_im.save(save_url) logging.info('Saved merged image to {}'.format(save_url)) return save_url
def delete_temp_cards() -> None: """ Delete the PNG images in the image folder """ for card in glob.glob(os.path.join(TEMP_CARD_DIR, '*.png')): logging.info('Deleting file {}'.format(card)) os.remove(card)
def download_and_save_card_images(cards: List[Dict[str, Any]]) -> None: """ Download and (temporarily) save card images for tweet processing :param cards: Cards to download and store """ for card in cards: card_image_url: str = card['image_uris']['png'] request_image = download_contents(card_image_url, 'image') with open(os.path.join(TEMP_CARD_DIR, '{}.png'.format(card['name'].replace('//', '_'))), 'wb') as out_file: shutil.copyfileobj(request_image.raw, out_file) logging.info('Saving image of card {}'.format(card['name']))
def start_game(force_new: bool = False) -> None: """ Start the process of validating an old game and/or creating a new one :param force_new: Start a new game, even if one is running. End the old game """ # If contest is over, print results and continue. Otherwise exit if is_active_contest_already(force_new): exit(0) # Clear out the cards directory delete_temp_cards() # Get 2 random cards cards: List[Dict[str, Any]] = download_random_cards(2) card1 = '{}: {}'.format( cards[0]['name'], cards[0]['scryfall_uri'].replace('api', 'card_golf')) card2 = '{}: {}'.format( cards[1]['name'], cards[1]['scryfall_uri'].replace('api', 'card_golf')) for card in cards: logging.info('Card to merge: {}'.format(card['name'])) # Save the images download_and_save_card_images(cards) # Merge the images tweet_image_url: str = merge_card_images(cards) message = ( "Can you make both of these cards show up in a Scryfall search without using 'or'?\n" "• {}\n" "• {}\n" "Reply to this tweet with a Scryfall URL in the next 24 hours to enter!" ).format(card1, card2) # Send the tweet tweet_id: int = send_tweet(message, tweet_image_url) json_entry: Dict[str, Any] = { 'tweet_id': tweet_id, 'cards': [{ 'name': cards[0]['name'], 'url': cards[0]['scryfall_uri'], }, { 'name': cards[1]['name'], 'url': cards[1]['scryfall_uri'], }], } write_to_json_db(TWEET_DATABASE, json_entry)
def download_contents(url: str, download_type: str = 'json') -> Any: """ Download contents from a URL :param url: URL to download :param download_type: Type of download (Default to JSON) :return: Contents """ request_response: Any = {} if download_type == 'json': request_response = requests.get(url=url).json() elif download_type == 'image': request_response = requests.get(url=url, stream=True) logging.info('Downloaded URL {}'.format(url)) return request_response
def test_query(user_name: str, scryfall_url: str) -> str: """ Load up the Scryfall URL tweeted by the user and see if it matches the competition requirements (i.e. is it exclusively the two cards we are looking for) :param user_name: Twitter username :param scryfall_url: Scryfall URL they tweeted :return: Winning query ('' if failed) """ try: query: str = urlparse.parse_qs( urlparse.urlparse(scryfall_url).query)['q'][0] scryfall_api_url = 'https://api.scryfall.com/cards/search?q={}'.format( urlparse.quote_plus(query)) response: Dict[str, Any] = download_contents(scryfall_api_url) if response['total_cards'] != 2: logging.info('{} result has wrong number of cards: {}'.format( user_name, response['total_cards'])) json_db: Dict[str, Any] = load_json_db(TWEET_DATABASE) max_key: str = max(json_db.keys()) valid_cards: List[str] = [ json_db[max_key]['cards'][0]['name'], json_db[max_key]['cards'][1]['name'] ] for card in response['data']: if card['name'] not in valid_cards: logging.info('{} result has wrong card: {}'.format( user_name, card['name'])) return '' if 'or' in query.lower(): logging.info("{} was correct, but they used 'OR': {}".format( user_name, query)) return '' # Correct response! logging.info('{} was correct! [ {} ] ({})'.format( user_name, query, len(query))) return urlparse.unquote(query) except KeyError: logging.info('{} submitted a bad Scryfall URL: {}'.format( user_name, scryfall_url)) return ''
def send_tweet(message_to_tweet: str, url_to_media: str) -> int: """ Send a tweet with an image. :param message_to_tweet: Message to send :param url_to_media: Image to upload :return: Tweet ID (-1 if it failed) :raises Exception: Tweet failed to send for some reason """ logging.info('Tweet to send: {}'.format(message_to_tweet)) try: if url_to_media is not None: resize_image(url_to_media) photo = open(url_to_media, 'rb') status = twitter_api.request('statuses/update_with_media', {'status': message_to_tweet}, {'media[]': photo}) logging.info('Twitter Status Code: {}'.format(status.status_code)) response = TwitterAPI.TwitterResponse(status, False).json() logging.info('Twitter Response Parsed: {}'.format(response)) return int(response['id_str']) raise Exception("No image attached to tweet") except UnicodeDecodeError: logging.exception( 'Your message could not be encoded. Perhaps it contains non-ASCII characters?' ) raise Exception("Tweet failed to send")
def get_results() -> List[Dict[str, Any]]: """ Get the results from the competition and print it out :return: Winner's name and their query """ valid_entries: List[Dict[str, Any]] = [] logging.info('GET RESULTS') json_db: Dict[str, Any] = load_json_db(TWEET_DATABASE) max_key: str = max(json_db.keys()) r = TwitterAPI.TwitterPager(twitter_api, 'statuses/mentions_timeline', { 'count': 200, 'since_id': json_db[max_key]['tweet_id'] }) for item in r.get_iterator(): if 'text' not in item: logging.warning('SUSPEND, RATE LIMIT EXCEEDED: %s\n' % item['message']) break logging.info('[TWEET] ' + item['user']['screen_name'] + ': ' + item['text']) for url in item['entities']['urls']: test_url = url['expanded_url'] if 'scryfall.com' not in test_url: continue logging.info('{} submitted solution: {}'.format( item['user']['screen_name'], test_url)) test_query_results = test_query(item['user']['screen_name'], test_url) if test_query_results: valid_entries.append({ 'name': item['user']['screen_name'], 'length': len(test_query_results), 'query': test_query_results }) return valid_entries