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
+
+
+
+### 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: