mirror of
https://github.com/neoarz/Syntrel.git
synced 2026-02-08 22:33:25 +01:00
Compare commits
11 Commits
livecontai
...
f6d3fc4bb4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6d3fc4bb4 | ||
|
|
9c71bf42d7 | ||
|
|
aaefc42664 | ||
|
|
e086dd4351 | ||
|
|
63e26bac74 | ||
|
|
bc2cfb57d1 | ||
|
|
6d61482216 | ||
|
|
b7d010e44c | ||
|
|
0d3fcb8146 | ||
|
|
23790c46b4 | ||
|
|
4a9b6b1f06 |
4
LICENSE
4
LICENSE
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
2
bot.py
2
bot.py
@@ -282,4 +282,4 @@ if __name__ == "__main__":
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Received keyboard interrupt")
|
||||
except Exception as e:
|
||||
logger.critical(f"Fatal error during bot execution: {type(e).__name__}: {e}")
|
||||
logger.critical(f"Fatal error during bot execution: {type(e).__name__}: {e}")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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'")
|
||||
|
||||
@@ -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
312
cogs/events/stickybot.py
Normal 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)
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import aiohttp
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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.
@@ -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
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
93
cogs/livecontainer/__init__.py
Normal file
93
cogs/livecontainer/__init__.py
Normal 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'")
|
||||
38
cogs/livecontainer/jit26.py
Normal file
38
cogs/livecontainer/jit26.py
Normal 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
|
||||
90
cogs/livecontainer/livecontainer.py
Normal file
90
cogs/livecontainer/livecontainer.py
Normal 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))
|
||||
@@ -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'")
|
||||
|
||||
@@ -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
|
||||
@@ -1,4 +1,3 @@
|
||||
import asyncio
|
||||
import os
|
||||
import tempfile
|
||||
import discord
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import asyncio
|
||||
import io
|
||||
import tempfile
|
||||
from typing import Optional
|
||||
|
||||
import discord
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -57,4 +57,3 @@ def upgrade_command():
|
||||
await context.send(embed=embed, view=view)
|
||||
|
||||
return upgrade
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import aiohttp
|
||||
import io
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import aiohttp
|
||||
import io
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import aiohttp
|
||||
import io
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
def duck_command():
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import random
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import aiohttp
|
||||
import io
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import aiohttp
|
||||
import io
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import aiohttp
|
||||
import io
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
def silly_command():
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
import aiohttp
|
||||
import io
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Context
|
||||
|
||||
|
||||
def tryitandsee_command():
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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'")
|
||||
|
||||
@@ -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>",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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>",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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>",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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>",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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>",
|
||||
)
|
||||
)
|
||||
|
||||
49
cogs/sidestore/unofficial.py
Normal file
49
cogs/sidestore/unofficial.py
Normal 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
|
||||
@@ -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():
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 /!\
|
||||
|
||||
@@ -2,7 +2,6 @@ aiohttp
|
||||
aiosqlite
|
||||
discord.py
|
||||
python-dotenv
|
||||
yt-dlp
|
||||
Pillow
|
||||
pillow-heif
|
||||
pytz
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user