diff --git a/README.md b/README.md index 3852287..b01a6a8 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,41 @@
-> [!NOTE] -> **New Project** -> -> This project is still in the making!!! I would appreciate if you gave this repo a star, that would mean a lot to me! +
+ +**All in one discord bot for helping with [SideStore](https://discord.gg/3DwCwpBHfv), [idevice](https://discord.gg/ZnNcrRT3M8), and more.** + +

+> [!NOTE] +> This bot is heavily customized and may not be a good starting point template wise. But if you wanna use this bot, or maybe add to your own server, download it by clicking the button below. + +### Download + +
+ + Download + +
+ +### Commands + +| Command group | Subcommands | +| --- | --- | +| owner | `sync`, `cog_management`, `shutdown`, `say`, `invite` | +| general | `help`, `botinfo`, `serverinfo`, `ping`, `feedback`, `uptime` | +| fun | `randomfact`, `coinflip`, `rps`, `8ball`, `minesweeper` | +| moderation | `kick`, `ban`, `nick`, `purge`, `hackban`, `warnings`, `archive` | +| sidestore | `sidestore`, `refresh`, `code`, `crash`, `pairing`, `server`, `half`, `sparse`, `afc`, `udid` | +| idevice | `idevice`, `noapps`, `errorcode` | +| miscellaneous | `keanu`, `labubu` | + + +### Contributing +Contributions are welcome. See `CONTRIBUTING.md` for guidelines. + ## License Dual licensed project, see [LICENSE](LICENSE) file for detailed attribution information diff --git a/TODO.md b/TODO.md index fca1846..12b5b9a 100644 --- a/TODO.md +++ b/TODO.md @@ -1,18 +1,18 @@ # Todo List for Syntrel -[ ] Finish [idevice commands](https://github.com/jkcoxson/idevice/blob/master/idevice/src/lib.rs#L522) -> [X] Add /idevice command -> -> [X] Add /no apps command -> -> [ ] Add rest of the errors yikes +- [x] Finish [idevice commands](https://github.com/jkcoxson/idevice/blob/master/idevice/src/lib.rs#L522) + - [x] Add /idevice command + - [x] Add /no apps command + - [x] Add rest of the errors yikes -[ ] Add unit tests +- [ ] Add unit tests -[ ] Add documentation to readme +- [ ] Add documentation to readme -[ ] Clean tag system from [tags branch](https://github.com/neoarz/Syntrel/tree/tags) (make sure db is persistent) +- [ ] Finish CONTRIBUTING.md -[X] Fix logging and add graceful shutdown +- [ ] Clean tag system from [tags branch](https://github.com/neoarz/Syntrel/tree/tags) (make sure db is persistent) -[X] Add uptime checker for the bot iself +- [x] Fix logging and add graceful shutdown + +- [x] Add uptime checker for the bot iself diff --git a/assets/download.png b/assets/download.png new file mode 100644 index 0000000..cdc7132 Binary files /dev/null and b/assets/download.png differ diff --git a/cogs/idevice/error_codes.py b/cogs/idevice/error_codes.py new file mode 100644 index 0000000..e41b08f --- /dev/null +++ b/cogs/idevice/error_codes.py @@ -0,0 +1,74 @@ +import json +import os +import discord +from discord import app_commands +from discord.ext import commands + + +class ErrorCodes(commands.Cog, name="errorcodes"): + def __init__(self, bot) -> None: + self.bot = bot + self.errors = self.load_errors() + self.key_to_data = {error['name']: (error['description'], error['code']) for error in self.errors} + self.code_to_key = {error['code']: error['name'] for error in self.errors} + + def load_errors(self): + json_path = os.path.join(os.path.dirname(__file__), 'errorcodes.json') + try: + with open(json_path, 'r', encoding='utf-8') as f: + return json.load(f) + except FileNotFoundError: + self.bot.logger.error(f"Error codes JSON file not found: {json_path}") + return [] + except json.JSONDecodeError as e: + self.bot.logger.error(f"Error parsing error codes JSON: {e}") + return [] + + async def errorcode_autocomplete(self, interaction: discord.Interaction, current: str): + current_lower = current.lower() + items = [] + for key, (title, code) in self.key_to_data.items(): + if not current or current_lower in key.lower() or current_lower in title.lower() or current_lower in str(code): + items.append(app_commands.Choice(name=f"{key} ยป {title} ({code})", value=key)) + if len(items) >= 25: + break + return items + + @app_commands.command(name="errorcode", description="Look up an idevice error code by name or number") + @app_commands.describe(name="Start typing to search all error names and codes") + @app_commands.autocomplete(name=errorcode_autocomplete) + async def errorcode(self, interaction: discord.Interaction, name: str): + key = name + if key not in self.key_to_data: + try: + num = int(name) + key = self.code_to_key.get(num) + except ValueError: + key = None + if key is None or key not in self.key_to_data: + await interaction.response.send_message("Error not found.", ephemeral=True) + return + + title, code = self.key_to_data[key] + + embed = discord.Embed( + description=f"## Error Code: {code}\n\n**Name**: `{key}`\n**Description**: {title}", + color=0xfa8c4a, + ) + embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") + + view = discord.ui.View() + view.add_item(discord.ui.Button( + label="Edit Command", + style=discord.ButtonStyle.secondary, + url="https://github.com/neoarz/Syntrel/blob/main/cogs/idevice/error_codes.py", + emoji="<:githubicon:1417717356846776340>" + )) + + await interaction.response.send_message(embed=embed, view=view) + +async def setup(bot) -> None: + cog = ErrorCodes(bot) + await bot.add_cog(cog) + + diff --git a/cogs/idevice/errorcodes.json b/cogs/idevice/errorcodes.json new file mode 100644 index 0000000..afeb44f --- /dev/null +++ b/cogs/idevice/errorcodes.json @@ -0,0 +1,332 @@ +[ + { + "name": "socket", + "description": "device socket io failed", + "code": -1 + }, + { + "name": "pem_parse_failed", + "description": "PEM parse failed", + "code": -2 + }, + { + "name": "rustls", + "description": "TLS error", + "code": -3 + }, + { + "name": "tls_builder_failed", + "description": "TLS verifiction build failed", + "code": -4 + }, + { + "name": "plist", + "description": "io on plist", + "code": -5 + }, + { + "name": "utf8", + "description": "can't convert bytes to utf8", + "code": -6 + }, + { + "name": "unexpected_response", + "description": "unexpected response from device", + "code": -7 + }, + { + "name": "get_prohibited", + "description": "this request was prohibited", + "code": -8 + }, + { + "name": "session_inactive", + "description": "no SSL session is active", + "code": -9 + }, + { + "name": "invalid_host_id", + "description": "device does not have pairing file", + "code": -10 + }, + { + "name": "no_established_connection", + "description": "no established connection", + "code": -11 + }, + { + "name": "heartbeat_sleepy_time", + "description": "device went to sleep", + "code": -12 + }, + { + "name": "heartbeat_timeout", + "description": "heartbeat timeout", + "code": -13 + }, + { + "name": "not_found", + "description": "not found", + "code": -14 + }, + { + "name": "service_not_found", + "description": "service not found", + "code": -15 + }, + { + "name": "cdtunnel_packet_too_short", + "description": "CDTunnel packet too short", + "code": -16 + }, + { + "name": "cdtunnel_packet_invalid_magic", + "description": "CDTunnel packet invalid magic", + "code": -17 + }, + { + "name": "packet_size_mismatch", + "description": "Proclaimed packet size does not match actual size", + "code": -18 + }, + { + "name": "json", + "description": "JSON serialization failed", + "code": -19 + }, + { + "name": "device_not_found", + "description": "device not found", + "code": -20 + }, + { + "name": "device_locked", + "description": "device lockded", + "code": -21 + }, + { + "name": "usb_connection_refused", + "description": "device refused connection", + "code": -22 + }, + { + "name": "usb_bad_command", + "description": "bad command", + "code": -23 + }, + { + "name": "usb_bad_device", + "description": "bad device", + "code": -24 + }, + { + "name": "usb_bad_version", + "description": "usb bad version", + "code": -25 + }, + { + "name": "bad_build_manifest", + "description": "bad build manifest", + "code": -26 + }, + { + "name": "image_not_mounted", + "description": "image not mounted", + "code": -27 + }, + { + "name": "pairing_dialog_response_pending", + "description": "pairing trust dialog pending", + "code": -28 + }, + { + "name": "user_denied_pairing", + "description": "user denied pairing trust", + "code": -29 + }, + { + "name": "password_protected", + "description": "device is locked", + "code": -30 + }, + { + "name": "misagent_failure", + "description": "misagent operation failed", + "code": -31 + }, + { + "name": "installation_proxy_operation_failed", + "description": "installation proxy operation failed", + "code": -32 + }, + { + "name": "afc", + "description": "afc error", + "code": -33 + }, + { + "name": "unknown_afc_opcode", + "description": "unknown afc opcode", + "code": -34 + }, + { + "name": "invalid_afc_magic", + "description": "invalid afc magic", + "code": -35 + }, + { + "name": "afc_missing_attribute", + "description": "missing file attribute", + "code": -36 + }, + { + "name": "crash_report_mover_bad_response", + "description": "crash report mover sent the wrong response", + "code": -37 + }, + { + "name": "reqwest", + "description": "http reqwest error", + "code": -38 + }, + { + "name": "internal_error", + "description": "internal error", + "code": -39 + }, + { + "name": "unknown_frame", + "description": "unknown http frame type", + "code": -40 + }, + { + "name": "unknown_http_setting", + "description": "unknown http setting type", + "code": -41 + }, + { + "name": "uninitialized_stream_id", + "description": "Unintialized stream ID", + "code": -42 + }, + { + "name": "unknown_xpc_type", + "description": "unknown XPC type", + "code": -43 + }, + { + "name": "malformed_xpc", + "description": "malformed XPC message", + "code": -44 + }, + { + "name": "invalid_xpc_magic", + "description": "invalid XPC magic", + "code": -45 + }, + { + "name": "unexpected_xpc_version", + "description": "unexpected XPC version", + "code": -46 + }, + { + "name": "invalid_c_string", + "description": "invalid C string", + "code": -47 + }, + { + "name": "http_stream_reset", + "description": "stream reset", + "code": -48 + }, + { + "name": "http_go_away", + "description": "go away packet received", + "code": -49 + }, + { + "name": "ns_keyed_archive_error", + "description": "NSKeyedArchive error", + "code": -50 + }, + { + "name": "unknown_aux_value_type", + "description": "Unknown aux value type", + "code": -51 + }, + { + "name": "unknown_channel", + "description": "unknown channel", + "code": -52 + }, + { + "name": "addr_parse_error", + "description": "cannot parse string as IpAddr", + "code": -53 + }, + { + "name": "disable_memory_limit_failed", + "description": "disable memory limit failed", + "code": -54 + }, + { + "name": "not_enough_bytes", + "description": "not enough bytes", + "code": -55 + }, + { + "name": "utf8_error", + "description": "failed to parse bytes as valid utf8", + "code": -56 + }, + { + "name": "invalid_argument", + "description": "invalid argument passed", + "code": -57 + }, + { + "name": "unknown_error_type", + "description": "unknown error returned from device", + "code": -59 + }, + { + "name": "ffi_invalid_arg", + "description": "invalid arguments were passed", + "code": -60 + }, + { + "name": "ffi_invalid_string", + "description": "invalid string was passed", + "code": -61 + }, + { + "name": "ffi_buffer_too_small", + "description": "buffer passed is too small", + "code": -62 + }, + { + "name": "unsupported_watch_key", + "description": "unsupported watch key", + "code": -63 + }, + { + "name": "malformed_command", + "description": "malformed command", + "code": -64 + }, + { + "name": "integer_overflow", + "description": "integer overflow", + "code": -65 + }, + { + "name": "canceled_by_user", + "description": "canceled by user", + "code": -66 + }, + { + "name": "malformed_package_archive", + "description": "malformed package archive", + "code": -67 + } +] \ No newline at end of file diff --git a/cogs/idevice/idevice.py b/cogs/idevice/idevice.py index f204dee..bc05afe 100644 --- a/cogs/idevice/idevice.py +++ b/cogs/idevice/idevice.py @@ -2,6 +2,142 @@ import discord from discord import app_commands from discord.ext import commands from discord.ext.commands import Context +import json +import os +import math + + +def load_error_codes(): + try: + json_path = os.path.join(os.path.dirname(__file__), 'errorcodes.json') + with open(json_path, 'r', encoding='utf-8') as f: + return json.load(f) + except FileNotFoundError: + return [] + except json.JSONDecodeError: + return [] + + +class ErrorCodesBrowserView(discord.ui.View): + def __init__(self, items_per_page=9): + super().__init__(timeout=300) + self.error_codes = load_error_codes() + self.items_per_page = items_per_page + self.current_page = 0 + self.max_pages = math.ceil(len(self.error_codes) / items_per_page) if self.error_codes else 1 + self.update_buttons() + + def update_buttons(self): + self.clear_items() + + first_button = discord.ui.Button( + emoji="<:leftmax:1420240325770870905>", + style=discord.ButtonStyle.primary, + disabled=(self.current_page == 0) + ) + first_button.callback = self.first_page + self.add_item(first_button) + + prev_button = discord.ui.Button( + emoji="<:left:1420240344926126090>", + style=discord.ButtonStyle.primary, + disabled=(self.current_page == 0) + ) + prev_button.callback = self.prev_page + self.add_item(prev_button) + + stop_button = discord.ui.Button( + emoji="<:middle:1420240356087173160>", + style=discord.ButtonStyle.secondary + ) + stop_button.callback = self.stop_interaction + self.add_item(stop_button) + + next_button = discord.ui.Button( + emoji="<:right:1420240334100627456>", + style=discord.ButtonStyle.primary, + disabled=(self.current_page >= self.max_pages - 1) + ) + next_button.callback = self.next_page + self.add_item(next_button) + + last_button = discord.ui.Button( + emoji="<:rightmax:1420240368846372886>", + style=discord.ButtonStyle.primary, + disabled=(self.current_page >= self.max_pages - 1) + ) + last_button.callback = self.last_page + self.add_item(last_button) + + def create_embed(self): + if not self.error_codes: + embed = discord.Embed( + title="Error Codes", + description="No error codes found.", + color=0xfa8c4a + ) + embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") + embed.set_footer(text="Page 0 of 0") + return embed + + embed = discord.Embed( + title="Error Codes", + color=0xfa8c4a + ) + embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") + + start_idx = self.current_page * self.items_per_page + end_idx = min(start_idx + self.items_per_page, len(self.error_codes)) + current_codes = self.error_codes[start_idx:end_idx] + + for i, error in enumerate(current_codes): + field_value = f"**Code:** `{error.get('code', 'N/A')}`\n**Name:** `{error.get('name', 'Unknown')}`\n**Description:** {error.get('description', 'No description')}" + embed.add_field( + name="\u200b", + value=field_value, + inline=True + ) + + # Add empty fields to maintain 3x3 grid if needed + while len(current_codes) % 3 != 0: + embed.add_field(name="\u200b", value="\u200b", inline=True) + + embed.set_footer(text=f"Page {self.current_page + 1} of {self.max_pages}") + + return embed + + async def first_page(self, interaction: discord.Interaction): + self.current_page = 0 + self.update_buttons() + embed = self.create_embed() + await interaction.response.edit_message(embed=embed, view=self) + + async def prev_page(self, interaction: discord.Interaction): + if self.current_page > 0: + self.current_page -= 1 + self.update_buttons() + embed = self.create_embed() + await interaction.response.edit_message(embed=embed, view=self) + + async def stop_interaction(self, interaction: discord.Interaction): + self.clear_items() + embed = self.create_embed() + embed.set_footer(text="Interaction stopped") + await interaction.response.edit_message(embed=embed, view=self) + self.stop() + + async def next_page(self, interaction: discord.Interaction): + if self.current_page < self.max_pages - 1: + self.current_page += 1 + self.update_buttons() + embed = self.create_embed() + await interaction.response.edit_message(embed=embed, view=self) + + async def last_page(self, interaction: discord.Interaction): + self.current_page = self.max_pages - 1 + self.update_buttons() + embed = self.create_embed() + await interaction.response.edit_message(embed=embed, view=self) class ideviceSelect(discord.ui.Select): @@ -12,12 +148,34 @@ class ideviceSelect(discord.ui.Select): label="No Apps", value="noapps", description="Help when apps aren't showing in installed apps view", - ) + ), + discord.SelectOption( + label="Error Codes", + value="errorcodes_ephemeral", + description="Browse idevice error codes", + ), ] super().__init__(placeholder="Choose an idevice command...", options=options) async def callback(self, interaction: discord.Interaction): command_name = self.values[0] + + if command_name == "errorcodes_ephemeral": + # Send success message first + embed = discord.Embed( + title="Command Executed", + description="Successfully executed `/errorcodes`", + color=0x00FF00 + ) + embed.set_author(name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png") + await interaction.response.edit_message(embed=embed, view=None) + + # Follow up with the error codes browser + view = ErrorCodesBrowserView() + embed = view.create_embed() + await interaction.followup.send(embed=embed, view=view, ephemeral=True) + return + command = self.bot.get_command(command_name) if command: