import argparse, datetime, os, requests, shutil, subprocess, sys, zipfile from QuestAssetDL import main as QuestAssetDL from github import Github, Auth # Temporary directory for scratch space TEMP_DIR = "__temp__" LATEST_RELASE_DIR = "latest_release" QALAG_OUTPUT_DIR = "qalag" WINDIFF_EXE_PATH = "bin\\WinDiff.exe" QUEST_DIR = "quest" ICONPACK_QUEST = "iconpack_quest.zip" APPNAMES_QUEST = "appnames_quest.json" ICONPACK_OTHER = "iconpack_others.zip" APPNAMES_OTHER = "appnames_other.json" APPNAMES_GALAG_NAME = "appnames_quest_en_US.json" def main(): parser = argparse.ArgumentParser(description="Quest Asset Generator") parser.add_argument( "-a", "--access-token", required=True, help="GitHub acess token" ) parser.add_argument( "-dr", "--download-release", action="store_true", help="Download latest asset release from github", ) parser.add_argument( "-da", "--download-assets", action="store_true", help="Download assets from Oculus", ) parser.add_argument("-c", "--compare", action="store_true", help="Compare assets") parser.add_argument( "-r", "--release", action="store_true", help="Draft a github release" ) args = parser.parse_args() # If nothing is specified, perform all actions if ( not args.download_release and not args.download_assets and not args.compare and not args.release ): args.download_release = True args.download_assets = True args.compare = True args.release = True # Instantiate github client ga = Auth.Token(args.access_token) g = Github(auth=ga) repo = g.get_user("HooverHigh").get_repo("QuestAppLauncher_Assets") # Set up temp dir folder_path_temp = os.path.abspath(TEMP_DIR) if not os.path.isdir(folder_path_temp): os.mkdir(folder_path_temp) # Download latest assets if args.download_assets: download_latest_assets() # Download latest release if args.download_release: download_release_assets(repo) if args.compare: compare() if args.release: create_release(repo) def get_release_download_path(): return os.path.join(os.path.abspath(TEMP_DIR), LATEST_RELASE_DIR) def get_galag_download_path(): return os.path.abspath(os.path.join(os.path.abspath(TEMP_DIR), QALAG_OUTPUT_DIR)) # Download latest Github release def download_release_assets(repo): try: release = repo.get_latest_release() except Exception: print("No published releases found, skipping download") return print("Downloading release: '%s'" % (release.title)) download_release_path = get_release_download_path() if os.path.isdir(download_release_path): shutil.rmtree(download_release_path) os.mkdir(download_release_path) assets = release.get_assets() for asset in assets: print("\tAsset: %s [%s]" % (asset.name, asset.url)) r = requests.get( asset.url, allow_redirects=True, headers={"Accept": "application/octet-stream"}, ) file_path = os.path.join(download_release_path, asset.name) open(file_path, "wb").write(r.content) # Extract quest icons iconpack_quest_release_zip = os.path.join(download_release_path, ICONPACK_QUEST) iconpack_quest_release_ext_path = os.path.join(download_release_path, QUEST_DIR) with zipfile.ZipFile(iconpack_quest_release_zip) as zip: zip.extractall(iconpack_quest_release_ext_path) # Download latest assets def download_latest_assets(): galag_output_dir = get_galag_download_path() if os.path.isdir(galag_output_dir): shutil.rmtree(galag_output_dir) os.mkdir(galag_output_dir) QuestAssetDL( platform="quest", locale="en_US", action="assets", output_dir_param=galag_output_dir, ) # Rename file for f in os.listdir(galag_output_dir): if f.startswith("appnames_quest") and f.endswith(".json"): os.rename( os.path.join(galag_output_dir, f), os.path.join(galag_output_dir, APPNAMES_QUEST), ) break else: raise FileNotFoundError("No appnames_quest JSON found") # Compare def compare(): # Check if we have a release to compare against release_path = get_release_download_path() if not os.path.isdir(release_path) or not os.path.exists( os.path.join(release_path, ICONPACK_QUEST) ): print("No previous release assets found, skipping comparison") return # Proceed with comparison iconpack_quest_release_ext_path = os.path.join(release_path, QUEST_DIR) iconpack_quest_generated_ext_path = os.path.join( get_galag_download_path(), QUEST_DIR ) launch_executable( ["-t", iconpack_quest_release_ext_path, iconpack_quest_generated_ext_path], bin_path=WINDIFF_EXE_PATH, ) appnames_quest_release_path = os.path.join(release_path, APPNAMES_QUEST) appnames_quest_generated_path = os.path.join( get_galag_download_path(), APPNAMES_QUEST ) launch_executable( [appnames_quest_release_path, appnames_quest_generated_path], bin_path=WINDIFF_EXE_PATH, ) # Create release def create_release(repo): tag = "v" + datetime.date.today().strftime("%m.%d.%Y") name = tag + ": Update quest assets" print(f"Creating release: tag: {tag}, name: {name}") release = repo.create_git_release( tag=tag, name=name, message="Updating quest assets", draft=True ) # Paths for "other" assets (from previous release) other_appnames = os.path.join(get_release_download_path(), APPNAMES_OTHER) other_iconpack = os.path.join(get_release_download_path(), ICONPACK_OTHER) # Upload other assets only if they exist (first release will skip) if os.path.exists(other_appnames): release.upload_asset(other_appnames) print(f"Uploaded {APPNAMES_OTHER}") else: print(f"Warning: {APPNAMES_OTHER} not found, skipping upload") if os.path.exists(other_iconpack): release.upload_asset(other_iconpack) print(f"Uploaded {ICONPACK_OTHER}") else: print(f"Warning: {ICONPACK_OTHER} not found, skipping upload") # Quest assets are always uploaded from the freshly downloaded data quest_appnames = os.path.join(get_galag_download_path(), APPNAMES_QUEST) quest_iconpack = os.path.join(get_galag_download_path(), ICONPACK_QUEST) # Ensure these files exist (they should, from download_latest_assets) if os.path.exists(quest_appnames): release.upload_asset(quest_appnames) else: raise FileNotFoundError(f"Missing generated Quest appnames: {quest_appnames}") if os.path.exists(quest_iconpack): release.upload_asset(quest_iconpack) else: raise FileNotFoundError(f"Missing generated Quest iconpack: {quest_iconpack}") # Launch executable and return output def launch_executable(args, bin_path): try: output = subprocess.check_output([bin_path] + args, stderr=subprocess.STDOUT) return output.decode(sys.stdout.encoding) except subprocess.CalledProcessError as e: raise Exception( str.format( str(e) + ". Output: '%s'" % (e.output.decode(sys.stdout.encoding).rstrip()) ) ) from e except Exception as e: raise type(e)(str.format(str(e) + " when calling '%s'" % (bin_path))) main()