+ Want to help improve this website? Contact us on GitHub! +
+ + diff --git a/igdb_enums.py b/igdb_enums.py new file mode 100644 index 0000000000..a583ff8b41 --- /dev/null +++ b/igdb_enums.py @@ -0,0 +1,288 @@ +enums = dict( + age_ratings=dict( + category={ + '1': 'ESRB', + '2': 'PEGI', + '3': 'CERO', + '4': 'USK', + '5': 'GRAC', + '6': 'CLASS_IND', + '7': 'ACB', + }, + rating={ + '1': 'Three', + '2': 'Seven', + '3': 'Twelve', + '4': 'Sixteen', + '5': 'Eighteen', + '6': 'RP', + '7': 'EC', + '8': 'E', + '9': 'E10', + '10': 'T', + '11': 'M', + '12': 'AO', + '13': 'CERO_A', + '14': 'CERO_B', + '15': 'CERO_C', + '16': 'CERO_D', + '17': 'CERO_Z', + '18': 'USK_0', + '19': 'USK_6', + '20': 'USK_12', + '21': 'USK_18', + '22': 'GRAC_ALL', + '23': 'GRAC_Twelve', + '24': 'GRAC_Fifteen', + '25': 'GRAC_Eighteen', + '26': 'GRAC_TESTING', + '27': 'CLASS_IND_L', + '28': 'CLASS_IND_Ten', + '29': 'CLASS_IND_Twelve', + '30': 'CLASS_IND_Fourteen', + '31': 'CLASS_IND_Sixteen', + '32': 'CLASS_IND_Eighteen', + '33': 'ACB_G', + '34': 'ACB_PG', + '35': 'ACB_M', + '36': 'ACB_MA15', + '37': 'ACB_R18', + '38': 'ACB_RC', + }, + ), + age_rating_contect_descriptions=dict( + category={ + '1': 'PEGI', + '2': 'ESRB', + }, + ), + characters=dict( + gender={ + '0': 'Male', + '1': 'Female', + '2': 'Other', + }, + species={ + '1': 'Human', + '2': 'Alien', + '3': 'Animal', + '4': 'Android', + '5': 'Unknown', + }, + ), + companies=dict( + change_date_category={ + '0': 'YYYYMMMMDD', + '1': 'YYYYMMMM', + '2': 'YYYY', + '3': 'YYYYQ1', + '4': 'YYYYQ2', + '5': 'YYYYQ3', + '6': 'YYYYQ4', + '7': 'TBD', + }, + start_date_category={ + '0': 'YYYYMMMMDD', + '1': 'YYYYMMMM', + '2': 'YYYY', + '3': 'YYYYQ1', + '4': 'YYYYQ2', + '5': 'YYYYQ3', + '6': 'YYYYQ4', + '7': 'TBD', + }, + ), + company_websites=dict( + category={ + '1': 'official', + '2': 'wikia', + '3': 'wikipedia', + '4': 'facebook', + '5': 'twitter', + '6': 'twitch', + # '7': None, + '8': 'instagram', + '9': 'youtube', + '10': 'iphone', + '11': 'ipad', + '12': 'android', + '13': 'steam', + '14': 'reddit', + '15': 'itch', + '16': 'epicgames', + '17': 'gog', + '18': 'discord', + }, + ), + games=dict( + category={ + '0': 'main_game', + '1': 'dlc_addon', + '2': 'expansion', + '3': 'bundle', + '4': 'standalone_expansion', + '5': 'mod', + '6': 'episode', + '7': 'season', + '8': 'remake', + '9': 'remaster', + '10': 'expanded_game', + '11': 'port', + '12': 'fork', + }, + status={ + '0': 'released', + # '1': None, + '2': 'alpha', + '3': 'beta', + '4': 'early_access', + '5': 'offline', + '6': 'cancelled', + '7': 'rumored', + '8': 'delisted', + }, + ), + game_version_features=dict( + category={ + '0': 'boolean', + '1': 'description', + }, + ), + game_version_feature_values=dict( + included_feature={ + '0': 'NOT_INCLUDED', + '1': 'INCLUDED', + '2': 'PRE_ORDER_ONLY', + }, + ), + platforms=dict( + category={ + '1': 'console', + '2': 'arcade', + '3': 'platform', + '4': 'operating_system', + '5': 'portable_console', + '6': 'computer', + }, + ), + platform_version_release_dates=dict( + category={ + '0': 'YYYYMMMMDD', + '1': 'YYYYMMMM', + '2': 'YYYY', + '3': 'YYYYQ1', + '4': 'YYYYQ2', + '5': 'YYYYQ3', + '6': 'YYYYQ4', + '7': 'TBD', + }, + region={ + '1': 'europe', + '2': 'north_america', + '3': 'australia', + '4': 'new_zealand', + '5': 'japan', + '6': 'china', + '7': 'asia', + '8': 'worldwide', + '9': 'korea', + '10': 'brazil', + }, + ), + platform_websites=dict( + category={ + '1': 'official', + '2': 'wikia', + '3': 'wikipedia', + '4': 'facebook', + '5': 'twitter', + '6': 'twitch', + # '7': None, + '8': 'instagram', + '9': 'youtube', + '10': 'iphone', + '11': 'ipad', + '12': 'android', + '13': 'steam', + '14': 'reddit', + '15': 'discord', + '16': 'google_plus', + '17': 'tumblr', + '18': 'linkedin', + '19': 'pinterest', + '20': 'soundcloud', + }, + ), + release_dates=dict( + category={ + '0': 'YYYYMMMMDD', + '1': 'YYYYMMMM', + '2': 'YYYY', + '3': 'YYYYQ1', + '4': 'YYYYQ2', + '5': 'YYYYQ3', + '6': 'YYYYQ4', + '7': 'TBD', + }, + region={ + '1': 'europe', + '2': 'north_america', + '3': 'australia', + '4': 'new_zealand', + '5': 'japan', + '6': 'china', + '7': 'asia', + '8': 'worldwide', + '9': 'korea', + '10': 'brazil', + }, + ), + external_games=dict( + category={ + '1': 'steam', + '5': 'gog', + '10': 'youtube', + '11': 'microsoft', + '13': 'apple', + '14': 'twitch', + '15': 'android', + '20': 'amazon_asin', + '22': 'amazon_luna', + '23': 'amazon_adg', + '26': 'epic_game_store', + '28': 'oculus', + '29': 'utomik', + '30': 'itch_io', + '31': 'xbox_marketplace', + '32': 'kartridge', + '36': 'playstation_store_us', + '37': 'focus_entertainment', + }, + media={ + '1': 'digital', + '2': 'physical', + }, + ), + websites=dict( + category={ + '1': 'official', + '2': 'wikia', + '3': 'wikipedia', + '4': 'facebook', + '5': 'twitter', + '6': 'twitch', + # '7': None, + '8': 'instagram', + '9': 'youtube', + '10': 'iphone', + '11': 'ipad', + '12': 'android', + '13': 'steam', + '14': 'reddit', + '15': 'itch', + '16': 'epicgames', + '17': 'gog', + '18': 'discord', + }, + ), +) diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000000..ec61121410 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1 @@ +flake8==4.0.1 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000..7fd4c261d1 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +igdb-api-v4==0.0.5 +python-dotenv==0.20.0 +requests==2.28.1 diff --git a/update_db.py b/update_db.py new file mode 100644 index 0000000000..a32dd1aab7 --- /dev/null +++ b/update_db.py @@ -0,0 +1,243 @@ +# standard imports +import argparse +import pathlib +import json +import os + +# lib imports +import requests +from dotenv import load_dotenv +from igdb.wrapper import IGDBWrapper + +# local imports +from igdb_enums import enums + +# setup environment if running locally +load_dotenv() + + +def igdb_authorization(client_id: str, client_secret: str) -> dict: + auth_headers = dict( + Accept='application/json', + client_id=client_id, + client_secret=client_secret, + grant_type='client_credentials' + ) + + token_url = 'https://id.twitch.tv/oauth2/token' + + authorization = post_json(url=token_url, headers=auth_headers) + return authorization + + +def post_json(url: str, headers: dict) -> dict: + result = requests.post(url=url, data=headers).json() + return result + + +def write_json_files(file_path: str, data: dict): + # determine the directory + directory = os.path.dirname(file_path) + + pathlib.Path(directory).mkdir(parents=True, exist_ok=True) + + with open(f'{file_path}.json', 'w') as f: + json.dump(obj=data, fp=f, indent=args.indent) + + +def get_igdb_data(): + request_dict = dict( + characters=dict( + fields=[ + 'games', + 'gender', + 'mug_shot.url', + 'name', + 'species', + ], + write_all=True, + ), + games=dict( + fields=[ + 'age_ratings.category', + 'age_ratings.rating', + 'aggregated_rating', + 'artworks.url', + 'collection.name', + 'cover.url', + 'franchise.name', + 'franchises.name', + 'game_modes.name', + 'genres.name', + 'involved_companies.company.name', + 'involved_companies.developer', + 'multiplayer_modes.*', + 'name', + 'platforms', + 'player_perspectives.name', + 'rating', + 'release_dates.date', + 'release_dates.y', + 'release_dates.platform', + 'screenshots.url', + 'storyline', + 'summary', + 'themes.name', + 'videos.name', + 'videos.video_id', + ], + append=dict( + characters=dict( + fields=[ + 'id', + 'gender', + 'mug_shot', + 'name', + 'species', + ] + ) + ), + write_all=False, + ), + platforms=dict( + fields=[ + 'abbreviation', + 'alternative_name', + 'name', + ], + append=dict( + games=dict( + fields=[ + 'id', + 'name', + 'release_dates', + ] + ) + ), + write_all=True, + ), + ) + limit = 500 + full_dict = dict() + + for end_point, end_point_dict in request_dict.items(): + print(f'now processing endpoint: {end_point}') + offset = 0 + result = True + full_dict[end_point] = dict() + + while result: + byte_array = wrapper.api_request( + endpoint=end_point, + query=f'fields {", ".join(end_point_dict["fields"])}; limit {limit}; offset {offset};' + ) + json_result = json.loads(byte_array) # this is a list of dictionaries + # items.extend(json_result) + + for item in json_result: + full_dict[end_point][item['id']] = item + + # file_path = os.path.join(end_point, str(item['id'])) + # write_json_files(file_path=file_path, data=item) + + if args.test_mode: + break + + # print(json.dumps(json_result, indent=4)) + + offset += limit + + if not json_result: + result = False + + if end_point_dict['write_all']: + # write the end_point file + file_path = os.path.join(end_point, 'all') + write_json_files(file_path=file_path, data=full_dict[end_point]) + + print(f'{len(full_dict[end_point])} items processed in endpoint: {end_point}') + + for end_point, end_point_dict in request_dict.items(): + try: + append_dict = request_dict[end_point]['append'] + except KeyError: + pass + else: + for item_type, item_type_dict in append_dict.items(): + print(f'adding {item_type} to {end_point}') + for item_id_src, value in full_dict[item_type].items(): + try: + append_to = value[end_point] + except KeyError: + pass + else: + for item_id_dest in append_to: + try: + full_dict[end_point][item_id_dest] + except KeyError: + # the destination item doesn't exist + pass + else: + try: + full_dict[end_point][item_id_dest][item_type] + except KeyError: + full_dict[end_point][item_id_dest][item_type] = [] + finally: + full_dict[end_point][item_id_dest][item_type].append(dict()) + + for field in item_type_dict['fields']: + try: + field_value = value[field] + except KeyError: + # this item doesn't have the specified field, no problem + pass + else: + full_dict[end_point][item_id_dest][item_type][-1][field] = field_value + + # write the individual files + for end_point, end_point_dict in full_dict.items(): + print(f'writing individual files for {end_point}') + for item_id, data in end_point_dict.items(): + file_path = os.path.join(end_point, str(item_id)) + write_json_files(file_path=file_path, data=data) + + +def get_igdb_enums(): + end_point = 'enums' + for enum, values in enums.items(): + file_path = os.path.join(end_point, enum) + write_json_files(file_path=file_path, data=values) + + if args.test_mode: + break + + # write the end_point file + file_path = os.path.join(end_point, 'all') + write_json_files(file_path=file_path, data=enums) + + +if __name__ == '__main__': + # setup arguments using argparse + parser = argparse.ArgumentParser(description="Download entire igdb database.") + parser.add_argument('--client_id', type=str, required=False, default=os.getenv('TWITCH_CLIENT_ID'), + help='Twitch developer client id') + parser.add_argument('--client_secret', type=str, required=False, default=os.getenv('TWITCH_CLIENT_SECRET'), + help='Twitch developer client secret') + parser.add_argument('-t', '--test_mode', action='store_true', + help='Only write one item file per end point, per request.') + parser.add_argument('-i', '--indent_json', action='store_true', help='Indent json files.') + + args = parser.parse_args() + args.indent = 4 if args.indent_json else None + + if not args.client_id or not args.client_secret: + raise SystemExit('No secrets supplied. Required secrets are "TWITCH_CLIENT_ID" and "TWITCH_CLIENT_SECRET". ' + 'They should be placed in org/repo secrets if using github, or ".env" file if running local.') + + # setup igdb authorization and wrapper + auth = igdb_authorization(client_id=args.client_id, client_secret=args.client_secret) + wrapper = IGDBWrapper(client_id=args.client_id, auth_token=auth['access_token']) + + # get date, process dictionaries and write data + get_igdb_data() + get_igdb_enums()