RZetko / galaxy-integration-ffxiv

Final Fantasy XIV integration for GOG Galaxy 2
MIT License
10 stars 6 forks source link

Achievement support #12

Open FriendsOfGalaxy opened 4 years ago

FriendsOfGalaxy commented 4 years ago

Hey, it would be cool to add real achievement support. See this similar topic for details: https://github.com/Mixaill/galaxy-integration-gw2/issues/4#issuecomment-541588150

FriendsOfGalaxy commented 4 years ago

I've wrote a quick parser for you. And I will pass to current achievements definition to GOG myself.

It will be useful next time if there will be more new achievements. Ideally this could be deployed on some server like Mixail has done for GW2 but as far as I know GOG still utilize it manually from time to time (or from request to request...)

import requests
import time
import json

GAME_ID = 'final_fantasy_xiv_shadowbringers'  # gameId is used in the plugin
XIVAPI_URL = 'https://xivapi.com'
SKIP_DESCR_WORDS = ['<Emphasis>', '</Emphasis>']
START_FROM = 1

def get_json(url):
    print('getting', url)
    r = requests.get(url)
    print(r.status_code)
    if r.status_code == 429:   # to many requests
        time.sleep(60)
        return get_json(url)
    return r.json()

def parse_description(descr: str):
    for w in SKIP_DESCR_WORDS:
        descr.replace(w, '')
    return descr

def parse_achievement(info: dict, details: dict):
    assert info['Name']
    assert info['ID']
    return {
        "name": info['Name'],
        "description": parse_description(details['Description']),  # Galaxy has no localization so far
        "api_key": str(info['ID']),
        "image_url_unlocked": XIVAPI_URL + info['Icon'],
        "image_url_locked": ""  # use default Galaxy icon
    }

if __name__ == "__main__":

    achievements = []

    # check number of pages
    base = get_json(XIVAPI_URL + '/Achievement')
    page_total = base['Pagination']['PageTotal']

    for i in range(START_FROM, page_total + 1):
        result = get_json(XIVAPI_URL + f'/Achievement?page={i}')
        for info in result['Results']:
            details = get_json(XIVAPI_URL + info['Url'])
            try:
                achievements.append(parse_achievement(info, details))
            except AssertionError:  # not valid/empty achievement
                print(f'invalid achievement: {info}. Skipping')
            time.sleep(0.1)  # avoid API limits

    with open('achievements_data.json', 'w') as f:
        data = {
            "release_per_platform_id": f"ffxiv_{GAME_ID}",
            "achievements": achievements
        }
        json.dump(data, f, indent=4)
UmbyUmbreon commented 4 years ago

It should also be noted that XIVAPI maintains this data in a CSV format in xivapi/ffxiv-datamining, so you could also possibly watch the achievement.csv file for changes and parse it to Galaxy's JSON format, if that works better?

FriendsOfGalaxy commented 4 years ago

I didn't know about this project. Yes, this is basically the same data.

UmbyUmbreon commented 4 years ago

OK, I plugged this together based on the above code during 5.21 maintenance, which added and changed a few achievements. If you feed it an achievement.csv file from the datamining repository, it will produce a JSON file with the achievement data for Galaxy.

import csv
import json
from itertools import islice
import urllib.request
import codecs

GAME_ID = 'final_fantasy_xiv_shadowbringers'  # gameId is used in the plugin
XIVAPI_URL = 'https://xivapi.com'

def parse_achievement(id, name, description, icon):
    assert id
    assert name

    return {
        'name': name,
        'description': description,
        'api_key': str(id),
        'image_url_unlocked': get_icon_url(icon) if icon else '',
        'image_url_locked': ''
    }

def get_icon_url(icon):
    # XIVAPI folder structure details: https://xivapi.com/docs/Icons
    if len(icon) >= 6:
        icon = str.rjust(icon, 5, "0")
    else:
        icon = f'0{str.rjust(icon, 5, "0")}'

    if len(icon) >= 6:
        folder = f'{icon[0]}{icon[1]}{icon[2]}000'
    else:
        folder = f'0{icon[1]}{icon[2]}000'
    return f'{XIVAPI_URL}/i/{folder}/{icon}.png'

if __name__ == '__main__':
    achievements = []
    url = 'https://raw.githubusercontent.com/xivapi/ffxiv-datamining/master/csv/Achievement.csv'

    stream = urllib.request.urlopen(url)
    csvdata = codecs.iterdecode(stream, 'utf-8')

    # We don't care about the first line in the file, skip it
    next(csvdata)

    reader = csv.DictReader(csvdata)

    # We also don't care about the 3rd line in the file, which deals with datatypes
    next(reader)

    for line in reader:
        try:
            achievements.append(parse_achievement(line['#'], line['Name'], line['Description'], line['Icon']))
        except AssertionError:
            pass

    with open('achievement_data.json', 'w', encoding='utf-8') as j:
        data = {
            'release_per_platform_id': f'ffxiv_{GAME_ID}',
            'achievements': achievements
        }
        json.dump(data, j, indent=4)
FriendsOfGalaxy commented 4 years ago

@UmbyUmbreon

I've run both and compared results. Those changes are:

To sum up direct call to API lasts longer, but is more reliable. csv may be outdated. Icon should be probably None instead of empty transparent 40x40 png in https://xivapi.com/i/000000/000000.png

tags are probably undesirable. I see `def get_icon_url(icon)` logic is more complex. Does it mean 'Icon' key directly from api response is false? I didn't spot any mismatch despite described above.
lzlrd commented 2 years ago

@FriendsOfGalaxy, I haven't given the code a look but the README states that achievement support is waiting on "platform support" which I assume means GOG needs to achievement definitions. Has there been any progress in that regard?

StoiaCode commented 2 years ago

@FriendsOfGalaxy, I haven't given the code a look but the README states that achievement support is waiting on "platform support" which I assume means GOG needs to achievement definitions. Has there been any progress in that regard?

Honestly since the plugin did not even add Endwalker yet, there will probably be a bunch of problems with Achievement integration anyway? But yes im curious as well.