jshackles / idle_master_py

The Python version of Idle Master
GNU General Public License v2.0
230 stars 71 forks source link

Pure game time idler #29

Open Gummibeer opened 7 years ago

Gummibeer commented 7 years ago

I've adjusted the python script a bit to make it possible that it runs with the steam API and without session/webbrowser. It can also run games in parallel and picks everytime the games with the lowest playtime. It has a setting for the amount of games and the single loop duration.

time.py

import requests
import time
import subprocess
import sys
import os
import json
import logging
import datetime
import ctypes
from colorama import init, Fore, Back, Style

init()

os.chdir(os.path.abspath(os.path.dirname(sys.argv[0])))

logging.basicConfig(filename="logs/idlemaster_time_" + str(time.time()) + ".log", filemode="w", format="[ %(asctime)s ] %(message)s",
                    datefmt="%m/%d/%Y %I:%M:%S %p", level=logging.DEBUG)
console = logging.StreamHandler()
console.setLevel(logging.WARNING)
console.setFormatter(logging.Formatter("[ %(asctime)s ] %(message)s", "%m/%d/%Y %I:%M:%S %p"))
logging.getLogger('').addHandler(console)

if sys.platform.startswith('win32'):
    ctypes.windll.kernel32.SetConsoleTitleA("Idle Master")

logging.warning(Fore.GREEN + "WELCOME TO IDLE MASTER" + Fore.RESET)

process_idle = {}
idle_time = {}

try:
    authData = {}
    execfile("./settings.py", authData)
    authData["steamId"] = authData["steamLogin"][:17]
except:
    logging.warning(Fore.RED + "Error loading config file" + Fore.RESET)
    raw_input("Press Enter to continue...")
    sys.exit()

if not authData["apiKey"]:
    logging.warning(Fore.RED + "No apiKey set" + Fore.RESET)
    raw_input("Press Enter to continue...")
    sys.exit()

if not authData["steamLogin"]:
    logging.warning(Fore.RED + "No steamLogin set" + Fore.RESET)
    raw_input("Press Enter to continue...")
    sys.exit()

if not authData["steamId"]:
    logging.warning(Fore.RED + "No steamId set" + Fore.RESET)
    raw_input("Press Enter to continue...")
    sys.exit()

def idleOpen(appID):
    try:
        logging.warning("Starting game " + getAppName(appID) + " to idle cards")
        global process_idle
        global idle_time

        idle_time[appID] = time.time()

        if sys.platform.startswith('win32'):
            process_idle[appID] = subprocess.Popen("steam-idle.exe " + str(appID))
        elif sys.platform.startswith('darwin'):
            process_idle[appID] = subprocess.Popen(["./steam-idle", str(appID)])
        elif sys.platform.startswith('linux'):
            process_idle[appID] = subprocess.Popen(["python2", "steam-idle.py", str(appID)])
    except:
        logging.warning(Fore.RED + "Error launching steam-idle with game ID " + str(appID) + Fore.RESET)
        raw_input("Press Enter to continue...")
        sys.exit()

def idleClose(appID):
    try:
        logging.warning("Closing game " + getAppName(appID))
        process_idle[appID].terminate()
        total_time = int(time.time() - idle_time[appID])
        logging.warning(getAppName(appID) + " took " + Fore.GREEN + str(
            datetime.timedelta(seconds=total_time)) + Fore.RESET + " to idle.")
    except:
        logging.warning(Fore.RED + "Error closing game. Exiting." + Fore.RESET)
        raw_input("Press Enter to continue...")
        sys.exit()

def getAppName(appID):
    try:
        api = requests.get("http://store.steampowered.com/api/appdetails/?appids=" + str(appID) + "&filters=basic")
        api_data = json.loads(api.text)
        return Fore.CYAN + api_data[str(appID)]["data"]["name"].encode('ascii', 'ignore') + Fore.RESET
    except:
        return Fore.CYAN + "App " + str(appID) + Fore.RESET

def getPlainAppName(appid):
    try:
        api = requests.get("http://store.steampowered.com/api/appdetails/?appids=" + str(appID) + "&filters=basic")
        api_data = json.loads(api.text)
        return api_data[str(appid)]["data"]["name"].encode('ascii', 'ignore')
    except:
        return "App " + str(appid)

def get_blacklist():
    try:
        with open('blacklist.txt', 'r') as f:
            lines = f.readlines()
        blacklist = [int(n.strip()) for n in lines]
    except:
        blacklist = []

    if not blacklist:
        logging.warning("No games have been blacklisted")

    return blacklist

def getKey(item):
    return item['playtime_forever']

def getGames():
    logging.warning("Finding games to idle")

    global authData
    blacklist = get_blacklist()
    games = []

    try:
        global authData
        url = "http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?format=json&key="+authData['apiKey']+"&steamid="+authData['steamId']
        api = requests.get(url)
        game_data = json.loads(api.text)['response']['games']
        for game in game_data:
            gameId = game['appid']
            if gameId in blacklist:
                logging.warning(getAppName(gameId) + " on blacklist, skipping game")
                continue
            else:
                games.append(game)

        return sorted(games, key=getKey)[:authData['parallel']]
    except:
        logging.warning(Fore.RED + "Error reading games api" + Fore.RESET)
        raw_input("Press Enter to continue...")
        sys.exit()

while True:
    global authData
    games = getGames()
    for game in games:
        idleOpen(game['appid'])

    delay = authData['sleeping'] * 60
    logging.warning("Sleeping for " + str(delay / 60) + " minutes")
    time.sleep(delay)

    for game in games:
        idleClose(game['appid'])

