diff --git a/README.md b/README.md index 8c7a91d..cda052f 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,48 @@ # not-my-locker-room -pip install requests +### How to use the code in this repo -Add a yaml file with data for title, tagline, photocredit, heading and a list of -social media links. See content.yml for an example. Then run -generate_homepage_from_content.py to build the page. For example: +First make sure you have all the necessary modules: `pip install -r requirements.txt` +Add a yaml file with data for title, tagline, photocredit, heading, and a list of +social media links (see `content.yml` or `dummy_content.yml` for examples). Then run +`generate_homepage_from_content.py` to build the page. For example: +``` ./generate_homepage_from_content.py --content content.yml --outfile not_my_locker_room.html +``` or +``` ./generate_homepage_from_content.py --content endorsements.yml --outfile endorsements.html +``` + +You can always run `./generate_homepage_from_content.py -h` for help & usage instructions. + +### How to add a new type of social content +Say you want to embed some content from hip social media platform Latergram, which has an API endpoint that takes a content URL and returns you embed HTML code for that content. + +**Step 1**: add a constant for your new content type in the `# keys and accepted values` section: +``` +CONTENT_TYPE_LATERGRAM = 'latergram' +``` +We can now match any social media row in the `content.yml` file of `type: latergram` to the appropriate API info. + +**Step 2**: add a key/value pair in the `API_INFOS` dict.: +``` +API_INFOS = { + ... + CONTENT_TYPE_LATERGRAM: { + 'endpoint': 'https://api.latergram.com/oembed/?url=%s', + 'response_html_key': 'html', + 'encode_url': False + }, + ... +} +``` +The key should be the constant for your content type defined in step 1. The value is a dict. with the following pieces of information: +* `endpoint`: Latergram's API endpoint for getting embed HTML code, prepared for string formatting (i.e. put `%s` wherever the URL to the content you're trying to embed should go). Presumes the endpoint requires no other params. besides a content URL. +* `response_html_key`: assuming a JSON blob as a response from the endpoint, this is the key where the embed HTML is stored (for instance, look at a [Twitter oEmbed example response](https://dev.twitter.com/rest/reference/get/statuses/oembed#example-response); the `response_html_key` in this case would be `html`.) +* `encode_url`: set to `True` if the API endpoint expects a URL-encoded content URL, otherwise set to `False`. \ No newline at end of file diff --git a/generate_homepage_from_content.py b/generate_homepage_from_content.py index 3fb093a..708534a 100755 --- a/generate_homepage_from_content.py +++ b/generate_homepage_from_content.py @@ -16,15 +16,14 @@ DEFAULT_PAGE_TEMPLATE_PATH = './index_template.html' DEFAULT_OUTFILE_PATH = 'index_generated.html' -# CSV keys and accepted values -CSV_KEY_TYPE = 'type' -CSV_KEY_URL = 'url' -CSV_KEY_QUOTE = 'quote' +# keys and accepted values +CONTENT_KEY_SOCIAL = 'social' +CONTENT_KEY_TYPE = 'type' +CONTENT_KEY_URL = 'url' +CONTENT_KEY_QUOTE = 'quote' CONTENT_TYPE_TWITTER = 'twitter' CONTENT_TYPE_INSTAGRAM = 'instagram' CONTENT_TYPE_WEBSITE = 'website' -CONTENT_TYPES = [CONTENT_TYPE_TWITTER, CONTENT_TYPE_INSTAGRAM, CONTENT_TYPE_WEBSITE] -EMBEDDED_CONTENT_TYPES = [CONTENT_TYPE_TWITTER, CONTENT_TYPE_INSTAGRAM] # HTML templates CONTENT_CONTAINER = '
%s
' @@ -32,6 +31,19 @@ '
%(website_url)s
' # API endpoints and keys +API_INFOS = { + CONTENT_TYPE_TWITTER: { + 'endpoint': 'https://publish.twitter.com/oembed?url=%s', + 'response_html_key': 'html', + 'encode_url': True + }, + CONTENT_TYPE_INSTAGRAM: { + 'endpoint': 'https://api.instagram.com/oembed/?url=%s', + 'response_html_key': 'html', + 'encode_url': False + } +} + TWITTER_OEMBED_ENDPOINT = 'https://publish.twitter.com/oembed?url=%s' TWITTER_HTML_KEY = 'html' INSTAGRAM_OEMBED_ENDPOINT = 'https://api.instagram.com/oembed/?url=%s' @@ -68,6 +80,25 @@ def parse_command_line_args(): return parser.parse_args() +def make_embed_code_getter(endpoint, response_html_key, encode_url): + def get_embed_code_from_api(url): + if encode_url: + query_path = endpoint % urllib.quote(url) + else: + query_path = endpoint % url + + response = requests.get(query_path) + if response.status_code != 200: + print ('Request to oEmbed endpoint "%s" failed with ' + 'status code %d, message %s' % + (query_path, response.status_code, response.content)) + return + + result_json = json.loads(response.content) + return result_json[response_html_key] + return get_embed_code_from_api + + def get_twitter_embed_code(url): """Given a URL, call twitter api for embed code.""" # Twitter API expects url-encoded url @@ -78,6 +109,7 @@ def get_twitter_embed_code(url): print ('Request to Twitter oEmbed endpoint for content "%s" failed ' 'with status code %d, message %s' % (url, response.status_code, response.content)) + return else: result_json = json.loads(response.content) @@ -98,23 +130,6 @@ def get_instagram_embed_code(url): return result_json[INSTAGRAM_HTML_KEY] -def html_element_from_embedded_content(url, content_type): - """Given embeddable content (a source url for twitter or instagram), call - the appropriate API to get embed code and insert into the appropriate HTML - wrapper.""" - if content_type == CONTENT_TYPE_TWITTER: - embed_code = get_twitter_embed_code(url) - elif content_type == CONTENT_TYPE_INSTAGRAM: - embed_code = get_instagram_embed_code(url) - else: - # We should have validated the content type before calling this func, - # so this case should never get tripped. - errString = ('Unexpected type %s (not an embedded content type). This ' - 'should never happen in this func.)' % content_type) - raise ValueError(errString) - return CONTENT_CONTAINER % (content_type, embed_code) - - def html_element_from_website_content(url, quote): """Given website content (source url, quote), insert into the appropriate HTML template.""" @@ -122,28 +137,32 @@ def html_element_from_website_content(url, quote): return CONTENT_CONTAINER % ('website', website_content) -def html_element_from_content(content_dict): +def html_element_from_content(content_dict, embed_code_getters): """Given a dict representing content (a type, a url, and optionally a quote), returns the content wrapped in the appropriate HTML element (ready for insertion on the homepage).""" - content_type = content_dict.get(CSV_KEY_TYPE) + content_type = content_dict.get(CONTENT_KEY_TYPE) if not content_type: print 'No content type provided for entry: "%s". Skipping.' % content_dict return - elif content_type not in CONTENT_TYPES: - print ('Unrecognized content type ("%s") in entry: "%s". Skipping.' - % (content_type, content_dict)) - return - elif content_type in EMBEDDED_CONTENT_TYPES: - url = content_dict.get(CSV_KEY_URL) + elif content_type == CONTENT_TYPE_WEBSITE: + url = content_dict.get(CONTENT_KEY_URL) + quote = content_dict.get(CONTENT_KEY_QUOTE) + elem = html_element_from_website_content(url, quote) + elif content_type in API_INFOS.keys(): + url = content_dict.get(CONTENT_KEY_URL) if not url: print 'No url provided for entry: "%s". Skipping.' % content_dict return - elem = html_element_from_embedded_content(url, content_type) - elif content_type == CONTENT_TYPE_WEBSITE: - url = content_dict.get(CSV_KEY_URL) - quote = content_dict.get(CSV_KEY_QUOTE) - elem = html_element_from_website_content(url, quote) + embed_code_getter_func = embed_code_getters.get(content_type) + if not embed_code_getter_func: + print 'No api info provided for content type "%s", skipping.' % content_type + return + elem = embed_code_getter_func(url) + else: + print ('Unrecognized content type ("%s") in entry: "%s". Skipping.' + % (content_type, content_dict)) + return # make sure to escape any lone '%' signs return elem.replace('%', '%%') @@ -153,9 +172,10 @@ def content_from_yaml(filepath): """Given the filepath of a yaml file containing content, returns a dict of content information.""" with open(filepath) as stream: - contents = yaml.load(stream) + contents = yaml.load(stream) return contents + def validate_filepath(filepath): """Make sure given filepath is a valid file.""" if not os.path.isfile(filepath): @@ -166,7 +186,12 @@ def validate_filepath(filepath): def main(): + # setup args = parse_command_line_args() + embed_code_getters = {} + for content_type, info in API_INFOS.iteritems(): + embed_code_getter = make_embed_code_getter(info['endpoint'], info['response_html_key'], info['encode_url']) + embed_code_getters[content_type] = embed_code_getter # set filepaths from command line args or default, validate filepaths. if args.content: @@ -194,21 +219,22 @@ def main(): # read in content yaml, generate html elements for each piece of content # (getting embed code from API where appropriate.) content = content_from_yaml(content_yaml_filepath) - print 'Loaded %d social media rows from yaml.' % len(content['social']) + num_rows = len(content[CONTENT_KEY_SOCIAL]) + print 'Loaded %d social media rows from yaml.' % num_rows html_elements_to_add = [] - for s in content['social']: - elem = html_element_from_content(s) - if elem: - html_elements_to_add.append(elem) + + for i, row in enumerate(content[CONTENT_KEY_SOCIAL]): + print '\tProcessing social media row %d/%d...' % (i+1, num_rows) + elem = html_element_from_content(row, embed_code_getters) + if elem: + html_elements_to_add.append(elem) # insert generated content into page template with open(page_template_filepath) as infile: page_template = infile.read() formatted = '\n\n'.join(html_elements_to_add) - content['formattedsocial'] = formatted - generated_page = Template(page_template).substitute(content) # write results to file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e35ad90 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +requests==2.7.0 +PyYAML==3.12