Skip to content

Instantly share code, notes, and snippets.

@Telmo
Forked from cortical-iv/destiny2_api_intro.py
Created September 3, 2020 08:42
Show Gist options
  • Save Telmo/eb461e62e90a2d786f3c34cd37ac6d68 to your computer and use it in GitHub Desktop.
Save Telmo/eb461e62e90a2d786f3c34cd37ac6d68 to your computer and use it in GitHub Desktop.

Revisions

  1. @cortical-iv cortical-iv revised this gist Nov 4, 2017. 1 changed file with 3 additions and 0 deletions.
    3 changes: 3 additions & 0 deletions destiny2_api_intro.py
    Original file line number Diff line number Diff line change
    @@ -43,6 +43,9 @@
    baseurl_groupv2 = 'https://bungie.net/Platform/GroupV2/'

    membership_types = {'xbox': '1', 'xbone': '1', 'psn': '2', 'pc': '4', 'ps4': '2'}

    #Following conversions have names I use for user summary stats as keys,
    #and names that bungie uses as values for when I extract from end points.
    pveKeyConversion = {'numEventsPve': 'activitiesEntered',
    'kdPve': 'killsDeathsRatio',
    'durationPlayedPve': 'totalActivityDurationSeconds',
  2. @cortical-iv cortical-iv revised this gist Nov 4, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion destiny2_api_intro.py
    Original file line number Diff line number Diff line change
    @@ -28,7 +28,7 @@
    Thanks to the folks at bungie api discord, the place to go for discussion
    and help: https://discord.gg/WJDSUgj
    """
    #%% inputs and parameters
    #%% imports
    import requests
    import json

  3. @cortical-iv cortical-iv created this gist Nov 4, 2017.
    638 changes: 638 additions & 0 deletions destiny2_api_intro.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,638 @@
    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    """
    Getting Started using the Destiny 2 Api
    An annotated guide to some of the public endpoints available for examining a user's
    characters, items, and clan using the Destiny 2 API. You will need to use your api key for
    this to work. Just insert it as a string where it says <my_api_key> in the beginning.
    It is broken into four parts:
    0: Imports, variables, and fixed parameters defined
    1: Main hooks (destiny2_api_public to make requests, and the url generators)
    2: Helper functions that use those hooks to do useful things
    3: Simple examples of the hooks and helper functions in action. Enter a user_name
    and user_platform to look at their profile, characters, items, clan.
    If this were a serious project, these would be different modules.
    Code segments are also broken into commented cells (separated by #%%) so you can step through
    this cell-by-cell (e.g., in Spyder), sort of like you would in a Jupyter notebook.
    Caveats:
    1. This code is not optimized, and is verbose. This is intentional. E.g., the
    number of calls to 'get_user_id', each of which makes a request to bungie.net, is obscene.
    2. Please let me know if you have a suggestion for improvements.
    3. Will likely not work for pc players (they have bnet#s appended to their username
    and this code current doesn't handle that).
    Acknowledgements:
    Thanks to the folks at bungie api discord, the place to go for discussion
    and help: https://discord.gg/WJDSUgj
    """
    #%% inputs and parameters
    import requests
    import json

    #%%variables that you might want to change on different runs
    user_name = 'cortical_iv' #put name of person whose info/clan you want to explore
    user_platform = 'psn' #either 'psn' or 'ps4' or 'xbone' or 'xbox' (pc is busted)
    save_to_file = 0 #flag: set to 1 if you want certain bits saved to file to peruse

    #%% fixed parameters
    my_api_key = <my_api_key> #put your api key here!
    baseurl = 'https://bungie.net/Platform/Destiny2/'
    baseurl_groupv2 = 'https://bungie.net/Platform/GroupV2/'

    membership_types = {'xbox': '1', 'xbone': '1', 'psn': '2', 'pc': '4', 'ps4': '2'}
    pveKeyConversion = {'numEventsPve': 'activitiesEntered',
    'kdPve': 'killsDeathsRatio',
    'durationPlayedPve': 'totalActivityDurationSeconds',
    'favoriteWeaponPve': 'weaponBestType',
    'longestKillDistancePve': 'longestKillDistance',
    'orbsGeneratedPve': 'orbsDropped',
    'suicideRatePve': 'suicides',
    'longestKillSpreePve': 'longestKillSpree'}
    raidKeyConversion = {'raidAttempts': 'activitiesEntered',
    'raidClears': 'activitiesCleared'}
    pvpKeyConversion = {'numEventsPvp': 'activitiesEntered',
    'numWinsPvp': 'activitiesWon',
    'winLossRatioPvp': 'winLossRatio',
    'kdPvp': 'killsDeathsRatio',
    'durationPlayedPvp': 'totalActivityDurationSeconds',
    'favoriteWeaponPvp': 'weaponBestType',
    'mostKillsPvp': 'bestSingleGameKills',
    'longestKillSpreePvp': 'longestKillSpree',
    'suicideRatePvp': 'suicides'}

    #%% api hooks
    def destiny2_api_public(url, api_key):
    """This is the main function for everything. It requests the info from the bungie servers
    by sending a url."""
    my_headers = my_headers = {"X-API-Key": my_api_key}
    response = requests.get(url, headers = my_headers)
    return ResponseSummary(response)


    class ResponseSummary:
    '''
    Object contains all the important information about the request sent to bungie.
    '''
    def __init__(self, response):
    self.status = response.status_code
    self.url = response.url
    self.data = None
    self.message = None
    self.error_code = None
    self.error_status = None
    self.exception = None
    if self.status == 200:
    result = response.json()
    self.message = result['Message']
    self.error_code = result['ErrorCode']
    self.error_status = result['ErrorStatus']
    if self.error_code == 1:
    try:
    self.data = result['Response']
    except Exception as ex:
    print("ResponseSummary: 200 status and error_code 1, but there was no result['Response']")
    print("Exception: {0}.\nType: {1}".format(ex, ex.__class__.__name__))
    self.exception = ex.__class__.__name__
    else:
    print('No data returned for url: {0}.\n {1} was the error code with status 200.'.format(self.url, self.error_code))
    else:
    print('Request failed for url: {0}.\n.Status: {0}'.format(self.url, self.status))

    def __repr__(self):
    """What will be displayed/printed for the class instance."""
    disp_header = "<" + self.__class__.__name__ + " instance>\n"
    disp_data = ".data: " + str(self.data) + "\n\n"
    disp_url = ".url: " + str(self.url) + "\n"
    disp_message = ".message: " + str(self.message) + "\n"
    disp_status = ".status: " + str(self.status) + "\n"
    disp_error_code = ".error_code: " + str(self.error_code) + "\n"
    disp_error_status = ".error_status: " + str(self.error_status) + "\n"
    disp_exception = ".exception: " + str(self.exception)
    return disp_header + disp_data + disp_url + disp_message + \
    disp_status + disp_error_code + disp_error_status + disp_exception


    """
    URL GENERATORS
    The following functions create urls in the format that the bungie servers want them.
    In the docs for each function I give the url to bungie docs, partly to help but also so
    you can see what I may have left out --- I'm not always including all possible query strings.
    I named each url generator according to the bungie end point (e.g., if the end point is X
    then the function is X_url)
    """
    def search_destiny_player_url(user_name, user_platform):
    """Main point is to get the user's id from their username.
    https://bungie-net.github.io/multi/operation_get_Destiny2-SearchDestinyPlayer.html
    """
    membership_type = membership_types[user_platform]
    return baseurl + 'SearchDestinyPlayer/' + membership_type + '/' + user_name + '/'


    def get_profile_url(user_name, user_platform, components, my_api_key):
    """Get information about different aspects of user's character like equipped items.
    https://bungie-net.github.io/multi/operation_get_Destiny2-GetProfile.html
    Note components are just strings: '200,300' : you need at least one component."""
    user_id = get_user_id(user_name, user_platform, my_api_key)
    membership_type = membership_types[user_platform]
    return baseurl + membership_type + '/' + 'Profile/' + user_id + '/?components=' + components


    def get_character_url(user_name, user_platform, character_id, components, my_api_key):
    """Similar to get_profile but does it for a single character. Note individual character
    id's are returned by get_profile.
    https://bungie-net.github.io/multi/operation_get_Destiny2-GetCharacter.html """
    user_id = get_user_id(user_name, user_platform, my_api_key)
    membership_type = membership_types[user_platform]
    return baseurl + membership_type + '/' + 'Profile/' + user_id + \
    '/Character/' + character_id + '/?components=' + components


    def get_item_url(user_name, user_platform, item_instance_id, components, my_api_key):
    """Pull item with item instance id (for instance if you have two instances of
    Uriel's Gift, this will let you pull information about one particular instance.
    You will get the itemInstanceId from item stats returned from get_profile or
    get_character.
    https://bungie-net.github.io/multi/operation_get_Destiny2-GetItem.html"""
    user_id = get_user_id(user_name, user_platform, my_api_key)
    membership_type = membership_types[user_platform]
    return baseurl + membership_type + '/Profile/' + user_id + \
    '/item/' + item_instance_id + '/?components=' + components

    def get_entity_definition_url(entity_hash, entity_type, my_api_key):
    """
    Hooking up with the manifest!
    https://bungie-net.github.io/multi/operation_get_Destiny2-GetDestinyEntityDefinition.html
    If you've got a hash, and know the entity type, you can use this (or just download
    the manifest and make your own database to do it 10x faster)
    """
    return baseurl + 'Manifest/' + entity_type + '/' + entity_hash

    def get_groups_for_member_url(user_name, user_platform, my_api_key):
    """This is how I find the groupId for the clan a user is in. You need the group
    id to do more interesting things like pull all members of a clan (see get_group_members)
    https://bungie-net.github.io/multi/operation_get_GroupV2-GetGroupsForMember.html"""
    user_id = get_user_id(user_name, user_platform, my_api_key)
    membership_type = membership_types[user_platform]
    return baseurl_groupv2 + 'User/' + membership_type + '/' + user_id + '/0/1/' #0/1 are filter/groupType


    def get_members_of_group_url(group_id):
    """Pull all members of a clan. Note clans can only have 100 members max.
    https://bungie-net.github.io/multi/operation_get_GroupV2-GetMembersOfGroup.html"""
    return baseurl_groupv2 + group_id + '/Members/?currentPage=1'


    def get_activity_history_url(user_name, user_platform, character_id, \
    activity_mode = 'None', page = '0', count = '100'):
    """Returns useful history of activities, filtered by tyupe if you want (e.g.,
    pvp, pve, etc: see modes below)
    https://bungie-net.github.io/multi/operation_get_Destiny2-GetActivityHistory.html
    Queries:
    count: total number of results to return:
    mode: filter for activity mode to return (None returns all activities--see below)
    page: page number of results to return, starting with 0.
    Sample of modes (this is not all of them)
    all: None; story: 2; strike: 3; raid: 4; PvP: 5; Patrol 6; PvE 7; Clash 12
    Nightfall 16; Trials: 39; Social: 40 (returns nada)
    """
    user_id = get_user_id(user_name, user_platform, my_api_key)
    membership_type = membership_types[user_platform]
    query_string = '?mode=' + activity_mode + '&page=' + page + '&count=' + count
    return baseurl + membership_type + '/Account/' + user_id + '/Character/' + \
    character_id + '/Stats/Activities/' + query_string

    def get_historical_stats_url(user_name, user_platform, character_id, my_api_key, activity_modes = 'None'):
    """Return tons of useful stats about a character (or set character_id = '0' for
    all character data lumped together).
    https://bungie-net.github.io/multi/operation_get_Destiny2-GetHistoricalStats.html
    Note modes are from the same list as above with GetActivityHistory."""
    user_id = get_user_id(user_name, user_platform, my_api_key)
    membership_type = membership_types[user_platform]
    query_string = '?modes=' + activity_modes
    return baseurl + membership_type + '/Account/' + user_id + '/Character/' + \
    character_id + '/Stats/' + query_string

    def get_historical_stats_for_account_url(user_name, user_platform, my_api_key):
    """Get lots of stats almost as useful as get historical stats for character, but not quite as
    no raid data.
    https://bungie-net.github.io/multi/operation_get_Destiny2-GetHistoricalStatsForAccount.html"""
    user_id = get_user_id(user_name, user_platform, my_api_key)
    membership_type = membership_types[user_platform]
    return baseurl + membership_type + '/Account/' + user_id + '/Stats/'


    #%% helper functions
    """
    HELPER FUNCTIONS (ImHelping)
    These functions all use the above url-generators and api endpoints, or some processed
    data from the endpoints, in the use-case bits below.
    """
    def get_user_id(user_name, user_platform, my_api_key):
    """Uses search_destiny_player end point to get user id. Returns None if there is a problem."""
    player_summary = destiny2_api_public(search_destiny_player_url(user_name, user_platform), my_api_key)
    if player_summary.error_code == 1:
    if player_summary.data:
    return player_summary.data[0]['membershipId']
    else:
    print('There is no data for {0} on {1}'.format(user_name, user_platform))
    return None
    else:
    print('There was an error getting id for {0}. Status: {1}'.format(user_name, player_summary.status))
    return None


    def extract_item_stats(item_hash, my_api_key):
    """
    For item_hash, return dict containing its stats by name and value (and the name of the
    item with its type). Note some items have no stats, but for weapons and armor you
    will get the standards you see in game.
    """
    item_url = get_entity_definition_url(item_hash, 'DestinyInventoryItemDefinition', my_api_key)
    item_summary = destiny2_api_public(item_url, my_api_key)
    stat_names_values = {}
    item_name = item_summary.data['displayProperties']['name']
    item_type = item_summary.data['itemTypeAndTierDisplayName']
    item_stats = item_summary.data['stats']['stats']
    stat_names_values['name'] = item_name
    stat_names_values['type'] = item_type
    print('Extracting stats for {0}'.format(item_name))
    for statHash in item_stats:
    tmp_url = get_entity_definition_url(statHash, 'DestinyStatDefinition', my_api_key)
    tmp_summary = destiny2_api_public(tmp_url, my_api_key)
    try:
    stat_name = tmp_summary.data['displayProperties']['name']
    stat_value = item_stats[statHash]['value']
    stat_names_values[stat_name] = stat_value
    print('Extracted {0} stats...'.format(stat_name))
    except KeyError: #this is common some hashes are undefined
    print('Warning: KeyError for statHash {0}'.format(statHash))
    continue
    print("Finished collecting stats for {0}\n".format(item_name))
    return stat_names_values

    def summarize_pve(user_name, user_stats):
    """pull stats of interest from accounts pve history. Uses the user_stats
    dictionary created by GetHistoricalStats"""
    user_pve_summary = {}
    allPvE = user_stats['allPvE']
    if allPvE:
    pve_stats = allPvE['allTime']
    for newKey, oldKey in pveKeyConversion.items():
    if newKey == 'suicideRatePve':
    user_pve_summary[newKey] = pve_stats[oldKey]['pga']['displayValue']
    else:
    user_pve_summary[newKey] = pve_stats[oldKey]['basic']['displayValue']
    else:
    user_pve_summary['numEventsPve'] = None
    #Raid stats are stored separately
    raid_dat = user_stats['raid']
    if raid_dat:
    raid_stats = raid_dat['allTime']
    for newKey, oldKey in raidKeyConversion.items():
    user_pve_summary[newKey] = raid_stats[oldKey]['basic']['displayValue']
    else:
    user_pve_summary['raidAttempts'] = None
    user_pve_summary['userName'] = user_name
    return user_pve_summary

    def summarize_pvp(user_name, user_stats):
    """pull stats of interest from accounts' pvp history. Uses user_stats structure returned
    by GetHistoricalStats."""
    user_pvp_summary = {}
    allPvP = user_stats['allPvP']
    if allPvP: #if they have done any pvp
    pvp_stats = allPvP['allTime']
    for newKey, oldKey in pvpKeyConversion.items():
    if newKey == 'suicideRatePvp':
    user_pvp_summary[newKey] = pvp_stats[oldKey]['pga']['displayValue']
    else:
    user_pvp_summary[newKey] = pvp_stats[oldKey]['basic']['displayValue']
    else: #they have not done any pvp
    user_pvp_summary['numEventsPvp'] = None
    user_pvp_summary['userName'] = user_name
    return user_pvp_summary

    def summarize_player_performance(user_name, user_stats):
    """Pools pvp and pve performance stats into one dictionary. Calls
    summarize_pve and summarize_pvp"""
    pve_stats = summarize_pve(user_name, user_stats)
    pvp_stats = summarize_pvp(user_name, user_stats)
    #merging dicts: http://treyhunner.com/2016/02/how-to-merge-dictionaries-in-python/
    return {**pve_stats, **pvp_stats}

    def generate_clan_list(member_data, clan_platform):
    """Using output of GetMembersOfGroup, create list of member info for clan members:
    each is a dict with username. id, join date. Filters out people not on original
    user's membership type."""
    #Filter out people not on psn
    membership_type = membership_types[clan_platform]
    clan_members_data = [] #dictionary with name: user_name, id: id, and membership_type
    for member in member_data:
    #print(member['destinyUserInfo']['displayName']) #don't use bungienetuserinfo some don't have
    clan_member = {}
    clan_member['membership_type'] = str(member['destinyUserInfo']['membershipType'])
    if clan_member['membership_type'] == membership_type:
    #print('go')
    clan_member['name'] = member['destinyUserInfo']['displayName']
    clan_member['id'] = member['destinyUserInfo']['membershipId']
    clan_member['date_joined'] = member['joinDate']
    clan_members_data.append(clan_member)
    return clan_members_data

    def print_clan_roster(clan_members_data):
    """Print name, membership type, id, and date joined. Just a way to organize columns."""
    name_list = [clanfolk['name'] for clanfolk in clan_members_data]
    col_width = max(len(word) for word in name_list)
    for clan_member in clan_members_data:
    memb_name = clan_member['name']
    length_name = len(memb_name)
    num_spaces = col_width - length_name
    memb_name_extended = memb_name + " "*num_spaces
    print("{0}\tMembership type: {1}\t Id: {2}\tJoined: {3}".format(memb_name_extended, \
    clan_member['membership_type'], clan_member['id'], clan_member['date_joined']))

    def summarize_clan_performance(clan_members_data, clan_platform, my_api_key):
    """Run summarize_player_performance for each player in clan_members_data dictionary.
    """
    num_members = len(clan_members_data)
    clan_performance = {}
    debug_bits = []
    player_count = 1
    print('\n *\n About to get stats for {0} members of clan\n *'.format(num_members))
    for clan_member in clan_members_data:
    member_name = clan_member['name']
    print('Processing player {0} (name/id): ({1}/{2})'.format(player_count, \
    member_name, clan_member['id']))
    try:
    member_stats_url = get_historical_stats_url(member_name, clan_platform, \
    '0', my_api_key, activity_modes = '4,5,7')
    member_response = destiny2_api_public(member_stats_url, my_api_key)
    member_performance = summarize_player_performance(member_name, member_response.data)
    member_performance['dateJoined'] = clan_member['date_joined']
    clan_performance[member_name] = member_performance
    except Exception as ex:
    print('failed with {0}. Exception: {1}'.format(member_name, ex.__class__.__name__))
    debug_bits.append({'member':member_name, 'exception': ex.__class__.__name__})
    player_count += 1
    #if player_count == 20: #for debugging
    # break
    return clan_performance, debug_bits

    def print_clan_performance(clan_performance):
    """Print tiny bit of selected info about each member in clan. This is a test, it really
    is just a sliver of the information available in clan_performance."""
    for member in clan_performance:
    print(member)
    member_data = clan_performance[member]
    user_summary = "{0} joined on {1}.\n".format(member_data['userName'], member_data['dateJoined'])

    if member_data['numEventsPve']:
    pve_summary = "PvE: Played {0} matches in {1} with {2} orbs generated total.\n".\
    format(member_data['numEventsPve'], member_data['durationPlayedPve'],\
    member_data['orbsGeneratedPve'])
    else:
    pve_summary = "PvE: They have played no PvE yet.\n"

    if member_data['raidAttempts']:
    raid_summary = "They have attempted the raid {0} times with {1} clears.\n". \
    format(member_data['raidAttempts'], member_data['raidClears'])
    else:
    raid_summary = "They have not yet attempted the raid.\n"

    if member_data['numEventsPvp']:
    pvp_summary = "PvP: {0} matches in {1} with a {2} kd and {3} w/l.\n".\
    format(member_data['numEventsPvp'], member_data['durationPlayedPvp'],\
    member_data['kdPvp'], member_data['winLossRatioPvp'])
    else:
    pvp_summary = "PvP: They have played no PvP yet.\n"

    print(user_summary + pve_summary + raid_summary + pvp_summary)

    def print_misadventure_rates(clan_performance):
    """Prints misadventure rates for each member...Not pretty yet or anything just an
    example of something you can do."""
    for member in clan_performance:
    member_data = clan_performance[member]
    if member_data['numEventsPve']:
    pve_printout = "{0} misadventure rate pve {1}. ".format(member_data['userName'], member_data['suicideRatePve'])
    else:
    pve_printout = "{0}: No pve.".format(member_data['userName'])
    if member_data['numEventsPvp']:
    pvp_printout = "misadventure rate pvp: {0}".format(member_data['suicideRatePvp'])
    else:
    pvp_printout = " No pvp."
    print(pve_printout + pvp_printout)

    def save_readable_json(data, filename):
    """This is if you want to save response data to filename in human-readable json format"""
    with open(filename, 'w') as fileObject:
    try:
    fileObject.write(json.dumps(data, indent = 3))
    print('Saved data to ' + filename)
    except:
    print('ya blew it saving ' + filename)

    #%% ########################################################
    # END FUNCTION AND CLASS DEFINITIONS #
    #########################################################


    """
    EXAMPLES
    """
    if __name__ == '__main__':
    #%%SearchDestinyPlayer to get user id
    player_url = search_destiny_player_url(user_name, user_platform)
    player_summary = destiny2_api_public(player_url, my_api_key)
    user_id = player_summary.data[0]['membershipId']

    #%% Or just get id using a helper function
    user_id_alt = get_user_id(user_name, user_platform, my_api_key)

    #%%GetProfile
    #Component types include: 100 profiles; 200 characters; 201 non-equipped items (need oauth);
    #205: CharacterEquipment: what they currently have equipped. All can see this
    components = '200,205'
    profile_url = get_profile_url(user_name, user_platform, components, my_api_key)
    user_profile_summary = destiny2_api_public(profile_url, my_api_key)

    #%%extract character id's from profile
    user_characters = user_profile_summary.data['characters']['data']
    character_ids = list(user_characters.keys())
    user_character_0 = user_characters[character_ids[0]]

    #%%GetCharacter
    #This basically is GetProfile but for just one character that you show an id for
    #Note if you search for inventory components (201) if you get nothing it might say privacy: 2
    #public:1, private: 2
    character_components = '201,205'
    char_url = get_character_url(user_name, user_platform, character_ids[0], character_components, my_api_key)
    character_summary = destiny2_api_public(char_url, my_api_key)

    #Note this contains equipment and itemComponents (later isempty)
    character_items = character_summary.data['equipment']['data']['items'] #
    first_item = character_items[0]
    item_instance_id = first_item['itemInstanceId']
    item_hash = str(first_item['itemHash']) #wtf is this? you might ask

    second_item = character_items[1]
    second_item_hash = str(second_item['itemHash'])

    fifth_item = character_items[4]
    fifth_item_hash = str(fifth_item['itemHash'])

    """
    Characteritems includes all equipped stuff. But you can't just read them off the data.
    They are encoded in hashes. To decode them you need to use the manifest. Which brings
    us to...GetEntityDefinition: https://destiny.plumbing/
    """

    #%% Access manifest via GetDestinyEntityDefinition,
    item_url = get_entity_definition_url(item_hash, 'DestinyInventoryItemDefinition', my_api_key)
    item_summary = destiny2_api_public(item_url, my_api_key)

    #now you have all sorts of information about that inventory item:
    #This will pretty much have everything you need
    item_data = item_summary.data

    print(item_data.keys())
    #Let's get name and type
    item_data['displayProperties']
    item_name = item_data['displayProperties']['name']
    item_type = item_data['itemTypeAndTierDisplayName']
    print("\nWhat you've got here is a {0}. It's name is {1}.".format(item_type, item_name))

    #%% If you want to pull some stats, you will also need to access statHash
    item_stats = item_data['stats']['stats']
    #EXplore first stat
    entity_type = 'DestinyStatDefinition'
    first_stat = list(item_stats.keys())[0]
    stat_value = item_stats[first_stat]['value']
    stat_url = get_entity_definition_url(first_stat, entity_type, my_api_key)
    stat_summary = destiny2_api_public(stat_url, my_api_key)
    stat_summary_data = stat_summary.data['displayProperties']
    stat_name = stat_summary_data['name']
    stat_description = stat_summary_data['description']

    print("The first stat is {0} and its value is {1}".format(stat_name, stat_value))

    #%% Because that's so tedious, I made a helper function:
    #EXTRACT_ITEM_STATS that prints out all stats for an item
    first_item_stats = extract_item_stats(item_hash, my_api_key)
    fifth_item_stats = extract_item_stats(fifth_item_hash, my_api_key)

    #print results (would be nice to have a better function for this)
    print("\n"+ str(first_item_stats) + "\n")
    print("\n"+ str(fifth_item_stats) + "\n")

    """
    You will notice extracting stats by url queries is slow it takes many seconds to run.
    So you should download the manifest so you don't have to futz around with
    request.get and such any more!
    For more on how to do this see:
    https://github.com/vpzed/Destiny2-API-Info/wiki/Manifest-Introduction
    http://allynh.com/blog/creating-a-python-app-for-destiny-part-5-reading-a-characters-inventory-and-vault-contents/
    """

    #%%GetItem testing
    item_components = '302,304,307'
    item_url = get_item_url(user_name, user_platform, item_instance_id, item_components, my_api_key)
    item_instance = destiny2_api_public(item_url, my_api_key)

    #%% GET_ACTIVITY_HISTORY
    ###testing full default
    all_activity_url = get_activity_history_url(user_name, user_platform, character_ids[0])
    all_activity_summary = destiny2_api_public(all_activity_url, my_api_key)
    all_activities = all_activity_summary.data['activities'] #this is a list

    #look at a couple if you want
    first_all = all_activities[0]
    last_all = all_activities[-1]

    #%% GET_HISTORICAL_STATS_FOR_ACCOUNT (nb: this is not as good as character-based)
    #use the char-based one which is next it has raid information
    user_history_url = get_historical_stats_for_account_url(user_name, user_platform, my_api_key)
    user_history = destiny2_api_public(user_history_url, my_api_key)
    if save_to_file:
    save_readable_json(user_history.data, 'user_stats.txt')

    #%% GET_HISTORICAL_STATS_CHAR (for character: this seems more informative
    #than the previous, as you can get raid info. Set char_id = '0' to pull account
    #totals for all characters
    main_stats_url = get_historical_stats_url(user_name, user_platform, '0', \
    my_api_key, activity_modes = '4,5,7')
    main_stats = destiny2_api_public(main_stats_url, my_api_key)

    #Example of how you might pull some data
    pve_dat = main_stats.data['allPvE']['allTime']
    if main_stats.data['raid']: #has user attempted rate
    raid_dat = main_stats.data['raid']['allTime']
    raid_clears = raid_dat['activitiesCleared']['basic']['displayValue']
    else:
    raid_dat = None
    if main_stats.data['allPvP']: #has user done pvp
    pvp_dat = main_stats.data['allPvP']['allTime']
    else:
    pvp_dat = None

    #########################
    #%%TEST HELPER FUNCTIONS:
    #SUMMARIZE_PVP, SUMMARIZE_PVE, AND SUMMARIZE_PLAYER_PERFORMANCE
    user_stats = main_stats.data
    user_pvp = summarize_pvp(user_name, user_stats)
    user_pve = summarize_pve(user_name, user_stats)
    user_summary_stats = summarize_player_performance(user_name, user_stats)

    ##################
    #%% CLAN STUFF
    ##################
    #TEST GET_GROUPS_FOR_MEMBER
    get_groups_url = get_groups_for_member_url(user_name, user_platform, my_api_key)
    groups_summary = destiny2_api_public(get_groups_url, my_api_key)

    #%% set flag for whether user is in clan or not
    if groups_summary.data['results']:
    user_in_clan = 1
    else:
    user_in_clan = 0
    print("User {0} is not in a clan. Why are you all alone, {0}?".format(user_name))

    #%%#Summarize basic info about the clan they are in
    if user_in_clan:
    user_clan_data = groups_summary.data['results'][0]['group']
    clan_id = user_clan_data['groupId']
    clan_name = user_clan_data['name']
    clan_description = user_clan_data['about']
    clan_motto = user_clan_data['motto']
    clan_num_members = user_clan_data['memberCount']
    print("\n\nUser is in the clan named '{0}'. Their motto is '{1}'.\nThere are currently {2} members.\n\n" \
    "Their book-lenth description is...\n{3}".format(clan_name, clan_motto, clan_num_members, clan_description))

    #%% TEST GET_MEMBERS_FOR_GROUP (get all clan members)
    clan_members_url = get_members_of_group_url(clan_id)
    clan_members_summary = destiny2_api_public(clan_members_url, my_api_key)
    #Check out some results
    num_members = clan_members_summary.data['totalResults']
    member_data = clan_members_summary.data['results'] #full data list
    clan_members_data = generate_clan_list(member_data, user_platform)

    #%% Display clan roster in a pretty way
    print_clan_roster(clan_members_data)

    #%% TEST SUMMARIZE_CLAN_PERFORMANCE
    clan_performance, debug_performance = \
    summarize_clan_performance(clan_members_data, user_platform, my_api_key)
    if save_to_file:
    save_readable_json(clan_performance, 'clan_stats.txt')

    #Helper functions to print some stuff for fun
    print_clan_performance(clan_performance)
    print_misadventure_rates(clan_performance)



    """Good luck, guardian. I'm sorry I didn't have time to explain what I didn't have
    time to understand. Time to go drink some vex milk."""