11 Commits

Author SHA1 Message Date
neoarz
f6d3fc4bb4 fix: delay from 5 to 10 sec 2026-01-04 22:06:33 -05:00
neoarz
9c71bf42d7 feat: sidestore unofficial command 2026-01-04 21:54:50 -05:00
neoarz
aaefc42664 chore: update license 2026-01-01 08:52:37 -05:00
neoarz
e086dd4351 chore: bleh 2025-12-29 19:40:52 -05:00
neoarz
63e26bac74 fix: sidestore message and preview 2025-12-29 18:17:10 -05:00
neo
bc2cfb57d1 feat: sticky message (#29) 2025-12-29 17:56:33 -05:00
neoarz
6d61482216 chore: rm download 2025-12-10 13:19:43 -05:00
CelloSerenity
b7d010e44c feat: Update mountddi, replace the pairing commands, switch to localdevvpn
* Update mountddi.py

* Update pairing command description and embed content

* Update crash.py

* Update afc.py

* Update VPN app reference in error message

* Update footer text in embed message

* feat: update mount ddi to be downloaded on the fly

also fix some small errors and update errorcodes.json

---------

Co-authored-by: neoarz <email@neoarz.dev>
2025-12-07 18:08:09 -05:00
CelloSerenity
0d3fcb8146 fix: Update to iloader and fix broken links (#26)
* Update afc.py

* Update afc.py

* Update error message and documentation link for UDID

* Update pairing file instructions and documentation URL

* Update pairing file instructions in refresh.py

* Update sparse.py

* NOTE TO SELF: This can probably be removed altogether or modified drastically because we don’t use jitterbug anymore

* Update jit26.py
2025-11-27 14:14:49 -05:00
neoarz
23790c46b4 chore: ruff fix 2025-11-22 19:51:46 -05:00
neo
4a9b6b1f06 feat: livecontainer command group (#25)
* initial commit

* fix: image link

* feat: finish cellos command

* fix: interactive sounds wierd 😭
2025-11-22 14:43:31 -05:00
72 changed files with 754 additions and 745 deletions

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 neoarz
Copyright (c) 2026 neoarz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -31,5 +31,5 @@ restricted for use:
- Any image files, links, graphics, logos, or media files
- Any proprietary content or branding materials
These assets remain the exclusive property of neoarz and may not be used,
These assets remain the exclusive property of neoarz/the respective owner and may not be used,
distributed, or modified without explicit written permission.

View File

@@ -30,10 +30,10 @@
| sidestore | `help`, `refresh`, `code`, `crash`, `pairing`, `server`, `half`, `sparse`, `afc`, `udid` |
| idevice | `help`, `noapps`, `errorcode`, `developermode`, `mountddi` |
| melonx | `help`, `transfer`, `mods`, `gamecrash`, `requirements`, `error`, `26`, `legal` |
| events | `baitbot` |
| events | `baitbot`, `stickybot` |
| miscellaneous | `keanu`, `labubu`, `piracy`, `tryitandsee`, `rickroll`, `dontasktoask`, `support`, `depart`, `docs` `sigma`, `duck`, `silly`, `color` |
| utilities | `translate`, `codepreview`, `dictionary` |
| media | `download`, `mcquote`, `img2gif`, `tweety`, `tts` |
| media | `mcquote`, `img2gif`, `tweety`, `tts` |
## Download

View File

@@ -4,7 +4,6 @@ from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
from datetime import datetime
import time
import pytz
from utils.contributors import generate_contributors_image

View File

@@ -5,6 +5,11 @@ from discord.ext.commands import Context
from .baitbot import baitbot_command, BaitBotListener, has_protected_role
from .mention import MentionListener
from .stickybot import (
stickybot_command,
StickyBotListener,
has_allowed_role as has_sticky_role,
)
def _require_group_prefix(context: Context) -> bool:
@@ -35,7 +40,7 @@ class Events(commands.GroupCog, name="events"):
embed.set_author(
name="Events", icon_url="https://yes.nighty.works/raw/eW5lLm.webp"
)
embed.add_field(name="Available", value="baitbot", inline=False)
embed.add_field(name="Available", value="baitbot, stickybot", inline=False)
await context.send(embed=embed)
async def _invoke_hybrid(self, context: Context, name: str, **kwargs):
@@ -50,6 +55,11 @@ class Events(commands.GroupCog, name="events"):
async def events_group_baitbot(self, context: Context):
await self._invoke_hybrid(context, "baitbot")
@events_group.command(name="stickybot")
@has_sticky_role()
async def events_group_stickybot(self, context: Context):
await self._invoke_hybrid(context, "stickybot")
@commands.check(_require_group_prefix)
@has_protected_role()
@commands.hybrid_command(
@@ -58,6 +68,14 @@ class Events(commands.GroupCog, name="events"):
async def baitbot(self, context):
return await baitbot_command()(self, context)
@commands.check(_require_group_prefix)
@has_sticky_role()
@commands.hybrid_command(
name="stickybot", description="View sticky bot configuration and status."
)
async def stickybot(self, context):
return await stickybot_command()(self, context)
async def setup(bot) -> None:
cog = Events(bot)
@@ -69,5 +87,9 @@ async def setup(bot) -> None:
mention_listener = MentionListener(bot)
await bot.add_cog(mention_listener)
sticky_bot = StickyBotListener(bot)
await bot.add_cog(sticky_bot)
bot.logger.info("Loaded extension 'events.baitbot'")
bot.logger.info("Loaded extension 'events.mention'")
bot.logger.info("Loaded extension 'events.stickybot'")

View File

@@ -35,5 +35,3 @@ class MentionListener(commands.Cog):
self.bot.logger.debug(
f"Failed to react with fallback emoji: {fallback_error}"
)

312
cogs/events/stickybot.py Normal file
View File

@@ -0,0 +1,312 @@
import discord
from discord.ext import commands
from discord.ext.commands import Context
import asyncio
# Make a pr to add your own server config here, you shouldn't need to touch the rest of the file, please fill in all the values for your own server
STICKY_CONFIGS = {
"neotest": {
"guild_id": 1069946178659160076,
"channel_ids": [
1455338488546459789,
],
"allowed_role_id": 1432165329483857940,
"message": "# Example sticky message", # You can add your own markdown here
"footer": "This is an automated sticky message.", # This will be appended to the message and uses "-#" to format the footer
"delay": 10, # in seconds
},
"SideStore": {
"guild_id": 949183273383395328,
"channel_ids": [
1279548738586673202,
],
"allowed_role_id": 949207813815697479,
"message": "## Please read the README in https://discord.com/channels/949183273383395328/1155736594679083089 and the documentation at <https://docs.sidestore.io> before asking your question.",
"footer": "This is an automated sticky message.",
"delay": 10,
},
}
def has_allowed_role():
async def predicate(context: Context):
if not context.guild:
context.bot.logger.warning(
f"[STICKYBOT] Unauthorized stickybot command attempt by {context.author} ({context.author.id}) in DMs"
)
embed = discord.Embed(
title="Permission Denied",
description="You don't have permission to use this command.",
color=0xE02B2B,
)
embed.set_author(
name="Events", icon_url="https://yes.nighty.works/raw/C8Hh6o.png"
)
await context.send(embed=embed, ephemeral=True)
return False
if not hasattr(context.author, "roles"):
context.bot.logger.warning(
f"[STICKYBOT] Unauthorized stickybot command attempt by {context.author} ({context.author.id}) in {context.guild.name} - no roles"
)
embed = discord.Embed(
title="Permission Denied",
description="You don't have permission to use this command.",
color=0xE02B2B,
)
embed.set_author(
name="Events", icon_url="https://yes.nighty.works/raw/C8Hh6o.png"
)
await context.send(embed=embed, ephemeral=True)
return False
for config in STICKY_CONFIGS.values():
if context.guild.id != config.get("guild_id"):
continue
allowed_role_id = config.get("allowed_role_id")
if allowed_role_id:
allowed_role = context.guild.get_role(allowed_role_id)
if allowed_role:
for role in context.author.roles:
if (
role.position >= allowed_role.position
and role.id != context.guild.default_role.id
):
return True
context.bot.logger.warning(
f"[STICKYBOT] Unauthorized stickybot command attempt by {context.author} ({context.author.id}) in {context.guild.name} - insufficient role permissions"
)
embed = discord.Embed(
title="Permission Denied",
description="You don't have permission to use this command.",
color=0xE02B2B,
)
embed.set_author(
name="Events", icon_url="https://yes.nighty.works/raw/C8Hh6o.png"
)
await context.send(embed=embed, ephemeral=True)
return False
return commands.check(predicate)
def stickybot_command():
async def wrapper(self, context: Context):
embed = discord.Embed(
title="Sticky Bot",
description="Sends sticky messages in configured channels.",
color=0x7289DA,
)
embed.set_author(
name="Events", icon_url="https://yes.nighty.works/raw/C8Hh6o.png"
)
found_config = False
if STICKY_CONFIGS:
for name, config in STICKY_CONFIGS.items():
guild_id = config.get("guild_id")
if context.guild and guild_id == context.guild.id:
channel_ids = config.get("channel_ids", [])
channel_displays = []
for channel_id in channel_ids:
channel = context.guild.get_channel(channel_id)
channel_display = (
f"<#{channel_id}> (`{channel_id}`)"
if channel
else f"`{channel_id}`"
)
channel_displays.append(channel_display)
channels_text = (
"\n".join(channel_displays) if channel_displays else "Not set"
)
allowed_role_id = config.get("allowed_role_id", "Not set")
role = context.guild.get_role(allowed_role_id)
role_display = (
f"<@&{allowed_role_id}> (`{allowed_role_id}`)"
if role
else f"`{allowed_role_id}`"
)
message_content = config.get("message", "*No message set*")
footer_text = config.get(
"footer", "This is an automated sticky message."
)
full_content = f"{message_content}\n-# {footer_text}"
embed.add_field(
name="\u200b",
value=f"**Channels:**\n{channels_text}\n\n**Allowed Role:**\n{role_display}\n\n**Message Preview:**\n```\n{full_content}\n```",
inline=False,
)
found_config = True
if not found_config:
embed.add_field(
name="No Configurations",
value="No sticky configurations found for this server",
inline=False,
)
if context.guild and context.guild.icon:
embed.set_thumbnail(url=context.guild.icon.url)
await context.send(embed=embed)
return wrapper
class StickyBotListener(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.last_sticky_messages = {}
self.debounce_tasks = {}
async def delete_last_sticky(self, channel):
try:
active_config = None
for config in STICKY_CONFIGS.values():
if channel.guild.id == config.get(
"guild_id"
) and channel.id in config.get("channel_ids", []):
active_config = config
break
if not active_config:
return
target_footer = active_config.get(
"footer", "This is an automated sticky message."
)
async for message in channel.history(limit=20):
if (
message.author.id == self.bot.user.id
and target_footer in message.content
):
await message.delete()
except Exception as e:
self.bot.logger.warning(
f"[STICKYBOT] Error cleaning up sticky in #{channel.name}: {e}"
)
async def send_sticky_message(self, channel, config):
if not channel:
return
last_msg_id = self.last_sticky_messages.get(channel.id)
deleted = False
if last_msg_id:
try:
old_msg = await channel.fetch_message(last_msg_id)
await old_msg.delete()
deleted = True
except discord.NotFound:
deleted = True
except discord.Forbidden:
self.bot.logger.warning(
f"[STICKYBOT] Missing delete permissions in #{channel.name}"
)
except Exception as e:
self.bot.logger.warning(
f"[STICKYBOT] Error deleting info in #{channel.name}: {e}"
)
if not deleted:
await self.delete_last_sticky(channel)
message_content = config.get("message")
if not message_content:
return
footer_text = config.get("footer", "This is an automated sticky message.")
footer = f"\n-# {footer_text}"
full_content = f"{message_content}{footer}"
try:
new_msg = await channel.send(
full_content, allowed_mentions=discord.AllowedMentions.none()
)
self.last_sticky_messages[channel.id] = new_msg.id
except discord.Forbidden:
self.bot.logger.warning(
f"[STICKYBOT] Missing send permissions in #{channel.name}"
)
except Exception as e:
self.bot.logger.error(
f"[STICKYBOT] Error sending sticky in #{channel.name}: {e}"
)
@commands.Cog.listener()
async def on_ready(self):
await self.bot.wait_until_ready()
await self.initialize_stickies()
async def initialize_stickies(self):
for name, config in STICKY_CONFIGS.items():
guild_id = config.get("guild_id")
guild = self.bot.get_guild(guild_id)
if not guild:
continue
channel_ids = config.get("channel_ids", [])
for channel_id in channel_ids:
channel = guild.get_channel(channel_id)
if channel:
await self.send_sticky_message(channel, config)
async def trigger_sticky(self, channel, guild):
if not guild or not channel:
return
active_config = None
for config in STICKY_CONFIGS.values():
if guild.id == config.get("guild_id"):
if channel.id in config.get("channel_ids", []):
active_config = config
break
if not active_config:
return
channel_id = channel.id
if channel_id in self.debounce_tasks:
self.debounce_tasks[channel_id].cancel()
async def debounce_wrapper():
try:
delay = active_config.get("delay", 5)
await asyncio.sleep(delay)
await self.send_sticky_message(channel, active_config)
except asyncio.CancelledError:
pass
except Exception as e:
self.bot.logger.error(f"[STICKYBOT] Error in debounce task: {e}")
finally:
if self.debounce_tasks.get(channel_id) == asyncio.current_task():
del self.debounce_tasks[channel_id]
self.debounce_tasks[channel_id] = self.bot.loop.create_task(debounce_wrapper())
@commands.Cog.listener()
async def on_message(self, message: discord.Message):
if message.guild is None or message.author.bot:
return
if message.id == self.last_sticky_messages.get(message.channel.id):
return
await self.trigger_sticky(message.channel, message.guild)
@commands.Cog.listener()
async def on_interaction(self, interaction: discord.Interaction):
if interaction.guild is None or interaction.user.bot:
return
if interaction.type == discord.InteractionType.application_command:
await self.trigger_sticky(interaction.channel, interaction.guild)

View File

@@ -1,5 +1,4 @@
import random
from itertools import repeat
import discord
from discord.ext import commands
import asyncio
@@ -62,21 +61,21 @@ class RowButton(discord.ui.Button):
def checkpos(count, rawpos, pos):
pos = view.GetBoardPos(rawpos)
if not rawpos - 1 in self.bombs or pos == 0:
if rawpos - 1 not in self.bombs or pos == 0:
count.append(rawpos - 1)
if not rawpos + 1 in self.bombs or pos == 4:
if rawpos + 1 not in self.bombs or pos == 4:
count.append(rawpos + 1)
if not rawpos - 6 in self.bombs or pos == 0:
if rawpos - 6 not in self.bombs or pos == 0:
count.append(rawpos - 6)
if not rawpos - 4 in self.bombs or pos == 4:
if rawpos - 4 not in self.bombs or pos == 4:
count.append(rawpos - 4)
if not rawpos + 6 in self.bombs or pos == 4:
if rawpos + 6 not in self.bombs or pos == 4:
count.append(rawpos + 6)
if not rawpos + 4 in self.bombs or pos == 0:
if rawpos + 4 not in self.bombs or pos == 0:
count.append(rawpos + 4)
if not rawpos - 5 in self.bombs:
if rawpos - 5 not in self.bombs:
count.append(rawpos - 5)
if not rawpos + 5 in self.bombs:
if rawpos + 5 not in self.bombs:
count.append(rawpos + 5)
return count

View File

@@ -1,4 +1,3 @@
import aiohttp
import discord
from discord.ext import commands

View File

@@ -1,9 +1,8 @@
import discord
from discord import app_commands
from discord.ext import commands
class FeedbackForm(discord.ui.Modal, title="Feeedback"):
class FeedbackForm(discord.ui.Modal, title="Feedback"):
feedback = discord.ui.TextInput(
label="What do you think about this bot?",
style=discord.TextStyle.long,

View File

@@ -2,10 +2,8 @@ import discord
from discord.ext import commands
from discord import app_commands
import aiohttp
import asyncio
from io import BytesIO
from PIL import Image
import re
from datetime import datetime, timezone
ONE_MONTH = 2628000

View File

@@ -21,6 +21,7 @@ class Help(commands.Cog, name="help"):
"sidestore",
"idevice",
"melonx",
"livecontainer",
"media",
"miscellaneous",
"utilities",
@@ -51,6 +52,7 @@ class Help(commands.Cog, name="help"):
"fun": "fun",
"idevice": "idevice",
"melonx": "melonx",
"livecontainer": "livecontainer",
"media": "media",
"misc": "miscellaneous",
"miscellaneous": "miscellaneous",
@@ -77,6 +79,7 @@ class Help(commands.Cog, name="help"):
"sidestore": "SideStore troubleshooting commands",
"idevice": "idevice troubleshooting commands",
"melonx": "MeloNX troubleshooting commands",
"livecontainer": "LiveContainer troubleshooting commands",
"media": "Media commands",
"utilities": "Utility commands",
"miscellaneous": "Miscellaneous commands",

View File

@@ -31,7 +31,9 @@ class Idevice(commands.GroupCog, name="idevice"):
await context.send(embed=embed, view=view)
@idevice_group.command(name="errorcodes")
async def idevice_group_errorcodes(self, context: Context, *, error_code: str = None):
async def idevice_group_errorcodes(
self, context: Context, *, error_code: str = None
):
await self._invoke_hybrid(context, "errorcodes", error_code=error_code)
@idevice_group.command(name="developermode")

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def developermode_command():
@@ -24,7 +21,7 @@ def developermode_command():
embed.set_author(
name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png"
)
embed.set_footer(text=f"Last Edited by neoarz")
embed.set_footer(text="Last Edited by neoarz")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()

Binary file not shown.

View File

@@ -328,5 +328,10 @@
"name": "malformed_package_archive",
"description": "malformed package archive",
"code": -67
},
{
"name": "developer_mode_not_enabled",
"description": "Developer mode is not enabled",
"code": -68
}
]

View File

@@ -1,7 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import json
import os
import math

View File

@@ -1,7 +1,9 @@
import discord
from discord.ext import commands
from discord.ext.commands import Context
import os
import aiohttp
import shutil
import tempfile
def mountddi_command():
@@ -12,23 +14,27 @@ def mountddi_command():
embed = discord.Embed(
color=0xFA8C4A,
description=(
"# How to Manually Mount DDI\n\n---\n\n"
"# How to Mount your DDI (Developer Disk Image):\n\n---\n\n"
"1. Ensure you are connected to StikDebug's VPN and Wi-Fi.*\n"
"2. Force close StikDebug from the app switcher, then repon it.\n"
"## This should resolve your error! Remember, this must be done every time you restart your device.\n"
"If it doesn't work after a couple tries or you live in a country where github.com is blocked, try the steps below to manually mount the DDI:*\n"
"1. **Download the DDI.zip file attached above:**\n"
" - Save it to your device and extract the contents\n\n"
"2. **Replace the DDI folder in StikDebug:**\n"
" - Navigate to the StikDebug default directory on your iPhone/iPad\n"
" - Delete the existing DDI folder completely\n"
" - Replace it with the DDI folder from the downloaded zip\n"
" - Make sure it's in the StikDebug default directory\n\n"
" - Replace it with the DDI folder from uncompressing the downloaded zip\n"
" - Make sure it's in StikDebug's default directory\n\n"
"3. **Restart and retry:**\n"
" - Completely restart StikDebug\n"
" - See if you get the same error again\n\n"
" - If you still get the same error, ask the idevice server for more help\n\n"
),
)
embed.set_author(
name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png"
)
embed.set_footer(text="Last Edited by neoarz")
embed.set_footer(text="Last Edited by CelloSerenity")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()
@@ -41,16 +47,51 @@ def mountddi_command():
)
)
ddi_file_path = os.path.join(os.path.dirname(__file__), "files/DDI.zip")
file = (
discord.File(ddi_file_path, filename="DDI.zip")
if os.path.exists(ddi_file_path)
else None
)
temp_dir = tempfile.mkdtemp()
try:
ddi_dir = os.path.join(temp_dir, "DDI")
os.makedirs(ddi_dir)
if file:
await context.send(embed=embed, view=view, file=file)
else:
await context.send(embed=embed, view=view)
base_url = "https://raw.githubusercontent.com/doronz88/DeveloperDiskImage/main/PersonalizedImages/Xcode_iOS_DDI_Personalized"
files = ["BuildManifest.plist", "Image.dmg", "Image.dmg.trustcache"]
async with aiohttp.ClientSession() as session:
for filename in files:
file_url = f"{base_url}/{filename}"
async with session.get(file_url) as response:
if response.status != 200:
await context.send(
f"Error: Failed to download {filename} (Status: {response.status})"
)
return
file_path = os.path.join(ddi_dir, filename)
with open(file_path, "wb") as f:
while True:
chunk = await response.content.read(8192)
if not chunk:
break
f.write(chunk)
zip_base_name = os.path.join(temp_dir, "DDI")
shutil.make_archive(zip_base_name, "zip", root_dir=temp_dir, base_dir="DDI")
zip_file_path = zip_base_name + ".zip"
if os.path.exists(zip_file_path):
await context.send(
embed=embed,
view=view,
file=discord.File(zip_file_path, filename="DDI.zip"),
)
else:
await context.send(
"Error: Failed to create zip file.", embed=embed, view=view
)
except Exception as e:
await context.send(f"An error occurred: {e}")
finally:
shutil.rmtree(temp_dir)
return mountddi

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def noapps_command():
@@ -26,7 +23,7 @@ def noapps_command():
embed.set_author(
name="idevice", icon_url="https://yes.nighty.works/raw/snLMuO.png"
)
embed.set_footer(text=f"Last Edited by neoarz")
embed.set_footer(text="Last Edited by neoarz")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()

View File

@@ -0,0 +1,93 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
from .livecontainer import LivecontainerView
from .jit26 import jit26_command
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
@app_commands.allowed_installs(guilds=True, users=True)
class Livecontainer(commands.GroupCog, name="livecontainer"):
def __init__(self, bot) -> None:
self.bot = bot
super().__init__()
@commands.group(name="livecontainer", invoke_without_command=True)
async def livecontainer_group(self, context: Context):
embed = discord.Embed(
title="LiveContainer Commands",
description="Choose a command from the dropdown below to get help with specific issues:",
color=0x0169FF,
)
embed.set_author(
name="LiveContainer",
icon_url="https://raw.githubusercontent.com/LiveContainer/LiveContainer/main/screenshots/livecontainer_icon.png",
)
view = LivecontainerView(self.bot)
await context.send(embed=embed, view=view)
@livecontainer_group.command(name="help")
async def livecontainer_group_help(self, context: Context):
embed = discord.Embed(
title="LiveContainer Commands",
description="Choose a command from the dropdown below to get help with specific issues:",
color=0x0169FF,
)
embed.set_author(
name="LiveContainer",
icon_url="https://raw.githubusercontent.com/LiveContainer/LiveContainer/main/screenshots/livecontainer_icon.png",
)
view = LivecontainerView(self.bot)
await context.send(embed=embed, view=view)
@livecontainer_group.command(name="26jit")
async def livecontainer_group_26jit(self, context: Context):
await self._invoke_hybrid(context, "26jit")
async def _invoke_hybrid(self, context: Context, name: str):
command = self.bot.get_command(name)
if command is not None:
await context.invoke(command)
else:
await context.send(f"Unknown LiveContainer command: {name}")
def _require_group_prefix(context: Context) -> bool:
if getattr(context, "interaction", None):
return True
group = getattr(getattr(context, "cog", None), "qualified_name", "").lower()
if not group:
return True
prefix = context.prefix or ""
content = context.message.content.strip().lower()
return content.startswith(f"{prefix}{group} ")
@app_commands.command(name="help", description="LiveContainer troubleshooting help")
async def help(self, interaction: discord.Interaction):
embed = discord.Embed(
title="LiveContainer Commands",
description="Choose a command from the dropdown below to get help with specific issues:",
color=0x0169FF,
)
embed.set_author(
name="LiveContainer",
icon_url="https://raw.githubusercontent.com/LiveContainer/LiveContainer/main/screenshots/livecontainer_icon.png",
)
view = LivecontainerView(self.bot)
await interaction.response.send_message(embed=embed, view=view, ephemeral=True)
@commands.check(_require_group_prefix)
@commands.hybrid_command(
name="26jit", description="Walkthrough for iOS 26 JIT and sideloading"
)
async def jit26(self, context):
return await jit26_command()(self, context)
async def setup(bot) -> None:
cog = Livecontainer(bot)
await bot.add_cog(cog)
bot.logger.info("Loaded extension 'livecontainer.help'")
bot.logger.info("Loaded extension 'livecontainer.26jit'")

View File

@@ -0,0 +1,38 @@
import discord
from discord.ext.commands import Context
def jit26_command():
async def command(self, context: Context):
embed = discord.Embed(
color=0x0169FF,
description=(
"# iOS 26 JIT & Sideloading Walkthrough\n\n---\n\n"
"Click the [button below](https://github.com/CelloSerenity/iOS-26-Sideloading-and-JIT-Complete-Walkthrough) to set up sideloading and enabling JIT for LiveContainer on iOS 26."
),
)
embed.set_author(
name="LiveContainer",
icon_url="https://raw.githubusercontent.com/LiveContainer/LiveContainer/main/screenshots/livecontainer_icon.png",
)
embed.set_footer(
icon_url="https://yes.nighty.works/raw/2PPWd3.webp",
text="Made By CelloSerenity",
)
view = discord.ui.View()
view.add_item(
discord.ui.Button(
label="Get Started",
url="https://github.com/CelloSerenity/iOS-26-Sideloading-and-JIT-Complete-Walkthrough",
style=discord.ButtonStyle.primary,
emoji="<:githubicon:1417717356846776340>",
)
)
if context.interaction:
await context.interaction.response.send_message(embed=embed, view=view)
else:
await context.send(embed=embed, view=view)
return command

View File

@@ -0,0 +1,90 @@
import discord
class LivecontainerSelect(discord.ui.Select):
def __init__(self, bot):
self.bot = bot
options = [
discord.SelectOption(
label="iOS 26 JIT & Sideloading",
value="26jit",
description="Walkthrough for iOS 26 JIT and sideloading",
),
]
super().__init__(
placeholder="Choose a LiveContainer command...", options=options
)
async def callback(self, interaction: discord.Interaction):
command_name = self.values[0]
command = self.bot.get_command(command_name)
if command:
try:
ctx = await self.bot.get_context(interaction.message)
if ctx:
await ctx.invoke(command)
embed = discord.Embed(
title="Command Executed",
description=f"Successfully executed `/{command_name}`",
color=0x00FF00,
)
embed.set_author(
name="LiveContainer",
icon_url="https://raw.githubusercontent.com/LiveContainer/LiveContainer/main/screenshots/livecontainer_icon.png",
)
await interaction.response.edit_message(embed=embed, view=None)
except discord.Forbidden:
guild_info = (
f"server {interaction.guild.name} (ID: {interaction.guild.id})"
if interaction.guild
else "DM or private channel"
)
self.bot.logger.warning(
f"Bot missing permissions in {guild_info} - cannot execute {command_name} command"
)
if interaction.guild is None:
embed = discord.Embed(
title="Error",
description="This command cannot be executed in DMs.",
color=0xFF0000,
)
else:
embed = discord.Embed(
title="Permission Error",
description="The bot needs the `send messages` permission to execute this command.",
color=0xFF0000,
)
embed.set_author(
name="LiveContainer",
icon_url="https://raw.githubusercontent.com/LiveContainer/LiveContainer/main/screenshots/livecontainer_icon.png",
)
await interaction.response.edit_message(embed=embed, view=None)
except Exception as e:
self.bot.logger.error(f"Error executing {command_name} command: {e}")
embed = discord.Embed(
title="Error",
description="An error occurred while executing the command.",
color=0xFF0000,
)
embed.set_author(
name="LiveContainer",
icon_url="https://raw.githubusercontent.com/LiveContainer/LiveContainer/main/screenshots/livecontainer_icon.png",
)
await interaction.response.edit_message(embed=embed, view=None)
else:
embed = discord.Embed(
title="Error", description="Command not found!", color=0xFF0000
)
embed.set_author(
name="LiveContainer",
icon_url="https://raw.githubusercontent.com/LiveContainer/LiveContainer/main/screenshots/livecontainer_icon.png",
)
await interaction.response.edit_message(embed=embed, view=None)
class LivecontainerView(discord.ui.View):
def __init__(self, bot):
super().__init__()
self.add_item(LivecontainerSelect(bot))

View File

@@ -4,7 +4,6 @@ from discord.ext import commands
from discord.ext.commands import Context
from typing import Optional
from .download import download_command
from .mcquote import mcquote_command
from .img2gif import img2gif_command
from .tweety import tweety_command
@@ -63,15 +62,12 @@ class Media(commands.GroupCog, name="media"):
)
embed.add_field(
name="Available",
value="download, mcquote, img2gif, tweety, tts",
value="mcquote, img2gif, tweety, tts",
inline=False,
)
await context.send(embed=embed)
async def _invoke_hybrid(self, context: Context, name: str, *args, **kwargs):
if name == "download":
await self.download(context, url=kwargs.get("url", ""))
return
if name == "mcquote":
await self.mcquote(context, text=kwargs.get("text", ""))
return
@@ -86,10 +82,6 @@ class Media(commands.GroupCog, name="media"):
return
await context.send(f"Unknown media command: {name}")
@media_group.command(name="download")
async def media_group_download(self, context: Context, *, url: str):
await self._invoke_hybrid(context, "download", url=url)
@media_group.command(name="mcquote")
async def media_group_mcquote(self, context: Context, *, text: str):
await self._invoke_hybrid(context, "mcquote", text=text)
@@ -108,14 +100,6 @@ class Media(commands.GroupCog, name="media"):
async def media_group_tts(self, context: Context, *, text: str = None):
await self._invoke_hybrid(context, "tts", text=text)
@commands.check(_require_group_prefix)
@commands.hybrid_command(
name="download",
description="Download a video from a URL using yt-dlp.",
)
async def download(self, context, *, url: str):
return await download_command()(self, context, url=url)
@commands.check(_require_group_prefix)
@commands.hybrid_command(
name="mcquote",
@@ -153,7 +137,6 @@ async def setup(bot) -> None:
cog = Media(bot)
await bot.add_cog(cog)
bot.logger.info("Loaded extension 'media.download'")
bot.logger.info("Loaded extension 'media.mcquote'")
bot.logger.info("Loaded extension 'media.img2gif'")
bot.logger.info("Loaded extension 'media.tweety'")

View File

@@ -1,531 +0,0 @@
import asyncio
import os
import tempfile
import discord
from discord.ext import commands
import yt_dlp
from urllib.parse import urlparse
import aiohttp
import logging
logger = logging.getLogger("discord_bot")
def download_command():
@commands.hybrid_command(
name="download",
description="Download a video from a URL using yt-dlp.",
)
@commands.cooldown(1, 30, commands.BucketType.user)
async def download(self, context, *, url: str):
if isinstance(context.channel, discord.DMChannel):
embed = discord.Embed(
title="Error",
description="This command can only be used in servers.",
color=0xE02B2B,
)
embed.set_author(
name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp"
)
interaction = getattr(context, "interaction", None)
if interaction is not None:
if not interaction.response.is_done():
await interaction.response.send_message(embed=embed, ephemeral=True)
else:
await interaction.followup.send(embed=embed, ephemeral=True)
else:
await context.send(embed=embed, ephemeral=True)
return
if isinstance(context.channel, discord.PartialMessageable):
embed = discord.Embed(
title="Error",
description="The bot needs the `send messages` permission in this channel.",
color=0xE02B2B,
)
embed.set_author(
name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp"
)
interaction = getattr(context, "interaction", None)
if interaction is not None:
if not interaction.response.is_done():
await interaction.response.send_message(embed=embed, ephemeral=True)
else:
await interaction.followup.send(embed=embed, ephemeral=True)
else:
await context.send(embed=embed, ephemeral=True)
return
if not url:
embed = discord.Embed(
title="Error",
description="Please provide a valid URL to download.",
color=0xE02B2B,
)
embed.set_author(
name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp"
)
interaction = getattr(context, "interaction", None)
if interaction is not None:
if not interaction.response.is_done():
await interaction.response.send_message(embed=embed, ephemeral=True)
else:
await interaction.followup.send(embed=embed, ephemeral=True)
else:
await context.send(embed=embed, ephemeral=True)
return
# Check if bot has send messages permission before starting download
try:
test_embed = discord.Embed(title="Testing permissions...", color=0x7289DA)
test_embed.set_author(
name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp"
)
await context.channel.send(embed=test_embed, delete_after=0.1)
except discord.Forbidden:
embed = discord.Embed(
title="Permission Error",
description="The bot needs the `send messages` permission to execute this command.",
color=0xE02B2B,
)
embed.set_author(
name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp"
)
interaction = getattr(context, "interaction", None)
if interaction is not None:
if not interaction.response.is_done():
await interaction.response.send_message(embed=embed, ephemeral=True)
else:
await interaction.followup.send(embed=embed, ephemeral=True)
else:
await context.send(embed=embed, ephemeral=True)
return
try:
parsed_url = urlparse(url)
if not parsed_url.scheme or not parsed_url.netloc:
embed = discord.Embed(
title="Error",
description="Please provide a valid URL.",
color=0xE02B2B,
)
embed.set_author(
name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp"
)
interaction = getattr(context, "interaction", None)
if interaction is not None:
if not interaction.response.is_done():
await interaction.response.send_message(
embed=embed, ephemeral=True
)
else:
await interaction.followup.send(embed=embed, ephemeral=True)
else:
await context.send(embed=embed, ephemeral=True)
return
except Exception:
embed = discord.Embed(
title="Error",
description="Please provide a valid URL.",
color=0xE02B2B,
)
embed.set_author(
name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp"
)
interaction = getattr(context, "interaction", None)
if interaction is not None:
if not interaction.response.is_done():
await interaction.response.send_message(embed=embed, ephemeral=True)
else:
await interaction.followup.send(embed=embed, ephemeral=True)
else:
await context.send(embed=embed, ephemeral=True)
return
processing_embed = discord.Embed(
title="Download (Processing)",
description="<a:mariospin:1423677027013103709> Downloading video... This may take a moment.",
color=0x7289DA,
)
processing_embed.set_author(
name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp"
)
interaction = getattr(context, "interaction", None)
if interaction is not None:
if not interaction.response.is_done():
await interaction.response.send_message(
embed=processing_embed, ephemeral=True
)
else:
await interaction.followup.send(embed=processing_embed, ephemeral=True)
else:
processing_msg = await context.send(embed=processing_embed)
temp_dir = tempfile.mkdtemp()
# Try Docker path first, fallback to local path for development
cookie_path = "/bot/cogs/media/files/cookies.txt"
if not os.path.exists(cookie_path):
cookie_path = os.path.join(
os.path.dirname(__file__), "files", "cookies.txt"
)
ydl_opts = {
"format": "bestvideo[filesize<200M]+bestaudio[filesize<200M]/best[filesize<200M]/bestvideo+bestaudio/best",
"outtmpl": os.path.join(temp_dir, "%(title)s.%(ext)s"),
"noplaylist": True,
"extract_flat": False,
"writesubtitles": False,
"writeautomaticsub": False,
"writethumbnail": False,
"ignoreerrors": False,
"merge_output_format": "mp4",
"cookiefile": cookie_path,
}
try:
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = await asyncio.get_event_loop().run_in_executor(
None, lambda: ydl.extract_info(url, download=True)
)
if not info:
raise Exception("Could not extract video information")
video_title = info.get("title", "Unknown Title")
video_duration_seconds = int(info.get("duration") or 0)
video_uploader = info.get("uploader", "Unknown")
video_url = info.get("webpage_url") or info.get("original_url") or url
platform = (
info.get("extractor") or info.get("extractor_key") or "Unknown"
)
view_count = info.get("view_count")
files = [
f
for f in os.listdir(temp_dir)
if os.path.isfile(os.path.join(temp_dir, f))
]
if not files:
raise Exception("No video file was downloaded")
video_file = os.path.join(temp_dir, files[0])
file_size = os.path.getsize(video_file)
logger.info(
f"File size: {file_size} bytes ({file_size / (1024 * 1024):.2f} MB)"
)
if file_size > 24 * 1024 * 1024: # 24MB limit
logger.info("File is over 24MB, uploading to Catbox")
async def upload_to_catbox(path: str) -> str:
try:
file_size_bytes = os.path.getsize(path)
except Exception:
file_size_bytes = -1
logger.info(
f"Catbox upload start: name={os.path.basename(path)} size={file_size_bytes}"
)
form = aiohttp.FormData()
form.add_field("reqtype", "fileupload")
form.add_field(
"fileToUpload",
open(path, "rb"),
filename=os.path.basename(path),
)
timeout = aiohttp.ClientTimeout(total=600)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.post(
"https://catbox.moe/user/api.php", data=form
) as resp:
text = await resp.text()
logger.info(
f"Catbox response: status={resp.status} body_len={len(text)}"
)
if resp.status == 200 and text.startswith("https://"):
url_text = text.strip()
logger.info(
f"Catbox upload success: url={url_text}"
)
return url_text
logger.error(
f"Catbox upload failed: status={resp.status} body={text.strip()[:500]}"
)
raise RuntimeError(f"Upload failed: {text.strip()}")
try:
link = await upload_to_catbox(video_file)
minutes, seconds = divmod(video_duration_seconds, 60)
duration_str = f"{minutes}:{seconds:02d}"
description_text = (
f"### **[{video_title}]({video_url})**"
if video_url
else f"### **{video_title}**"
)
embed = discord.Embed(
title="Download",
description=description_text,
color=0x7289DA,
)
embed.set_author(
name="Media",
icon_url="https://yes.nighty.works/raw/y5SEZ9.webp",
)
embed.add_field(
name="Uploader",
value=video_uploader or "Unknown",
inline=True,
)
embed.add_field(
name="Duration", value=duration_str, inline=True
)
embed.add_field(name="Platform", value=platform, inline=True)
embed.set_footer(
text=f"Requested by {context.author.name}",
icon_url=context.author.display_avatar.url,
)
if interaction is not None:
await context.channel.send(embed=embed)
await context.channel.send(link)
try:
await interaction.delete_original_response()
except:
pass
else:
await processing_msg.delete()
await context.channel.send(embed=embed)
await context.channel.send(link)
return
except Exception as upload_error:
logger.exception(f"Catbox upload exception: {upload_error}")
error_msg = str(upload_error)
if "greater than 200mb" in error_msg.lower():
description = "The video is too large to upload. The file exceeds 200MB (Catbox limit) and cannot be sent via Discord (25MB limit)."
else:
description = f"The video is over 25MB and upload to hosting failed: {upload_error}"
embed = discord.Embed(
title="Error",
description=description,
color=0xE02B2B,
)
embed.set_author(
name="Media",
icon_url="https://yes.nighty.works/raw/y5SEZ9.webp",
)
if interaction is not None:
try:
await interaction.delete_original_response()
except:
pass
await interaction.followup.send(embed=embed, ephemeral=True)
else:
await processing_msg.delete()
await context.send(embed=embed, ephemeral=True)
return
else:
logger.info("File is under 24MB, sending directly to Discord")
minutes, seconds = divmod(video_duration_seconds, 60)
duration_str = f"{minutes}:{seconds:02d}"
description_text = (
f"### **[{video_title}]({video_url})**"
if video_url
else f"### **{video_title}**"
)
embed = discord.Embed(
title="Download",
description=description_text,
color=0x7289DA,
)
embed.set_author(
name="Media",
icon_url="https://yes.nighty.works/raw/y5SEZ9.webp",
)
embed.add_field(
name="Uploader", value=video_uploader or "Unknown", inline=True
)
embed.add_field(name="Duration", value=duration_str, inline=True)
embed.add_field(name="Platform", value=platform, inline=True)
embed.set_footer(
text=f"Requested by {context.author.name}",
icon_url=context.author.display_avatar.url,
)
try:
with open(video_file, "rb") as f:
file = discord.File(f, filename=files[0])
if interaction is not None:
await context.channel.send(embed=embed)
await context.channel.send(file=file)
try:
await interaction.delete_original_response()
except:
pass
else:
await processing_msg.delete()
await context.channel.send(embed=embed)
await context.channel.send(file=file)
except discord.HTTPException as e:
if e.status == 413:
logger.info(
"Discord rejected file (413), falling back to Catbox upload"
)
async def upload_to_catbox(path: str) -> str:
try:
file_size_bytes = os.path.getsize(path)
except Exception:
file_size_bytes = -1
logger.info(
f"Catbox upload start: name={os.path.basename(path)} size={file_size_bytes}"
)
form = aiohttp.FormData()
form.add_field("reqtype", "fileupload")
form.add_field(
"fileToUpload",
open(path, "rb"),
filename=os.path.basename(path),
)
timeout = aiohttp.ClientTimeout(total=600)
async with aiohttp.ClientSession(
timeout=timeout
) as session:
async with session.post(
"https://catbox.moe/user/api.php", data=form
) as resp:
text = await resp.text()
logger.info(
f"Catbox response: status={resp.status} body_len={len(text)}"
)
if resp.status == 200 and text.startswith(
"https://"
):
url_text = text.strip()
logger.info(
f"Catbox upload success: url={url_text}"
)
return url_text
logger.error(
f"Catbox upload failed: status={resp.status} body={text.strip()[:500]}"
)
raise RuntimeError(
f"Upload failed: {text.strip()}"
)
try:
link = await upload_to_catbox(video_file)
description_text_with_link = (
f"### **[{video_title}]({video_url})**\n\n{link}"
if video_url
else f"### **{video_title}**\n\n{link}"
)
embed = discord.Embed(
title="Download",
description=description_text,
color=0x7289DA,
)
embed.set_author(
name="Media",
icon_url="https://yes.nighty.works/raw/y5SEZ9.webp",
)
embed.add_field(
name="Uploader",
value=video_uploader or "Unknown",
inline=True,
)
embed.add_field(
name="Duration", value=duration_str, inline=True
)
embed.add_field(
name="Platform", value=platform, inline=True
)
embed.set_footer(
text=f"Requested by {context.author.name}",
icon_url=context.author.display_avatar.url,
)
if interaction is not None:
await context.channel.send(embed=embed)
await context.channel.send(link)
try:
await interaction.delete_original_response()
except:
pass
else:
await processing_msg.delete()
await context.channel.send(embed=embed)
await context.channel.send(link)
except Exception as upload_error:
logger.exception(
f"Catbox upload exception: {upload_error}"
)
embed = discord.Embed(
title="Error",
description=f"Discord rejected the file and Catbox upload failed: {upload_error}",
color=0xE02B2B,
)
embed.set_author(
name="Media",
icon_url="https://yes.nighty.works/raw/y5SEZ9.webp",
)
if interaction is not None:
try:
await interaction.delete_original_response()
except:
pass
await interaction.followup.send(
embed=embed, ephemeral=True
)
else:
await processing_msg.delete()
await context.send(embed=embed, ephemeral=True)
else:
raise e
except Exception as e:
embed = discord.Embed(
title="Error",
description=f"Failed to download video: {str(e)}",
color=0xE02B2B,
)
embed.set_author(
name="Media", icon_url="https://yes.nighty.works/raw/y5SEZ9.webp"
)
if interaction is not None:
try:
await interaction.delete_original_response()
except:
pass
await interaction.followup.send(embed=embed, ephemeral=True)
else:
try:
await processing_msg.delete()
except:
pass
await context.send(embed=embed, ephemeral=True)
finally:
for file in os.listdir(temp_dir):
try:
os.remove(os.path.join(temp_dir, file))
except:
pass
try:
os.rmdir(temp_dir)
except:
pass
return download

View File

@@ -1,4 +1,3 @@
import asyncio
import os
import tempfile
import discord

View File

@@ -1,6 +1,5 @@
import asyncio
import io
import tempfile
from typing import Optional
import discord

View File

@@ -267,7 +267,7 @@ class TweetyView(discord.ui.View):
os.remove(temp_file_path)
except Exception as e:
except Exception:
embed = discord.Embed(
title="Error",
description="Error regenerating tweet image",
@@ -462,7 +462,7 @@ def tweety_command():
)
embed = discord.Embed(
title="Tweet Generated",
description=f"<:error:1424007141768822824> Tweet sometimes may look a bit broken, im gonna rewrite the API another time... (it wasnt made for Syntrel in the first place)",
description="<:error:1424007141768822824> Tweet sometimes may look a bit broken, im gonna rewrite the API another time... (it wasnt made for Syntrel in the first place)",
color=0x7289DA,
)
embed.set_author(
@@ -491,7 +491,7 @@ def tweety_command():
await processing_msg.delete()
embed = discord.Embed(
title="Error",
description=f"Connection error: Could not reach tweet API",
description="Connection error: Could not reach tweet API",
color=0xE02B2B,
)
embed.set_author(

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def error_command():
@@ -23,7 +20,7 @@ def error_command():
embed.set_author(
name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png"
)
embed.set_footer(text=f"Last Edited by Meshal :D")
embed.set_footer(text="Last Edited by Meshal :D")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def crash_command():
@@ -26,7 +23,7 @@ def crash_command():
embed.set_author(
name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png"
)
embed.set_footer(text=f"Last Edited by Meshal :D")
embed.set_footer(text="Last Edited by Meshal :D")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def ios26_command():
@@ -26,7 +23,7 @@ def ios26_command():
embed.set_author(
name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png"
)
embed.set_footer(text=f"Last Edited by Meshal :D")
embed.set_footer(text="Last Edited by Meshal :D")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def legal_command():
@@ -38,7 +35,7 @@ def legal_command():
embed.set_author(
name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png"
)
embed.set_footer(text=f"Last Edited by stossy11")
embed.set_footer(text="Last Edited by stossy11")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def mods_command():
@@ -29,7 +26,7 @@ def mods_command():
embed.set_author(
name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png"
)
embed.set_footer(text=f"Last Edited by Meshal :D")
embed.set_footer(text="Last Edited by Meshal :D")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def requirements_command():
@@ -24,7 +21,7 @@ def requirements_command():
embed.set_author(
name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png"
)
embed.set_footer(text=f"Last Edited by Meshal :D")
embed.set_footer(text="Last Edited by Meshal :D")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def transfer_command():
@@ -35,7 +32,7 @@ def transfer_command():
embed.set_author(
name="MeloNX", icon_url="https://yes.nighty.works/raw/TLGaVa.png"
)
embed.set_footer(text=f"Last Edited by Meshal :D")
embed.set_footer(text="Last Edited by Meshal :D")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()

View File

@@ -57,4 +57,3 @@ def upgrade_command():
await context.send(embed=embed, view=view)
return upgrade

View File

@@ -1,6 +1,5 @@
import discord
from discord.ext import commands
from discord.ext.commands import Context
import aiohttp
import io

View File

@@ -1,6 +1,5 @@
import discord
from discord.ext import commands
from discord.ext.commands import Context
import aiohttp
import io

View File

@@ -1,6 +1,5 @@
import discord
from discord.ext import commands
from discord.ext.commands import Context
import aiohttp
import io

View File

@@ -1,6 +1,4 @@
import discord
from discord.ext import commands
from discord.ext.commands import Context
def duck_command():

View File

@@ -1,6 +1,5 @@
import discord
from discord.ext import commands
from discord.ext.commands import Context
import random

View File

@@ -1,6 +1,5 @@
import discord
from discord.ext import commands
from discord.ext.commands import Context
def labubu_command():
@@ -52,7 +51,7 @@ def labubu_command():
embed.set_author(
name="Labubu", icon_url="https://yes.nighty.works/raw/YxMC0r.png"
)
embed.set_footer(text=f"May look broken on mobile")
embed.set_footer(text="May look broken on mobile")
if getattr(context, "interaction", None):
inter = context.interaction

View File

@@ -1,6 +1,5 @@
import discord
from discord.ext import commands
from discord.ext.commands import Context
import aiohttp
import io

View File

@@ -1,6 +1,5 @@
import discord
from discord.ext import commands
from discord.ext.commands import Context
import aiohttp
import io

View File

@@ -1,6 +1,5 @@
import discord
from discord.ext import commands
from discord.ext.commands import Context
import aiohttp
import io

View File

@@ -1,6 +1,4 @@
import discord
from discord.ext import commands
from discord.ext.commands import Context
def silly_command():

View File

@@ -1,6 +1,5 @@
import discord
from discord.ext import commands
from discord.ext.commands import Context
import aiohttp
import io

View File

@@ -1,6 +1,4 @@
import discord
from discord.ext import commands
from discord.ext.commands import Context
def tryitandsee_command():

View File

@@ -3,7 +3,6 @@ from datetime import datetime
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
def archive_command():

View File

@@ -1,7 +1,6 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
def ban_command():
@@ -222,7 +221,7 @@ def ban_command():
)
await context.send(embed=embed, ephemeral=True)
except Exception as e:
except Exception:
embed = discord.Embed(
title="Error!",
description="An unexpected error occurred.",

View File

@@ -1,7 +1,6 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
def hackban_command():

View File

@@ -1,7 +1,6 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
def kick_command():
@@ -142,7 +141,7 @@ def kick_command():
)
await context.send(embed=embed, ephemeral=True)
except Exception as e:
except Exception:
embed = discord.Embed(
title="Error!",
description="An unexpected error occurred.",

View File

@@ -1,7 +1,6 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
def nick_command():

View File

@@ -1,7 +1,6 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
def purge_command():

View File

@@ -92,7 +92,7 @@ class Logs(commands.Cog, name="logs"):
selected_lines = all_lines[-lines:] if len(all_lines) > lines else all_lines
log_content = "".join(selected_lines)
log_file = f"logs.txt"
log_file = "logs.txt"
with open(log_file, "w", encoding="utf-8") as f:
f.write(
f"Bot logs extracted at {discord.utils.utcnow().strftime('%Y-%m-%d %H:%M:%S')} UTC\n"

View File

@@ -13,6 +13,7 @@ from .afc import afc_command
from .udid import udid_command
from .half import half_command
from .sparse import sparse_command
from .unofficial import unofficial_command
@app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True)
@@ -103,6 +104,10 @@ class Sidestore(commands.GroupCog, name="sidestore"):
async def sidestore_group_sparse(self, context: Context):
await self._invoke_hybrid(context, "sparse")
@sidestore_group.command(name="unofficial")
async def sidestore_group_unofficial(self, context: Context):
await self._invoke_hybrid(context, "unofficial")
@app_commands.command(name="help", description="SideStore troubleshooting help")
async def help(self, interaction: discord.Interaction):
embed = discord.Embed(
@@ -180,6 +185,13 @@ class Sidestore(commands.GroupCog, name="sidestore"):
async def sparse(self, context):
return await sparse_command()(self, context)
@commands.check(_require_group_prefix)
@commands.hybrid_command(
name="unofficial", description="Unofficial guides and video walkthroughs"
)
async def unofficial(self, context):
return await unofficial_command()(self, context)
async def setup(bot) -> None:
cog = Sidestore(bot)
@@ -195,3 +207,4 @@ async def setup(bot) -> None:
bot.logger.info("Loaded extension 'sidestore.udid'")
bot.logger.info("Loaded extension 'sidestore.half'")
bot.logger.info("Loaded extension 'sidestore.sparse'")
bot.logger.info("Loaded extension 'sidestore.unofficial'")

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def afc_command():
@@ -15,15 +12,15 @@ def afc_command():
description=(
"# AFC Connection Failure\n\n---\n\n"
+ "1. Make sure Wi-Fi is connected to a stable network\n"
+ "2. Make sure StosVPN is connected and updated\n"
+ "3. If issue still persists, create and import a new pairing file using `idevice_pair`. See [Pairing File instructions](https://docs.sidestore.io/docs/installation/pairing-file) for details"
+ "2. Make sure LocalDevVPN is connected and updated\n"
+ "3. If issue still persists, replace pairing file using `iloader`. See [Pairing File instructions](https://docs.sidestore.io/docs/advanced/pairing-file) for details"
),
)
embed.set_author(
name="SideStore",
icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true",
)
embed.set_footer(text=f"Last Edited by CelloSerenity")
embed.set_footer(text="Last Edited by CelloSerenity")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()
@@ -39,7 +36,7 @@ def afc_command():
discord.ui.Button(
label="Documentation",
style=discord.ButtonStyle.primary,
url="https://docs.sidestore.io/docs/troubleshooting/common-issues#afc-connection-failure",
url="https://docs.sidestore.io/docs/troubleshooting/common-issues/#afc-connection-failure--no-wi-fi-or-vpn-connection",
emoji="<:sidestorepride:1417717648795631787>",
)
)

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def code_command():
@@ -34,7 +31,7 @@ def code_command():
name="SideStore",
icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true",
)
embed.set_footer(text=f"Last Edited by CelloSerenity")
embed.set_footer(text="Last Edited by CelloSerenity")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def crash_command():
@@ -15,12 +12,12 @@ def crash_command():
description=(
"# SideStore Crashing After Refresh\n\n---\n\n"
+ "First, to try and save your data:\n"
+ "1. DON'T DELETE SIDESTORE, reinstall with AltServer's `Sideload .ipa`.\n"
+ "1. DON'T DELETE SIDESTORE, reinstall using iloader's Install `SideStore (Stable)` button.\n"
+ "If that doesn't work:\n"
+ "1. Delete your current SideStore. Reinstall with AltServer.\n"
+ "1. Delete your current SideStore. Reinstall with iloader.\n"
+ "2. Import your pairing file and sign into SideStore.\n"
+ "3. Download the SideStore .ipa file, and save it to your Files app.\n"
+ '4. Import the "Sidestore.ipa" file into SideStore, just like how you import any other IPA.\n\n'
+ '4. Import the "SideStore.ipa" file into SideStore, just like how you import any other IPA.\n\n'
+ "This process ensures SideStore is refreshed without issues."
),
)
@@ -28,7 +25,7 @@ def crash_command():
name="SideStore",
icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true",
)
embed.set_footer(text=f"Last Edited by CelloSerenity")
embed.set_footer(text="Last Edited by CelloSerenity")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def half_command():
@@ -21,7 +18,7 @@ def half_command():
+ "- Change Anisette Server\n"
+ "- Reset adi.pb\n"
+ "- Sign out from SideStore and sign back in\n"
+ "- Recreate pairing file\n"
+ "- Replace pairing file\n"
+ "- Reinstall SideStore\n\n"
),
)
@@ -29,7 +26,7 @@ def half_command():
name="SideStore",
icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true",
)
embed.set_footer(text=f"Last Edited by CelloSerenity")
embed.set_footer(text="Last Edited by CelloSerenity")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()
@@ -45,7 +42,7 @@ def half_command():
discord.ui.Button(
label="Documentation",
style=discord.ButtonStyle.primary,
url="https://docs.sidestore.io/docs/troubleshooting/common-issues#sidestore-hangs-halfway-through-installation",
url="https://docs.sidestore.io/docs/troubleshooting/common-issues/#sidestore-hangs-partway-through-installation",
emoji="<:sidestorepride:1417717648795631787>",
)
)

