diff --git a/reaper/cogs/log_reading.py b/reaper/cogs/log_reading.py index 419856e..2f426df 100644 --- a/reaper/cogs/log_reading.py +++ b/reaper/cogs/log_reading.py @@ -22,11 +22,12 @@ async def version_check(): if gh_api_response.status_code == 200: gh_data = gh_api_response.json() else: - logger.warn( + logger.error( f"Error code when retrieving GitHub API: {gh_api_response.status_code}" ) + return None except requests.exceptions.RequestException as err: - logger.warn(f"GitHub API request failed: {err}") + logger.error(f"GitHub API request failed: {err}") return None ns_current_version = gh_data["name"][1:] @@ -34,76 +35,73 @@ async def version_check(): return ns_current_version +class Mod: + def __init__( + self, + name: str, + version: str, + core: bool = False, + enabled: bool = True, + blacklisted: bool = False, + ): + self.name = name + self.version = version + self.core = core + self.enabled = enabled + self.blacklisted = blacklisted + + def __str__(self): + if self.version: + if self.blacklisted: + return f"{self.name} - {self.version} - Blacklisted" + elif self.core: + return f"{self.name} - {self.version} - Core" + else: + return f"{self.name} - {self.version}" + return self.name + + class LogButtons(discord.ui.View): - def __init__(self, mods_list, mods_list_disabled): + def __init__(self, mods): super().__init__() - self.mods_list = mods_list - self.mods_list_disabled = mods_list_disabled + self.mods = mods - # note: disabled mods still appear in enabled list. dunno why @discord.ui.button(label="List of enabled mods", style=discord.ButtonStyle.success) async def mod_list( self, interaction: discord.Interaction, button: discord.ui.Button ): mod_string = "" - for mod in self.mods_list: - if mod + "\n" in self.mods_list_disabled: - continue - else: - mod_string = mod_string + "- " + mod + for mod in self.mods: + if mod.enabled: + mod_string += str(mod) + "\n" - await interaction.response.send_message( - f"The user has the following mods enabled: \n{mod_string}", ephemeral=True - ) + if len(mod_string) > 2000: # Discord message limit + mod_string = mod_string[:1997] + "..." + + await interaction.response.send_message(mod_string, ephemeral=True) @discord.ui.button(label="List of disabled mods", style=discord.ButtonStyle.red) async def mod_list_disabled( self, interaction: discord.Interaction, button: discord.ui.Button ): - mod_string_disabled = "" - for mod in self.mods_list_disabled: - mod_string_disabled = mod_string_disabled + "- " + mod + mod_string = "" + for mod in self.mods: + if not mod.enabled: + mod_string += str(mod) + "\n" - await interaction.response.send_message( - f"The user has the following mods disabled: \n{mod_string_disabled}", - ephemeral=True, - ) + if len(mod_string) > 2000: # Discord message limit + mod_string = mod_string[:1997] + "..." + + await interaction.response.send_message(mod_string, ephemeral=True) class LogReading(commands.Cog): def __init__(self, bot: commands.Bot) -> None: self.bot = bot - intents = discord.Intents.default() - intents.messages = True - intents.message_content = True - @commands.Cog.listener() async def on_message(self, message): - hud = False - callback = False - compile_error_client_kill_callback = False - problem_found = False - audio_problem = False - rgb_error = False - framework_error = False - bad_mod = "" - better_server_browser = False - old_version = False - disabled_core_mod = False - double_barrel_crash = False - mod_problem = False - invalid_value = False - script_error = [] - crash_counter = 0 - filename = None - audio_list = [] - mods_list = [] - mods_list_disabled = [] - mods_disabled_core = [] - log_file = io.BytesIO() - allowed_channels = util.json_handler.load_allowed_channels() if message.author.bot: return @@ -128,387 +126,244 @@ async def on_message(self, message): logger.info("Found a log!") + log_file = io.BytesIO() await message.attachments[0].save(log_file) log_file.seek(0) lines = [] for line in log_file: lines.append(line.decode("utf-8").strip()) - # We need to do this because there are circumstances where we want to read lines that came before the line we are reading now - # And this is the easiest way to do it - - for i, line in enumerate(lines): - if "NorthstarLauncher version:" in line: - - ver_split = line.split("version:")[1] - current_version = await version_check() - if ver_split.strip() == "0.0.0.1+dev": - return - elif ( - ver_split.strip() < current_version - ): # apparently this works? I'm shocked - dm_log.add_field( - name="", - value=f"Version: {ver_split.strip()}", - inline=False, - ) - problem_found = True - old_version = True - - # Check mods + log = "\n".join(lines) - elif "(DISABLED)" in line: - if ( - "Northstar.Client" in line - or "Northtar.Custom" in line - or "Northstar.CustomServers" in line - ): - disabled_core_mod = True - mods_disabled_core.append(line.split("'")[1]) - logger.info(line.split("'")[1]) - mods_list_disabled.append(line.split("'")[1]) + mods = [] - elif "blacklisted mod" in line: - mods_list_disabled.append(line.split('"')[1] + " (blacklisted)\n") - - elif "Loading mod" in line: - - if "R2Northstar" in line: - continue - else: - for mod in mods_list_disabled: - if mod + "\n" in line: - continue - else: - mods_list.append(line.split("Loading mod")[1]) - - # Check if HUD Revamp is installed: conflicts with Client Kill callback - if "HUD Revamp" in line: - dm_log.add_field( - name="", - value="HUD Revamp: True", - inline=False, - ) - logger.info("I found HUD Revamp!") - hud = True - - # Check if Client Kill callback is installed: conflicts with HUD Revamp - elif "ClientKillCallback" in line: - dm_log.add_field( - name="", - value="Client Kill Callback: True", - inline=False, - ) - logger.info("I found Client Kill Callback!") - problem_found = True - callback = True - - # Check if the OLD, merged better server browser is loading. It's broken now and causes issues - elif "Better.Serverbrowser" in line: - dm_log.add_field( - name="", - value="Better.Serverbrowser: True", - inline=False, - ) - logger.info("I found better server browser!") - problem_found = True - better_server_browser = True - - # Check for a compile error for missing Client Kill callback as a dependency, or when there's a conflict with it - elif 'COMPILE ERROR expected ",", found identifier "inputParams"' in line: - dm_log.add_field( - name="", - value="Client kill callback compile error: True", - inline=False, - ) - logger.info("I found a compile error!") - problem_found = True - compile_error_client_kill_callback = True - - elif 'COMPILE ERROR Undefined variable "ModSettings_AddDropdown"' in line: - dm_log.add_field( - name="", - value="Missing negativbild: True", - inline=False, - ) - logger.info("I found a person missing negativbild!") - problem_found = True - rgb_error = True - - elif 'COMPILE ERROR Undefined variable "NS_InternalLoadFile"' in line: - dm_log.add_field( - name="", - value="Titan Framework issue: True", - inline=False, - ) - logger.info("I found a titan framework issue >:(") - problem_found = True - framework_error = True - - elif "SCRIPT ERROR" in line: - dm_log.add_field( - name="", - value="SCRIPT ERROR: True", + northstar_match = re.search(r"NorthstarLauncher version: \s*(.*)", log) + if northstar_match: + version_number = northstar_match.group(1).strip()[:-2] + latest_version = await version_check() + if version_number == "0.0.0.1+dev": + return + if latest_version.strip() > version_number: + problem.add_field( + name="Outdated Northstar Version", + value=f"It seems that you're running an older version of Northstar ({version_number}). Updating may not solve your issue, but you should do it anyway. The current version is {latest_version}. Please update by using one of the methods in the [installation channel](https://discord.com/channels/920776187884732556/922662496588943430).\n\nIf you've already updated and are still seeing this, please check if you have a file called `Northstar.dll` in `Titanfall2/R2Northstar`. If you do, delete it, and try launching again.", inline=False, ) - logger.info("I found a script error!") - problem_found = True - j = i - script_read_failsafe = 15 - while lines[j] != "" and j < i + script_read_failsafe: - # script traceback ends with a blank line, 15 is just a failsafe - script_error.append(lines[j]) - logger.info(lines[j]) - j += 1 - - elif ( - 'Failed reading masterserver authentication response: encountered parse error "Invalid value."' - in line - ): dm_log.add_field( - name="", - value="Cloudflare issue: True", + name="Outdated Northstar Version", + value=f"Their version: {version_number}", inline=False, ) - logger.info("I found a CloudFlare issue >:(") - problem_found = True - invalid_value = True - - # Check for audio replacements being loaded - # If 2 seperate mods replacing the same audio are enabled at the same time the game fucking kills itself - elif "Finished async read of audio sample" in line: - - if "packages" in line: - # Split the string after "R2Northstar/mods" to keep the folder name onwards - a = line.split(r"R2Northstar\packages")[1] - if r"R2Northstar\mods" in line: - a = line.split(r"R2Northstar\mods")[1] - # Split the previous split at "audio" to cleanly format as "FolderName, audioname" - # side note: why the fuck don't we use the mod name at all literally anywhere even when registering the audio fully - b = a.split("audio") - # Further clean up the last split - c = [item.split("\\")[1] for item in b] - - # Add these to the audio list for checking for errors - audio_list.append(c) - # Checks for first line of the crash section of the log - elif ( - "[NORTHSTAR] [error] Northstar has crashed! a minidump has been written and exception info is available below:" - in line - ): - # Stores the previous line (right before the crash), we have to skip the version printout - checkLine = lines[i - 2] - crash_counter += 1 - # More than 1 crash, flip multiCrash to true. Only needs to happen once so check for equality - # if crashCounter == 2: - # multiCrash = True - # Check for paks being loaded right before crash, only search if one crash - if "LoadStreamPak" in checkLine and crash_counter == 1: - mod_problem = True - # Use regex to grab the name of the pak that probably failed - match = re.search(r"LoadStreamPak: (\S+)", checkLine) - bad_stream_pak_load = str(match.group(1)) - problem_found = True - - elif f"registered starpak '{bad_stream_pak_load}'" in line: - - match = re.search( - r"Mod\s+(.*?)\s+registered", - line, - ) - bad_stream_pak_load = match.group( - 1 - ) # Store problematic mod in global var - - if bad_stream_pak_load == "Northstar.Custom": - double_barrel_crash = True - # Properly set up the list for actual checking - d = list(set(tuple(audio) for audio in audio_list)) - - # Set up a list for checking duplicates - audio_duplicates_list = {} - - for item in d: - # Grab the audio replacement string (e.g. "player_killed_indicator") and add it to a list to check directly for conflicts - audio_duplicate = item[1] - # If the audio override already in the list, add the mod name to the list - if audio_duplicate in audio_duplicates_list: - audio_duplicates_list[audio_duplicate].append(item[0]) - else: - audio_duplicates_list[audio_duplicate] = [item[0]] - - for audio_duplicate, names in audio_duplicates_list.items(): - if len(names) > 1: - problem_found = True - logger.info( - f"Found duplicates of {audio_duplicate}: {', '.join(names)}" - ) - - if problem_found: - logger.info("Found problems in the log! Replying...") - - if disabled_core_mod: - disabled_core_string = "" - - if len(mods_disabled_core) > 1: - if len(mods_disabled_core) == 2: - for mod in mods_disabled_core: - disabled_core_string = disabled_core_string + mod + # Check if core mods disabled, mod checking + mod_matches = re.finditer( + r"'([^']*)' loaded successfully, version (\d+\.\d+\.\d+).*?(DISABLED)?", log + ) + disabled_core_mods = [] + if mod_matches: + for mod_match in mod_matches: + name = mod_match.group(1) + version = mod_match.group(2) + disabled = bool(mod_match.group(3)) + core = False + if name in [ + "Northstar.Client", + "Northstar.Custom", + "Northstar.CustomServers", + ]: + core = True + if disabled and core: + disabled_core_mods.append(name) + mods.append(Mod(name, version, core=core, enabled=not disabled)) + # TODO: blacklisted mods, might not be worth it since its like never used + + if len(disabled_core_mods) > 0: + problem.add_field( + name="Disabled core mods:", + value=f"The core mods {', '.join(disabled_core_mods)} are disabled! Please re-enable them using a mod manager or by deleting `Titanfall2/R2Northstar/enabledmods.json` (this only applies if trying to play Northstar. If you are playing vanilla via Northstar and encountering an issue, this does not apply)", + inline=False, + ) + dm_log.add_field( + name="Goober turned off core mods", + value=f"Core mods that are disabled: {', '.join(disabled_core_mods)}", + inline=False, + ) + # Compile errors + compile_search = re.search(r"COMPILE ERROR\s*(.*)", log) + if compile_search: + details = compile_search.group(1) + if 'expected ",", found indentifier "inputParams"' in details: + hud = False + callback = False + # its ok to check the mods now because all of the mods will have been loaded at this point + for mod in mods: + if mod.name == "HUD Revamp": + hud = True + if mod.name == "ClientKillCallback": + callback = True + if hud and callback: # TODO: is this still true? problem.add_field( - name="Disabled core mods", - value=f"The core mods {disabled_core_string} are disabled! Please re-enable them using a mod manager or by deleting `Titanfall2/R2Northstar/enabledmods.json` (this only applies if trying to play Northstar. If you are playing vanilla via Northstar and encountering an issue, it is something else)", + name="Mod Incompatibility", + value="I noticed you have both HUD Revamp and Client Kill Callback installed. Currently, these two mods create conflicts. The easiest way to solve this is to delete/disable HUD Revamp.", inline=False, ) - - elif len(mods_disabled_core) == 1: - for mod in mods_disabled_core: - disabled_core_string = disabled_core_string + mod - problem.add_field( - name="Disabled core mod", - value=f"The core mod {disabled_core_string} is disabled! Please re-enable it using a mod manager or by deleting `Titanfall2/R2Northstar/enabledmods.json` (this only applies if trying to play Northstar. If you are playing vanilla via Northstar and encountering an issue, it is something else)", + dm_log.add_field( + name="Mod Incompatibility", + value="HUD Revamp", inline=False, ) - - if hud and callback and compile_error_client_kill_callback: - problem.add_field( - name="", - value="I noticed you have both HUD Revamp and Client Kill Callback installed. Currently, these two mods create conflicts. The easiest way to solve this is to delete/disable HUD Revamp.", - inline=False, - ) - - else: - - if compile_error_client_kill_callback: + else: problem.add_field( name="Missing dependency!", value="One or more mods you have may require the mod [Client killcallback](https://northstar.thunderstore.io/package/S2Mods/KraberPrimrose/) to work. Please install or update the mod via a mod manager or Thunderstore.", inline=False, ) - - if rgb_error: + dm_log.add_field( + name="Missing dependency!", + value="Client killcallback", + inline=False, + ) + elif 'Undefined variable "ModSettings_AddDropdown"' in details: problem.add_field( name="Missing dependency!", value="One or more mods you have may require the mod [Negativbild](https://northstar.thunderstore.io/package/odds/Negativbild/) to work. Please install or update this mod via a mod manager or Thundersore", - ) - - if framework_error: - problem.add_field( - name="Titan Framework", - value="Currently, Titan Framework expects a work in progress Northstar feature to function. As such, having it installed will cause issues (temporarily, until the feature is implemented), which uninstalling it will fix. You can temporarily make it work by manually installing the mod by moving the plugins inside the `plugins` folder of the mod into `r2northstar/plugins`, however this is a TEMPORARY fix, and you'll have to undo it when Northstar gets its next update.", inline=False, ) - - if invalid_value: - problem.add_field( - name="Server setup error", - value="If you are trying to setup a server, you likely made an error while setting it up. Please double check your port forwarding and try again.", + dm_log.add_field( + name="Missing dependency!", + value="Negativbild", inline=False, ) - - if better_server_browser: - problem.add_field( - name="Better server browser", - value='There are two mods called better server browser. The one called "Better.Serverbrowser" causes issues when installed, as it was added to Northstar a while ago. Removing it should fix that specific issue.', - ) - - for audio_duplicate, names in audio_duplicates_list.items(): - if len(names) > 1: - audio_problem = True - problem.add_field( - name="Audio replacement conflict", - value=f"The following mods replace the same audio (`{audio_duplicate}`):\n {', '.join(names)}", - inline=False, - ) - dm_log.add_field( - name="Audio replacement conflict", - value=f"The following mods replace the same audio (`{audio_duplicate}`):\n {', '.join(names)}", - inline=False, - ) - - if old_version: + elif ( + 'COMPILE ERROR Undefined variable "NS_InternalLoadFile"' in details + ): # TODO: check if this is still needed problem.add_field( - name="Older Version", - value=f"It seems that you're running an older version of Northstar. Updating may not solve your issue, but you should do it anyway. The current version is {await version_check()}. Please update by using one of the methods in the [installation channel](https://discord.com/channels/920776187884732556/922662496588943430).", + name="Titan Framework", + value="Currently, Titan Framework expects a work in progress Northstar feature to function. As such, having it installed will cause issues (temporarily, until the feature is implemented), which uninstalling it will fix. You can temporarily make it work by manually installing the mod by moving the plugins inside the `plugins` folder of the mod into `r2northstar/plugins`, however this is a TEMPORARY fix, and you'll have to undo it when Northstar gets its next update.", inline=False, ) - problem.add_field( - name="\u200b", - value="If you've already updated and are still seeing this, please check if you have a file called `Northstar.dll` in `Titanfall2/R2Northstar`. If you do, delete it, and try launching again.", + dm_log.add_field( + name="Titan Framework", + value="", inline=False, ) - - if audio_problem: + else: problem.add_field( - name="Fixing audio replacement conflicts", - value="Please remove mods until only one of these audio mods are enabled. These names aren't perfect to what they are for the mod, however they are the file names for the mod, so you can just remove the folder matching the name from `Titanfall2/R2Northstar/mods`.", + name="Unknown compile error", + value=f"`{details}`\nOne or more of your mods are either broken, clashing, or incompatible. Try removing any mods you added recently or check if they require any dependencies. Please wait for a human to assist you further.", inline=False, ) - - if script_error: - logger.info("adding field") - problem.add_field( - name="Script Error", - value=f"The following script error was found: \n ```{'\n'.join(script_error)}```\nYou likely have an old or outdated mod. You can try to find it based on the files listed in this part of the log and delete it, or wait for a human to give you more information.", + dm_log.add_field( + name="Unknown compile error", + value=f"`{details}`", inline=False, ) + if re.match( + r'.*Failed reading masterserver authentication response: encountered parse error "Invalid value.".*', + log, + re.DOTALL, + ): - if mod_problem: - if double_barrel_crash: - - problem.add_field( - name="Mod crashing", - value="Northstar crashed right after loading the double barrel assets.\nPlease try deleting `w_shotgun_doublebarrel.mdl` from `Titanfall2/R2Northstar/mods/Northstar.Custom/mod/models/weapon/shotgun_doublebarrel`.\nDoing this will solve this specific crash, but will make you hold an error when trying to use the double barrel in game.", - ) - - else: + dm_log.add_field( + name="", + value="Cloudflare issue: True", + inline=False, + ) + problem.add_field( + name="Server setup error", + value="If you are trying to setup a server, you likely made an error while setting it up. Please double check your port forwarding and try again.", + inline=False, + ) + script_error_match = re.search(r"SCRIPT ERROR: (.*)", log) + if script_error_match: + details = script_error_match.group(1) + dm_log.add_field( + name="", + value="SCRIPT ERROR: True", + inline=False, + ) + problem.add_field( + name="Script Error", + value=f"`{details}`\nYou likely have a broken or incompatible mod, please wait for a human to assist you further.", + inline=False, + ) - problem.add_field( - name="Mod crashing", - value=f"Northstar crashed right after attempting to load as asset from the mod `{bad_mod}`. Please try removing/disabling this mod to see if this solves the issue.", - ) + # First case: Handles paths with 'packages' and 'mods' subfolders (without capturing audio_file) + audio_matches_case1 = re.findall( + r"Finished async read of audio sample R2Northstar\\packages\\([^\\]+)\\mods\\[^\\]+\\audio\\([^\\]+)", + log, + ) - dm_me = await self.bot.fetch_user(self.bot.owner_id) + # Second case: Handles paths with only 'mods' folder (without capturing audio_file) + audio_matches_case2 = re.findall( + r"Finished async read of audio sample R2Northstar\\mods\\([^\\]+)\\audio\\([^\\]+)", + log, + ) - view = LogButtons(mods_list, mods_list_disabled) + loaded_audio = {} + # Handling matches for the first case + for mod_folder, audio_folder in audio_matches_case1: + if audio_folder not in loaded_audio: + loaded_audio[audio_folder] = [] + if mod_folder not in loaded_audio[audio_folder]: + loaded_audio[audio_folder].append(mod_folder) + + # Handling matches for the second case + for mod_folder, audio_folder in audio_matches_case2: + if audio_folder not in loaded_audio: + loaded_audio[audio_folder] = [] + if mod_folder not in loaded_audio[audio_folder]: + loaded_audio[audio_folder].append(mod_folder) + + audio_conflicts = [] + for audio_folder, mod_folders in loaded_audio.items(): + if len(mod_folders) > 1: + audio_conflicts.append(audio_folder) + + if audio_conflicts: + + formatted_conflicts = "" + for audio_conflict in audio_conflicts: + formatted_conflicts += f"The following mods are replacing the same audio (`{audio_conflict}`):\n" + formatted_conflicts += ", ".join(loaded_audio[audio_conflict]) + "\n" - if len(problem.fields) > 0: - problem.add_field( - name="", - value="Please note that I am a bot and am still heavily being worked on. There is a chance that some or all of this information is incorrect, in which case I apologize.\nIf you still encounter issues after doing this, please send another log.", - inline=False, - ) - await message.channel.send(embed=problem, reference=message, view=view) - dm_log.add_field( - name="I found an issue in the log and replied!", - value=f"A link to their log can be found here: {message.jump_url}", - ) - await dm_me.send(embed=dm_log) - else: - await dm_me.send( - f"I failed to respond to a log! The log can be found here: {message.jump_url}" - ) - await message.channel.send("I failed to respond to the log properly!") + dm_log.add_field( + name="", + value="Duplicate audio found!", + inline=False, + ) + problem.add_field( + name="Duplicate audio", + value=f"{formatted_conflicts}\nThese mods have conflicting audio replacements. It's possible that this might be causing your crash.", + inline=False, + ) - dm_log.clear_fields() - audio_duplicates_list.clear() - audio_list.clear() - problem.clear_fields() + view = LogButtons(mods) + dm_me = await self.bot.fetch_user(self.bot.owner_id) - elif not problem_found: - await message.channel.send( - "I couldn't find a common issue from the log. Please wait for a human to assist you", - reference=message, + if problem.fields: + problem.add_field( + name="", + value="Please note that I am a bot and am still heavily being worked on. There is a chance that some or all of this information is incorrect, in which case I apologize.\nIf you still encounter issues after doing this, please send another log.", + inline=False, + ) + else: + problem.add_field( + name="No common issues found!", + value="While waiting for a human to help you, consider the following:\n- Have you recently added any mods?\n- Does this issue persist when you disable all of your mods?", ) - dm_me = await self.bot.fetch_user(self.bot.owner_id) + await message.reply(embed=problem, view=view) + if dm_log.fields: dm_log.add_field( - name="I didn't find any issues!", - value=f"Here's a log you could potentially train from: {message.jump_url}", + name="I found an issue in the log and replied!", + value=f"A link to their log can be found here: {message.jump_url}", ) - await dm_me.send(embed=dm_log) - dm_log.clear_fields() - - logger.info("I didn't find any problems in the log!") + else: + dm_log.add_field( + name="I didn't find any issues in a log!", + value=f"Here's a log you can train from: {message.jump_url}", + ) + await dm_me.send(embed=dm_log) async def setup(bot: commands.Bot) -> None: