import cgi from contextlib import closing from datetime import date from enum import Enum import json from pprint import pprint import re import ssl from urllib.error import URLError, HTTPError import urllib.parse import urllib.request from flask import Blueprint, current_app, flash, request, render_template from .. import util #################### # constants bp = Blueprint('tmdb', __name__, url_prefix='/tmdb') TMDB_URL = 'https://api.themoviedb.org' TMDB_SEARCH_MOVIE = '/3/search/movie' TMDB_SEARCH_TV = '/3/search/tv' #################### # data structures class query_type(Enum): UNKNOWN = 0 MOVIE = 1 TV_SHOW = 2 class query_data(object): def __init__(self): self.errors = [] self.full_url = '' self.movie_year = 0 self.parsed_query_str = '' self.query_params = {} self.query_type = query_type.UNKNOWN self.raw_query_str = '' self.response_body = '' self.response_headers = [] self.response_object = {} self.response_status = 0 self.tmdb_api_key = '' #################### # flask routes @bp.route('/query_movie') def query_movie(): view_data = query_data() view_data.tmdb_api_key = current_app.config['TMDB_API_KEY'] # NOTE: query string can be submitted from a link or form input if 'query' in request.form: view_data.raw_query_str = request.form['query'] elif 'query' in request.args: view_data.raw_query_str = request.args['query'] if parseQueryStr(view_data) and makeQuery(view_data): try: p = view_data.response_object = json.loads(view_data.response_body) pprint(p) except json.JSONDecodeError as e: view_data.errors.append(e) else: # display errors pass if view_data.errors: flash(view_data.errors) return render_template( 'tmdb/query.html', view_data=view_data, debug_str=util.dumpObjAttr(view_data) ) ################### # helpers # TODO: need a way to correctly parse titles like: "2001: A Space Odyssey (1968)" # with legitimate dates in the title def parseQueryStr(query_obj): if not query_obj.raw_query_str: query_obj.errors.append('%s: raw query string is empty' % util.getFunctionName()) return False s = query_obj.raw_query_str.replace('.', ' ').strip() year = None current_year = date.today().year # NOTE: find all 4 digit numbers to match the year of queries with 4 digit numbers in the title # ex) "2036.Origin.Unknown.2018" regex = re.compile('[0-9]{4}') for m in regex.finditer(s): i = int(m[0]) if i > 1878 and i <= current_year: query_obj.parsed_query_str = s[0:m.start()].strip() # NOTE: slice query string to year match query_obj.movie_year = i query_obj.query_type = query_type.MOVIE break # TODO: parse tv show titles if query_obj.query_type == query_type.MOVIE: return True else: query_obj.errors.append('%s: unable to determine query type' % util.getFunctionName()) return False def makeQuery(query_obj): if query_obj.query_type == query_type.MOVIE: search_part = TMDB_SEARCH_MOVIE elif query_obj.query_type == query_type.TV_SHOW: search_part = TMDB_SEARCH_TV else: query_obj.errors.append('%s(), query type is not set' % util.getFunctionName()) return False request_headers = {"Accept": "text/html,application/xhtml+xml,application/xml"} query_obj.query_params = urllib.parse.urlencode( { 'api_key': query_obj.tmdb_api_key, 'query': query_obj.parsed_query_str, 'year': query_obj.movie_year, } ) query_obj.full_url = TMDB_URL + search_part + '?' + query_obj.query_params context = ssl.create_default_context() req = urllib.request.Request(query_obj.full_url, None, request_headers) ct_header = '' try: response = urllib.request.urlopen(req, context=context) except HTTPError as e: query_obj.errors.append(e) return False except URLError as e: query_obj.errors.append(e) return False for k, v in response.info().items(): query_obj.response_headers.append((k, v)) if k == 'Content-Type': ct_header = str(k + ':' + v) query_obj.response_status = response.status if 'charset' in ct_header: charset = cgi.parse_header(ct_header)[1]['charset'] query_obj.response_body = response.read().decode(charset) else: query_obj.errors.append('%s(), no charset key in ct_header' % util.getFunctionName()) query_obj.response_body = response.read() return False return True