View File

@@ -1,40 +1,22 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def pairing_command():
@commands.hybrid_command(
name="pairing", description="Help with pairing file issues"
)
@commands.hybrid_command(name="pairing", description="Link to the pairing guide")
async def pairing(self, context):
embed = discord.Embed(
color=0x8E82F9,
description=(
"# Cannot Choose Pairing File\n\n---\n\n"
+ "1. **Check File Extension:**\n"
+ " Make sure your pairing file's extension ends with `.mobiledevicepairing` or `.plist`\n"
+ " - If it doesn't, double-check to see if you had zipped your pairing file before sending it to your phone. Failing to do so may lead to the file being corrupted during transport\n\n"
+ "2. **Move Pairing File:**\n"
+ " If you are unable to select the pairing file from within the app:\n"
+ " - Rename the file to `ALTPairingFile.mobiledevicepairing`\n"
+ ' - Try moving the pairing file to the root directory of the SideStore folder in the Files app under "On My iPhone/iPad"\n\n'
+ "3. **Certificate Signing:**\n"
+ " When signing SideStore with certain certificates, you won't be able to select the pairing file from within the app\n"
+ " - Try the fix mentioned above\n"
+ " - If you do not see the SideStore folder in the Files app:\n"
+ " • Connect your phone to your computer\n"
+ " • Drag and drop the pairing file into the SideStore app's files section\n"
+ " • Ensure the file is renamed to `ALTPairingFile.mobiledevicepairing`\n"
"# How to obtain your pairing file:\n\n---\n\n"
+ "[Click here](https://docs.sidestore.io/advanced/pairing-file) to read the SideStore documentation on replacing your pairing file.\n"
),
)
embed.set_author(
name="SideStore",
icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true",
)
embed.set_footer(text=f"Last Edited by CelloSerenity")
embed.set_footer(text="Last Edited by CelloSerenity")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()
@@ -50,7 +32,7 @@ def pairing_command():
discord.ui.Button(
label="Documentation",
style=discord.ButtonStyle.primary,
url="https://docs.sidestore.io/docs/troubleshooting/#cannot-choose-pairing-file",
url="https://docs.sidestore.io/docs/advanced/pairing-file",
emoji="<:sidestorepride:1417717648795631787>",
)
)

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def refresh_command():
@@ -15,16 +12,16 @@ def refresh_command():
description=(
"# Can't Refresh or Install Apps\n\n---\n\n"
+ "1. Make sure your device is connected to a stable Wi-Fi network and not using cellular data.\n"
+ "2. Verify VPN is connected in the StosVPN app.\n"
+ "3. **Create a brand new pairing file.**\n"
+ " - If none of the above worked, it is very likely that the pairing file is corrupted. You can reference the documentation on how to create a new pairing file [here](https://docs.sidestore.io/docs/installation/pairing-file).\n"
+ "2. Verify VPN is connected in the LocalDevVPN app.\n"
+ "3. **Replace your pairing file.**\n"
+ " - If none of the above worked, it is very likely that the pairing file is corrupted. You can reference the documentation on how to replace your pairing file [here](https://docs.sidestore.io/docs/advanced/pairing-file).\n"
),
)
embed.set_author(
name="SideStore",
icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true",
)
embed.set_footer(text=f"Last Edited by neoarz")
embed.set_footer(text="Last Edited by CelloSerenity")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()
@@ -40,7 +37,7 @@ def refresh_command():
discord.ui.Button(
label="Documentation",
style=discord.ButtonStyle.primary,
url="https://docs.sidestore.io/docs/installation/pairing-file",
url="https://docs.sidestore.io/docs/advanced/pairing-file",
emoji="<:sidestorepride:1417717648795631787>",
)
)

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def server_command():
@@ -28,7 +25,7 @@ def server_command():
name="SideStore",
icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true",
)
embed.set_footer(text=f"Last Edited by CelloSerenity")
embed.set_footer(text="Last Edited by CelloSerenity")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
class SidestoreSelect(discord.ui.Select):
@@ -54,6 +51,11 @@ class SidestoreSelect(discord.ui.Select):
value="udid",
description="SideStore could not determine device UDID",
),
discord.SelectOption(
label="Unofficial Guides",
value="unofficial",
description="Unofficial guides and video walkthroughs",
),
]
super().__init__(placeholder="Choose a SideStore command...", options=options)

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def sparse_command():
@@ -23,7 +20,7 @@ def sparse_command():
name="SideStore",
icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true",
)
embed.set_footer(text=f"Last Edited by neoarz")
embed.set_footer(text="Last Edited by neoarz")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()
@@ -39,7 +36,7 @@ def sparse_command():
discord.ui.Button(
label="Documentation",
style=discord.ButtonStyle.primary,
url="https://docs.sidestore.io/docs/advanced/sparserestore",
url="https://docs.sidestore.io/docs/advanced/alternative#sparserestore-3-app-limit",
emoji="<:sidestorepride:1417717648795631787>",
)
)