logging.warning(Fore.GREEN + "Successfully completed idling process" + Fore.RESET)
raw_input("Press Enter to continue...")

settings.py

steamLogin = ""
apiKey = ""
parallel = 6
sleeping = 5

These values are need - the steamLogin just to extract the steamID. It's running pretty well. I've also adjusted some other things like the log location and that it creates everytime a new log file and things like this.

With this you can decrease the amount of unplayed games and increase the play time for every game and with this also your total time.

Gummibeer commented 7 years ago

Have fixed some things, improved the logging part a bit more and fixed all python code style and other hints:

import requests
import time
import subprocess
import sys
import os
import json
import logging
import ctypes
from colorama import init, Fore

init()

os.chdir(os.path.abspath(os.path.dirname(sys.argv[0])))

logging.basicConfig(
    filename="logs/idlemaster_time_" + str(time.time()) + ".log",
    filemode="w",
    format="[ %(asctime)s ] %(message)s",
    datefmt="%m/%d/%Y %I:%M:%S %p",
    level=logging.DEBUG
)
console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
console.setFormatter(logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s", "%d-%m-%Y %H:%M:%S"))
logging.getLogger('').addHandler(console)
logging.getLogger("requests").setLevel(logging.WARNING)

if sys.platform.startswith('win32'):
    ctypes.windll.kernel32.SetConsoleTitleA("Idle Master")

DEVNULL = open(os.devnull, 'w')

process_idle = {}
idle_time = {}
authData = {}

def stop():
    raw_input("Press [Enter] to continue ...")
    sys.exit()

def debug(message):
    logging.debug(message + Fore.RESET)

def info(message):
    logging.info(Fore.GREEN + message + Fore.RESET)

def warning(message):
    logging.warning(Fore.YELLOW + message + Fore.RESET)
    stop()

def error(message):
    logging.error(Fore.RED + message + Fore.RESET)
    stop()

info("WELCOME TO IDLE MASTER (GAMES BY TIME)")

try:
    execfile("./settings.py", authData)
    authData["steamId"] = authData["steamLogin"][:17]
except Exception, e:
    error("Error loading settings file: " + str(e))

if not authData["apiKey"]:
    warning("No apiKey set")

if not authData["steamLogin"]:
    warning("No steamLogin set")

if not authData["steamId"]:
    warning("No steamId set")

def idle_open(app_id):
    try:
        debug(Fore.GREEN + u'\u2713' + Fore.RESET + " " + get_app_name(app_id))
        global process_idle
        global idle_time

        idle_time[app_id] = time.time()

        if sys.platform.startswith('win32'):
            process_idle[app_id] = subprocess.Popen("steam-idle.exe " + str(app_id), stdout=DEVNULL, stderr=DEVNULL)
        elif sys.platform.startswith('darwin'):
            process_idle[app_id] = subprocess.Popen(["./steam-idle", str(app_id)], stdout=DEVNULL, stderr=DEVNULL)
        elif sys.platform.startswith('linux'):
            process_idle[app_id] = subprocess.Popen(["python2", "steam-idle.py", str(app_id)], stdout=DEVNULL, stderr=DEVNULL)
    except Exception, e:
        error("Error starting steam-idle with game ID " + str(app_id) + ": " + str(e))

def idle_close(app_id):
    try:
        debug(Fore.RED + u'\u2715' + Fore.RESET + " " + get_app_name(app_id))
        process_idle[app_id].terminate()
    except Exception, e:
        error("Error closing steam-idle with game ID " + str(app_id) + ": " + str(e))

def get_app_name(app_id):
    try:
        api = requests.get("http://store.steampowered.com/api/appdetails/?appids=" + str(app_id) + "&filters=basic")
        api_data = json.loads(api.text)
        return Fore.CYAN + api_data[str(app_id)]["data"]["name"].encode('ascii', 'ignore') + Fore.RESET
    except:
        return Fore.CYAN + "App " + str(app_id) + Fore.RESET

def get_blacklist():
    try:
        with open('blacklist.txt', 'r') as f:
            lines = f.readlines()
        blacklist = [int(n.strip()) for n in lines]
    except:
        blacklist = []

    if not blacklist:
        debug("No games have been blacklisted")

    return blacklist

def get_key(item):
    return item['playtime_forever']

def get_games():
    global authData
    info("Searching for "+str(authData['parallel'])+" games to idle")

    blacklist = get_blacklist()
    game_list = []

    try:
        url = "http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?format=json&key="+authData['apiKey']+"&steamid="+authData['steamId']+"&time="+str(time.time())
        api = requests.get(url)
        game_data = json.loads(api.text)['response']['games']
        for data in game_data:
            game_id = data['appid']
            if game_id in blacklist:
                continue
            else:
                game_list.append(data)

        return sorted(game_list, key=get_key)[:authData['parallel']]
    except Exception, e:
        error("Error reading games api: " + str(e))

while True:
    try:
        games = get_games()
        for game in games:
            idle_open(game['appid'])

        delay = authData['sleeping']
        info("Idling for " + str(delay) + " minutes")
        time.sleep(delay * 60)

        for game in games:
            idle_close(game['appid'])
    except KeyboardInterrupt:
        warning("Closing cause of keyboard interrupt")
    except Exception, e:
        error("error in main loop: " + str(e))

info("Successfully completed idling process")
stop()
Finnian3 commented 7 years ago

Noice, I'll try this in a few days

Gummibeer commented 7 years ago

@Finnian3 the latest version is at https://github.com/Gummibeer/steam_idle_time_python

including a new config and way to work - on windows it's running super stable also with 20 and up games at the same time. Linux steam client itself crashes sometimes - so the time isn't counted. :/