#!/usr/bin/env python3 import os import subprocess import argparse import atexit from shutil import unpack_archive import yaml import mergedeep from lib import handlers from appdirs import user_data_dir, user_config_dir config = { 'global': { 'game_dir': os.path.join(user_data_dir(), 'minitch'), 'os': 'linux' }, 'games': {} } def read_config(): global config path = os.path.join(user_config_dir(), 'minitchrc') try: with open(path, 'r') as f: c = {**config, **yaml.safe_load(f)} mergedeep.merge(config, c) except FileNotFoundError: pass except yaml.YAMLError: raise SystemExit('Invalid config') def write_config(): global config path = os.path.join(user_config_dir(), 'minitchrc') with open(path, 'w') as f: yaml.dump(config, f, default_flow_style=False) def parse_config(args): if args.api_key: config['global']['api_key'] = args.api_key def parse_search(args): global config if not 'api_key' in config['global']: raise SystemExit('You have to set your api key first.') game_handler = handlers.GameHandler(config['global']['api_key'], output_format=args.output) game_handler.find(args.query) def parse_uploads(args): global config if not 'api_key' in config['global']: raise SystemExit('You have to set your api key first.') game_handler = handlers.GameHandler(config['global']['api_key'], output_format=args.output) game_handler.uploads(args.game_id) def install_game(game_id, dst_path, output_format): global config # Download game game_handler = handlers.GameHandler(config['global']['api_key'], output_format=output_format) dst, upload, meta = game_handler.download(game_id, dst_path, os_type=config['global']['os']) # Unpack game print('Extracting...') unpack_archive(dst, dst_path) return dst, upload, meta def parse_install(args): global config # Create target directory game_path = os.path.join(config['global']['game_dir'], args.game_id) os.makedirs(game_path, exist_ok=True) try: dst, upload, meta = install_game(args.game_id, game_path, args.output) # Write game to config config['games'][args.game_id] = { 'path': game_path, 'last_update': upload['updated_at'], 'url': meta['url'], 'title': meta['title'], 'description': meta['short_text'] } print('FYI: As itch.io does not have any information on how to execute the game, you have to set the executable manually on first run with the --executable option') except handlers.HandlerException as err: raise SystemExit(str(err)) def parser_run(args): global config if not args.game_id in config['games']: raise SystemExit('Game is not installed.') if args.executable: config['games'][args.game_id]['executable'] = args.executable if not 'executable' in config['games'][args.game_id]: raise SystemExit('You have to define the executable before you can run the game.') if args.update: game_handler = handlers.GameHandler(config['global']['api_key'], output_format=args.output) print('Checking for updates...') if game_handler.check_update(args.game_id, config['games'][args.game_id]['last_update'], config['global']['os']): dst, upload, meta = install_game(args.game_id, config['games'][args.game_id]['path'], args.output) config['games'][args.game_id]['last_update'] = upload['updated_at'] # Run executable os.chdir(config['games'][args.game_id]['path']) os.chmod(config['games'][args.game_id]['executable'], 755) game_args = config['games'][args.game_id]['args'] if 'args' in config['games'][args.game_id] else [] try: subprocess.run([config['games'][args.game_id]['executable'], *game_args]) except KeyboardInterrupt: pass def parser_list(args): global config game_handler = handlers.GameHandler(config['global']['api_key'], output_format=args.output) game_handler.list_local(config['games']) def main(): read_config() # Write config on exit atexit.register(write_config) parser = argparse.ArgumentParser(description='Tiny little itch.io client') subparsers = parser.add_subparsers(title='subcommands', description='valid subcommands', help='additional help') # Global arguments parser.add_argument('--output', '-o', choices=('table', 'json'), default='table', help='set the output format') # Parser for "config" command parser_config = subparsers.add_parser('config', aliases=['c'], help='config help') parser_config.add_argument('--api_key', type=str, help='set api key') parser_config.set_defaults(func=parse_config) # Parser for "search" command parser_search = subparsers.add_parser('search', aliases=['s'], help='search help') parser_search.add_argument('query', type=str, help='the search query') parser_search.set_defaults(func=parse_search) # Parser for "uploads" command parser_search = subparsers.add_parser('uploads', help='uploads help') parser_search.add_argument('game_id', type=str, help='game_id to query') parser_search.set_defaults(func=parse_uploads) # Parser for "install" command parser_search = subparsers.add_parser('install', aliases=['i'], help='install help') parser_search.add_argument('game_id', type=str, help='game_id to query') parser_search.set_defaults(func=parse_install) # Parser for "run" command parser_search = subparsers.add_parser('run', aliases=['r'], help='run help') parser_search.add_argument('game_id', type=str, help='game_id to run') parser_search.add_argument('--executable', '-e', type=str, help='set executable relative to game path (e.g. -e "chess_2_linux_v2/chess 2.x86_64")') parser_search.add_argument('--update', '-u', help='check for update before running', action='store_true') parser_search.set_defaults(func=parser_run) # Parser for "list" command parser_search = subparsers.add_parser('list', aliases=['l'], help='list help') parser_search.set_defaults(func=parser_list) args = parser.parse_args() try: args.func(args) except AttributeError: parser.error('too few arguments') if __name__ == "__main__": main()