View File

@@ -1,8 +1,5 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import time
def udid_command():
@@ -14,15 +11,15 @@ def udid_command():
color=0x8E82F9,
description=(
"# SideStore Could Not Determine Device UDID\n\n---\n\n"
+ "This error usually occurs when the pairing file is corrupted. Please create a new pairing file using `idevice_pair` and try again.\n\n"
+ "If you forgot how to create a new pairing file, you can refer to the documentation below."
+ "This error usually occurs when the pairing file is corrupted. Please replace your pairing file using `iloader` and try again.\n\n"
+ "If you forgot how to replace your pairing file, you can refer to the documentation below."
),
)
embed.set_author(
name="SideStore",
icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true",
)
embed.set_footer(text=f"Last Edited by CelloSerenity")
embed.set_footer(text="Last Edited by CelloSerenity")
embed.timestamp = discord.utils.utcnow()
view = discord.ui.View()
@@ -38,7 +35,7 @@ def udid_command():
discord.ui.Button(
label="Documentation",
style=discord.ButtonStyle.secondary,
url="https://docs.sidestore.io/docs/installation/pairing-file/",
url="https://docs.sidestore.io/docs/advanced/pairing-file/",
emoji="<:sidestorepride:1417717648795631787>",
)
)

View File

@@ -0,0 +1,49 @@
import discord
from discord.ext import commands
def unofficial_command():
@commands.hybrid_command(
name="unofficial", description="Unofficial guides and video walkthroughs"
)
async def unofficial(self, context):
embed = discord.Embed(
color=0x8E82F9,
description=(
"# Unofficial Guides and Videos\n\n---\n\n"
+ "**PLEASE ONLY READ THE OFFICIAL DOCUMENTATION AND TROUBLESHOOTING GUIDE LOCATED AT https://docs.sidestore.io**\n\n"
+ "If you do not try this first we **WILL NOT** provide support.\n\n"
+ "There are currently **NO official video walkthroughs**."
),
)
embed.set_author(
name="SideStore",
icon_url="https://github.com/SideStore/assets/blob/main/icons/classic/Default.png?raw=true",
)
embed.set_footer(text="Last Edited by CelloSerenity")
embed.timestamp = discord.utils.utcnow()
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/sidestore/unofficial.py",
emoji="<:githubicon:1417717356846776340>",
)
)
view.add_item(
discord.ui.Button(
label="Documentation",
style=discord.ButtonStyle.primary,
url="https://docs.sidestore.io",
emoji="<:sidestorepride:1417717648795631787>",
)
)
if context.interaction:
await context.interaction.response.send_message(embed=embed, view=view)
else:
await context.send(embed=embed, view=view)
return unofficial

