Merge pull request #3 from neoarz/readme+idevice

This commit is contained in:
neo
2025-09-23 23:34:12 -04:00
committed by GitHub
6 changed files with 608 additions and 16 deletions

View File

@@ -8,13 +8,41 @@
<br> <br>
> [!NOTE] <div align="center">
> **New Project**
> **All in one discord bot for helping with [SideStore](https://discord.gg/3DwCwpBHfv), [idevice](https://discord.gg/ZnNcrRT3M8), and more.**
> This project is still in the making!!! I would appreciate if you gave this repo a star, that would mean a lot to me!
</div>
<br> <br>
> [!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
<div align="left">
<a href="https://discord.com/oauth2/authorize?client_id=1376728824108286034" target="_blank" rel="noopener noreferrer">
<img src="assets/download.png" alt="Download" style="width: 300px; height: auto;">
</a>
</div>
### 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 ## License
Dual licensed project, see [LICENSE](LICENSE) file for detailed attribution information Dual licensed project, see [LICENSE](LICENSE) file for detailed attribution information

22
TODO.md
View File

@@ -1,18 +1,18 @@
# Todo List for Syntrel # Todo List for Syntrel
[ ] Finish [idevice commands](https://github.com/jkcoxson/idevice/blob/master/idevice/src/lib.rs#L522) - [x] Finish [idevice commands](https://github.com/jkcoxson/idevice/blob/master/idevice/src/lib.rs#L522)
> [X] Add /idevice command - [x] Add /idevice command
> - [x] Add /no apps command
> [X] Add /no apps command - [x] Add rest of the errors yikes
>
> [ ] 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

BIN
assets/download.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 985 KiB

View File

@@ -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)

View File

@@ -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
}
]

View File

@@ -2,6 +2,142 @@ import discord
from discord import app_commands from discord import app_commands
from discord.ext import commands from discord.ext import commands
from discord.ext.commands import Context 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): class ideviceSelect(discord.ui.Select):
@@ -12,12 +148,34 @@ class ideviceSelect(discord.ui.Select):
label="No Apps", label="No Apps",
value="noapps", value="noapps",
description="Help when apps aren't showing in installed apps view", 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) super().__init__(placeholder="Choose an idevice command...", options=options)
async def callback(self, interaction: discord.Interaction): async def callback(self, interaction: discord.Interaction):
command_name = self.values[0] 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) command = self.bot.get_command(command_name)
if command: if command: