def __init__(self,globalNum): logger.info("Initialising MetaSteam"); #Locks: #The lock for reading and writing the output json of game data self.jsonLock = threading.Lock() #the lock for the runtime objects of game data self.internalDataLock = threading.Lock() #Data: self.userName = "******" self.globalNumberOfGamesToSearch = int(globalNum) #Steam Executable Location: self.steamLocation = None #Steam Libraries: self.libraryLocations =[] #Location of meta steam program: if isFrozen(): self.programLocation = os.path.dirname(unicode(sys.executable,sys.getfilesystemencoding())) else: self.programLocation = os.path.dirname(unicode(__file__,sys.getfilesystemencoding())) logger.info("programLocation="+self.programLocation) #The location of the settings json data: self.settingsFile = os.path.join(self.programLocation,"data","settings.json") #Game information: self.installedGames = {} #key = appid self.profileGames = {} #key = appid #initialisation: self.loadSettingsFromJson() self.findLibraries() self.findSteam() #Web Scrapers: self.scraper = SteamStoreScraper() self.profileScraper = SteamProfileScraper(self.userName) #TODO: verify locations self.verifyLocations() #import already scraped gameData self.importFromJson() if not self.profileGames: self.getProfileGames() else: logger.info("Skipping Profile Scrape") self.loadGamesFromManifests() self.exportToJson() #By this point, all previously found games #should be loaded. Now just find new games, #and get info for them logger.info("Setting up web info extraction thread") getInfoThread = threading.Thread(target=MetaSteam.getInfoForAllGames,args=(self,)) logger.info("Starting web info extraction thread") getInfoThread.start()
def compare_to_user(username): logger.info( "TODO: allow comparison of user profiles") profileScraper = SteamProfileScraper(username) extractedInfo = profileScraper.scrape() return extractedInfo
class MetaSteam: ''' @class MetaSteam @method __init__ @purpose constructor @param globalNum The max number of games to scrape in a session ''' def __init__(self,globalNum): logger.info("Initialising MetaSteam"); #Locks: #The lock for reading and writing the output json of game data self.jsonLock = threading.Lock() #the lock for the runtime objects of game data self.internalDataLock = threading.Lock() #Data: self.userName = "******" self.globalNumberOfGamesToSearch = int(globalNum) #Steam Executable Location: self.steamLocation = None #Steam Libraries: self.libraryLocations =[] #Location of meta steam program: if isFrozen(): self.programLocation = os.path.dirname(unicode(sys.executable,sys.getfilesystemencoding())) else: self.programLocation = os.path.dirname(unicode(__file__,sys.getfilesystemencoding())) logger.info("programLocation="+self.programLocation) #The location of the settings json data: self.settingsFile = os.path.join(self.programLocation,"data","settings.json") #Game information: self.installedGames = {} #key = appid self.profileGames = {} #key = appid #initialisation: self.loadSettingsFromJson() self.findLibraries() self.findSteam() #Web Scrapers: self.scraper = SteamStoreScraper() self.profileScraper = SteamProfileScraper(self.userName) #TODO: verify locations self.verifyLocations() #import already scraped gameData self.importFromJson() if not self.profileGames: self.getProfileGames() else: logger.info("Skipping Profile Scrape") self.loadGamesFromManifests() self.exportToJson() #By this point, all previously found games #should be loaded. Now just find new games, #and get info for them logger.info("Setting up web info extraction thread") getInfoThread = threading.Thread(target=MetaSteam.getInfoForAllGames,args=(self,)) logger.info("Starting web info extraction thread") getInfoThread.start() ''' @class MetaSteam @method findLibraries @purpose To find, across all drives, steam directories for search later @modifies MetaSteam.libraryLocations ''' def findLibraries(self): if skipWin: return try: logger.info("finding libraries") drives = win32api.GetLogicalDriveStrings() logger.info("Drives:" + str(drives)) drives = drives.split('\000')[:-1] for drive in drives: logger.info( "Checking Drive: " + drive) loc = os.path.join(drive,"*") result = glob.glob(loc) #TODO: check lower case behaviour for windows potentials = [s for s in result if "steam" in s.lower()] #if any("steam" in s.lower() for s in result): for potential in potentials: folder = os.path.join(potential,"steamapps") if os.path.exists(folder): logger.info( "Found: " + folder) self.libraryLocations.append(folder) else: logger.warn("Steam Location Doesnt Exist: " + folder) #raise MetaSteamException("Steam Location doesnt exist: " + folder) except Exception as e: logger.error("Exception: findLibraries: " + str(e)) ''' @class MetaSteam @method findSteam @purpose finds the steam executable @modifies MetaSteam.steamLocation @modifies MetaSteam.libraryLocations ''' def findSteam(self): if skipWin: return try: logger.info("Finding Steam") #if a steam location was loaded from settings: if self.steamLocation and os.path.exists(self.steamLocation): self.libraryLocations.append(os.path.join(self.steamLocation.replace('Steam.exe',''),"steamapps")) return else: logger.warn("Steam Location unknown: " + self.steamLocation) logger.warn("Attempting hard coded location") steamLocation = os.path.join("C:\\","Program Files (x86)","Steam") logger.info("Steam Location: " + steamLocation) if os.path.exists(steamLocation): logger.info("Steam Location Exists") self.steamLocation = steamLocation self.libraryLocations.append(os.path.join(steamLocation,"steamapps")) return else: logger.warn("Couldn't find Steam") #raise MetaSteamException("Default Steam Location doesnt exist") #final fallback: self.steamLocation = None except Exception as e: self.steamLocation = None logger.error("Exception: findSteam: " + str(e)) ''' @class MetaSteam @method verifyLocations @purpose verifies that steamlocation and steamLibraries exist ''' def verifyLocations(self): for location in self.libraryLocations: if not os.path.exists(location): logger.warn("Removing non-existent location: " + location) self.libraryLocations.remove(location) if self.steamLocation is not None and not os.path.exists(self.steamLocation): logger.warn("steamLocation doesnt exist: " + self.steamLocation) self.steamLocation = None #check the steamLocation points to the exe: if self.steamLocation is not None and ".exe" not in self.steamLocation: logger.warn("steamLocation isnt an exe: " + self.steamLocation) self.steamLocation = None ''' @class MetaSteam @method exportToJson @purpose Exports the found game data, on drive and web scraped, to a file @threadSafe ''' def exportToJson(self): try: logger.info("Exporting to Json") if not os.path.exists(os.path.join(self.programLocation,"data")): raise MetaSteamException("No Data Directory Exists") self.jsonLock.acquire() self.internalDataLock.acquire() outputFile = open(os.path.join(self.programLocation,"data","gameData.json"),'w') combinedData = {} combinedData['installed'] = self.installedGames combinedData['profile'] = self.profileGames outputJson = json.dump(combinedData,outputFile,sort_keys=True, indent=4, separators=(','':'),ensure_ascii=True, skipkeys=True) except Exception as e: logger.error("Exception: exportToJson: " + str(e)) finally: outputFile.close() self.internalDataLock.release() self.jsonLock.release() ''' @class MetaSteam @method importFromJson @purpose Loads a json file that has information about games @modifies MetaSteam.installedGames @modifies MetaSteam.profileGames @threadSafe ''' def importFromJson(self): inputFile = None try: self.jsonLock.acquire() self.internalDataLock.acquire() logger.info("Loading Game Data Json") inputFile = codecs.open(os.path.join(self.programLocation, "data","gameData.json")) importedJson = json.load(inputFile) # for game in importedJson['installed'].values(): self.installedGames[game['appid']] = game # for game in importedJson['profile'].values(): self.profileGames[game['appid']] = game except Exception as e: logger.error("Exception: importFromJson: " + str(e)) finally: if inputFile: inputFile.close() self.jsonLock.release() self.internalDataLock.release() ''' @class MetaSteam @method loadSettingsFromJson @purpose To load external settings for username,steamlocation,librarylocations @modifies MetaSteam.userName @modifies MetaSteam.steamLocation @modifies MetaSteam.libraryLocations ''' def loadSettingsFromJson(self): try: logger.info("Loading settings from json") inputFile = codecs.open(self.settingsFile) importedJson = json.load(inputFile) self.userName = importedJson['steamProfileName'] logger.info("Loaded username:"******"Loaded steam location:" + self.steamLocation) self.libraryLocations = importedJson['steamLibraryLocations'] logger.info("Loaded libraryLocations:" + str(self.libraryLocations)) #todo: deal with web browser except Exception as e: logger.exception("Exception: loadSettingsFromJson: " + str(e)) finally: inputFile.close() ''' @class MetaSteam @method loadGamesFromManifest @purpose find all installed manifests in libraryLocations, parse each manifest ''' def loadGamesFromManifests(self): try: logger.info("Loading Games") for folder in self.libraryLocations: manifests = glob.glob(os.path.join(folder,"*.acf")) logger.info(str(folder) + " : Number of found manifests: " + str(len(manifests))) for manifest in manifests: self.parseManifest(manifest) except Exception as e: logger.error("Exception: loadGamesFromManifest: " + str(e)) ''' @class MetaSteam @method parseManifest @purpose To take an installed game manifest and extract info from it @modifies MetaSteam.installedGames[gameid] @threadSafe ''' def parseManifest(self,manifest): try: #logger.info("Parsing a manifest") f = open(manifest,'r') regex = re.compile('"(.+?)"\s+"(.+?)"') data = {} for line in f: line = unicode(line,errors="ignore") #logger.info("Line type: " + str(type(line))) match = regex.search(line) if match: data[match.group(1)] = match.group(2) gameid = data['appid'] #logger.info("Found: " + data['name']) #logger.info("TYPE: " + str(type(gameid))) data['__Installed'] = True #if it doesnt exist yet: if not gameid in self.installedGames.keys(): self.internalDataLock.acquire() self.installedGames[gameid] = data self.internalDataLock.release() else: #if it exists already copy over information self.internalDataLock.acquire() for field in data.keys(): self.installedGames[gameid][field] = data[field] self.internalDataLock.release() except Exception as e: logger.error("Exception: parseManifest: " + str(e)) finally: f.close() ''' @class MetaSteam @method combineData @todo @purpose To fold the two forms of stored data together ''' def combineData(self): print("TODO:combine data from store scraping and profile scraping") logger.warn("Combine Data not implemented") ''' @class MetaSteam @method getProfileGames @purpose Call the profile scraper, to extract all information about a users games, not just installed games @modifies MetaSteam.profileGames @threadSafe ''' def getProfileGames(self): try: logger.info( "Getting Profile Games") extractedInfo = self.profileScraper.scrape() self.internalDataLock.acquire() self.profileGames = extractedInfo self.internalDataLock.release() except Exception as e: logger.error("Exception: getProfileGames: " + str(e)) ''' @class MetaSteam @method getInfoForGame @purpose To store scrape a specific game, and update the passed in game's data @para game A Game dictionary, likely built from a parsed manifest ''' #get steam page tags and release date def getInfoForGame(self,game): logger.info("Getting Info for Game: " + str(game['appid'])) try: extractedInfo = self.scraper.scrape(game['appid']) game['__tags'] = extractedInfo[0] game['releaseDate'] = extractedInfo[1] game['__description'] = extractedInfo[2] game['__review'] = extractedInfo[3] game['__developer'] = extractedInfo[4] game['__publisher'] = extractedInfo[5] game['__scraped'] = True except Exception as e: logger.warn("Exception: getInfoForGame: " + str(e)) finally: return game ''' @class MetaSteam @method getInfoForAllGames @purpose for all games installed, scrape information about them @todo be able to reset __scraped @threadSafe ''' def getInfoForAllGames(self): try: logger.info("Getting web information for all games") scrapedGames = [] for game in self.installedGames.values(): if self.globalNumberOfGamesToSearch < 1: break if '__scraped' in game: continue try: self.internalDataLock.acquire() self.installedGames[game['appid']] = self.getInfoForGame(game) except Exception as e: logger.error("Exception: getInfoForAllGames: " + game['appid'] + str(e)) finally: self.internalDataLock.release() if 'name' in game.keys(): logger.info("Game: " + game['name'] + " parsed") game['__scraped'] = True self.exportToJson() self.globalNumberOfGamesToSearch -= 1 scrapedGames.append(game['name']) time.sleep(waitTime) logger.info( "Have scanned all games for this run: " + str(len(scrapedGames))) except Exception as e: logger.error("Exception: getInfoForAllGames: " + str(e)) finally: self.exportToJson() ''' @class MetaSteam @method loadVisualisation @purpose Starts the http server and opens the web browser to the metaSteam page ''' def loadVisualisation(self): try: #Start the web server in a separate thread logger.info( "Sending to RunLocalServer: " + str(self)) serverThread = threading.Thread(target=MetaSteamHTTPServer.runLocalServer,args=(self,)) serverThread.start() logger.info( "\nOPENING WEBBROWSER:\n\n") webbrowser.open("http:\\localhost:8000\web\MetaSteam.html") except Exception as e: logger.error("Exception: loadVisualisation: " + str(e)) ''' @class MetaSteam @method startGame @purpose Calls the steam executable to start the specified game @param appid the steam appid of the game you want to start ''' def startGame(self,appid): try: if self.steamLocation is None: logger.warn("Tried to start game, but there is no registered steam executable") return False logger.info( "Starting game: " + str(appid)) if appid == None: appid = 440 logger.warn( "No Appid, defaulting to TF2") logger.info("Opening using: " + self.steamLocation) subprocess.call([self.steamLocation,"-applaunch",str(appid)]) return True except Exception as e: logger.error("Exception: startGame: " + str(e)) finally: return False