View File

@@ -1,10 +1,8 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import aiohttp
import re
import json
def codepreview_command():

View File

@@ -1,7 +1,6 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import aiohttp
@@ -38,7 +37,7 @@ def dictionary_command():
}
except aiohttp.ClientError:
return {"success": False, "error": "Network error occurred"}
except Exception as e:
except Exception:
return {"success": False, "error": "An unexpected error occurred"}
@commands.hybrid_command(
@@ -124,7 +123,7 @@ def dictionary_command():
meanings = entry.get("meanings", [])
embed = discord.Embed(
title=f"Dictionary",
title="Dictionary",
description=f"**```{word_title}```**",
color=0x7289DA,
)

View File

@@ -1,12 +1,9 @@
import discord
from discord import app_commands
from discord.ext import commands
from discord.ext.commands import Context
import aiohttp
import asyncio
import re
import json
import urllib.parse
languages = {

View File

@@ -7,7 +7,6 @@ services:
volumes:
- ./database:/bot/database
- ./discord.log:/bot/discord.log
- ./cogs/media/files:/bot/cogs/media/files
# Alternatively you can set the environment variables as such:
# /!\ The token shouldn't be written here, as this file is not ignored from Git /!\

View File

@@ -2,7 +2,6 @@ aiohttp
aiosqlite
discord.py
python-dotenv
yt-dlp
Pillow
pillow-heif
pytz

View File

@@ -1,8 +1,6 @@
#!/usr/bin/env python3
# From https://github.com/neoarz/Velora/blob/main/Velora/ui/ascii.py
import math
def gradient_text(text, start_color, end_color):
def rgb_interp(start, end, t):